5.1 字符串创建与存储的机制是什么?
- 对于 String s1 = new String(“abc”) 与 String s2 = new String(“abc”)语句,存在两个引用对象s1和s2,两个内容相同的字符串对象“abc”,它们在内存中的地址是不同的。只要用到new总会生成新的对象。
- 对于String s1 = "abc"与String s2 = "abc"语句,在JVM中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,s1,s2引用的是同一个常量池中的对象。String的实现采用了 享元(Flyweight)的设计模式,当创建一个字符串常量时,例如String s = “abc”,会首先在字符串常量池中查找是否已经有相同的字符串被定义,判断依据是String类equals(Object obj)方法的返回值。由于String是不可变类,一旦创建好了就不能被修改,因此String对象可以被共享而且不会导致程序混乱。
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。
String s = "abc"; // 把“abc”放到常量区中,在编译时产生
String s = "ab" + "c"; // 把"ab" + "c"转换为字符串常量“abc”放到常量区
String s = new String("abc"); // 在运行时把“abc”放到堆里面
String s1 = "abc"; // 在常量区里面存放了一个"abc"字符串对象
String s2 = "abc"; // s2引用常量区中的对象,因此不会创建新的对象
String s3 = new String("abc"); // 在堆中创建新的对象
String s4 = new String("abc"); // 在堆中又创建了一个新的对象
String s = new String(“abc”);详细解释:
语句的执行可以分为两个阶段。第一个阶段是创建对象的过程,即new String(“abc”);第二个过程是赋值的过程,即String s =。第一个过程中new String(“abc”);会调用String的构造函数:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
new String(“abc”);创建了几个对象?
一个或两个。 如果常量池中有“abc”,那么只创建一个对象;如果常量池中没有字符串“abc”,那么就会创建两个对象。
5.2 “==”、equals和hashCode有什么区别?
- “==”运算符用来比较两个变量的值是否相等。通常比较两个基本数据类型或两个引用变量是否相等。
- 对于赋值语句String s1 = new String() ,变量s占用一块内存空间,而new String()则存储在另外一块存储空间里,此时,变量s所对应内存中存储的数值就是对象占用的那块内存的首地址。如果要比较两个变量是否指向同一对象,即要看这两个变量所对应内存中的数值是否相等,这时就可以用“==”运算符进行比较。
- equals是Object类提供的方法之一。 每个Java类都继承自Object类,所以每个对象都具有equals方法。Object类中定义的equals(Object)方法是直接用“ == ” 运算符比较两个对象,所以在没有覆盖equals(Object)方法的情况下,equals(Object) 与 “ == ”运算符一样,比较的是引用。
- 相比“==”运算符,equals(Object)方法可以被覆盖,所以可以通过覆盖的方法让它不是比较引用而是比较数据内容。
Object类equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
String类中重写Object类的equals方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
如果一个类没有自己定义equals()方法,那么它将继承Object类的equals()方法。也就是 “ == ” 运算符,此时使用 “ ==” 运算符和equals()方法会得到相同结果。如果编写的类希望 比较实例的内容是否相等,那么必须覆盖equals()方法。
hashCode()方法:
hashCode()方法是从Object类中继承过来的,他也可以用来鉴定两个对象是否相等。Object类中的hashCode()方法返回对象在内存中地址转成的一个int值,所以如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。
equals()方法和hashCode()方法的区别:
一般来讲,equals()方法是给用户调用的,如果需要判断两个对象是否相等,可以重写equals()方法,然后在代码中调用就可以了。对于hashCode()方法,用户一般不会调用它,例如在hashmap中,由于Key是不可重复的,它在判断key是否重复时就判断了hashCode()方法,而且也用到了equals()方法。此处的“不可以重复”指的是equals()和hashCode()只要有一个不等就可以了。
覆盖约定:
一般在覆盖equal()方法的同时 也要覆盖hashCode()方法,否则,就会违反Object.hashCode约定,导致该类无法与所有基于散列值(hash)的集合类(HashMap、HashSet和Hashtable)结合在一起正常运行。
hashCode()方法的返回值和equals()方法的关系:
如果x.equals(y)为true,那么这两个对象的hashCode()方法的返回值必须是相同结果; 如果x.equals(y)为false,那么这两个对象的hashCode()方法的返回值可能相等,可能不相等。
反之,如果hashCode()方法的返回值不相等,一定能推出equals()方法的返回值也不相等,而hashCode()方法的返回值相等,equals()方法的返回值可能相等,可能不相等。
为什么不同的对象hashcode可能相同:
因为当输入数据量太大,哈希值却是固定32长度的,这意味着哈希值是一个有限集合,无法建立一对一关系,所以hashcode相等是有可能会发生的。
Object类中的hashCode()方法:
public native int hashCode();
String类中重写Object类的hashCode()方法:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
Set里的元素是不能重复的,那么用什么方法来区分是否重复呢?
用equals()方法来区分是否重复。
5.3 String、StringBuffer、StringBuilder和StringTokenizer有什么区别?
Java语言有四个类可以对字符或字符串进行操作。Character(单个字符)、String、StringBuffer和StringTokenizer。
String类是不可变类,String对象一旦被创建,其值将不能被改变,适合在被 共享的场合使用;StringBuffer是可变类,当一个字符串经常被修改时,最好使用StringBuffer实现。使用String会生成许多无用的对象被垃圾回收器回收,从而影响性能。
String初始化,可以利用构造函数和赋值语句;而StringBuffer初始化只能使用构造函数。
String字符串的修改原理机制:
当用String类型对字符串进行修改时,其实现方法是首先创建一个StringBilder,其次调用StringBilder的append()方法,最后调用StringBuilder的toString()方法返回结果。
String s = "Hello";
s += "World";
//以上代码等价于以下代码
StringBilder sb = new StringBilder();
sb.append("World");
package org.base;
public class Test {
public static void testString(){
String s = "Hello";
String s1 = "world";
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s += s1;
}
long end = System.currentTimeMillis();
System.out.println("testString:" + (end - start));
}
public static void testStringBuilder(){
StringBuilder s = new StringBuilder("Hello");
String s1 = "world";
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s.append(s1);
}
long end = System.currentTimeMillis();
System.out.println("testStringBuilder:" + (end - start));
}
public static void main(String[] args) {
testString();
testStringBuilder();
}
}
运行结果:
testString:249
testStringBuilder:1
StringBuilder和StringBuffer都是字符串缓冲区。StringBuillder是线程不安全的,如果只是在单线程中使用字符串缓冲区,那么StringBuilder效率高。当有多个线程访问时,使用StringBuffer。因为StringBuffer必要时可以对这些方法进行同步。
StringTokenizer:分隔字符串的工具类。
package org.base;
import java.util.StringTokenizer;
public class Test {
public static void main(String[] args) {
StringTokenizer st = new StringTokenizer("Welcome to our country!");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
}
}
运行结果:
Welcome
to
our
country!
5.4 Java中数组是不是对象?
数组是指具有相同类型的数据的集合,具有固定长度,并且在内存中占据连续的空间。在C/C++语言中,数组名是一个指针,指向数组的首元素,既没有属性也没有方法,在Java语言中,数组有自己的属性(length属性),也有一些方法可以被调用(clone方法)。这个角度来讲,数组是对象。
通过instanceof判断数据的类型:
package org.base;
public class Test {
public static void main(String[] args) {
int[] a = {1,2};
int[][] b = new int[2][4];
String[] s = {"a","b"};
if(a instanceof int[])
System.out.println("the type for a is int[]");
if(b instanceof int[][])
System.out.println("the type for a is int[][]");
if(s instanceof String[])
System.out.println("the type for a is String[]");
}
}
运行结果:
the type for a is int[]
the type for a is int[][]
the type for a is String[]
5.5 数组的初始化方式有哪几种?
Java中数据被创建后会根据数组存放的数据类型初始化对应的初始值(int类型会初始化0,对象会初始化null)。
一维数组:
int[] a = new int[5];
int[] b = {1,2,3,4,5};
int[] c;
c = new int[5];
二维数组:
int[][] a;
int b[][];
int[] c[];
原生类指未被实例化的类,数组一般指实例化、被分配空间的类。
5.6 length属性与length()方法有什么区别?
length()方法是针对字符串而言的,String提供了length()方法来计算字符串的长度。
除了length属性和length()方法外,Java中还提供了一个计算对象大小的方法–size()方法,该方法是针对泛型集合而言的。