鹰击长空,鱼翔浅底,万类霜天竞自由

Java中关于String类型的几个问题


如何比较两个字符串?用"="还是equals()?

简单来说,“==”是用来检测俩引用是不是指向内存中的同一个对象,而equals()方法则检测的是两个对象的值是否相等。只要你想检测俩字符串是不是相等的,你就必须得用equals()方法。
如果你知道“字符串保留(string intern)”的概念那就更好了:

Stringintern()方法返回字符串对象的规范化表示形式。它遵循以下规则:对于任意两个字符串st,当且仅当s.equals(t)true时,s.intern()==t.intern()才为true。为什么呢?

一个初始时为空的字符串池,它由类String私有地维护。当调用intern()方法时,如果池已经包含一个等于此String对象的字符串(该对象由equals()方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此String对象的引用。

所有字面值字符串和字符串赋值常量表达式都是内部的。

String str1 = "abc";
String str2 = new String("abc"); //true
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2)); //true
System.out.println(str1 == str2.intern()); //true

substring()方法具体是都干了些啥?

在JDK6中,这个方法只会在标识现有字符串的字符数组上 给一个窗口来表示结果字符串,但是不会创建一个新的字符串对象。如果需要创建个新字符串对象,可以这样在结果后面"+"一个空的字符串:

str.substring(m, n) + "";

这么写的话就会创建一个新的字符数组来表示结果字符串。同时,这么写也有一定的几率让你的代码跑的更快,因为垃圾回收器会吧没有在使用的大字符串回收而留下子字符串。
Oracle JDK7中的substring()方法会创建一个新的字符数组,而不用之前存在的。下面举出详细的列子说明substring()在JDK6和JDK7中的区别。

String str = "abcdef";
str = str.substring(1,3);
System.out.println(str);

substring()被调用时,发生了什么?

你肯定知道,字符串strfinal不可变的,所以当str被重新赋值为str.substring(1,3)时,它应该指向一个全新的字符串,就像下面图片显示的那样:

5E39556283BA416E96AE693AF93CDFDCT1503469714945W650H303.jpg

但是这张图并不完全正确,它并没有说明内存堆中真正发生了什么。那么substring()方法的到底是怎样工作的呢?在JDK6和JDK7中又有什么区别呢?

JDK6中的substring()

实际上String类的原理是将字符串存储在一个字符数组中的,在JDK6中,类String包括3个属性域:char value[], int offset, int count,分别用来存储实际的字符数组、数组的第一个索引、字符的个数。

substring()方法被调用时,一个新的字符串被创建,但是这个字符串的字符数组value仍然指向了原来那个数组的内存堆!惟一不同的是两个字符串的offsetcount属性值不同,如下图:

C18E5B785AD4467E83421F818DF29F91T1503469714991W650H389.jpg

下面是简化后的String类源码:

//JDK 6  
String(int offset, int count, char value[]) {  
    this.value = value;  
    this.offset = offset;  
    this.count = count;  
}  
   
public String substring(int beginIndex, int endIndex) {  
    //check boundary  
    return  new String(offset + beginIndex, endIndex - beginIndex, value);  
}

可以很明显的看出,String对象中的value域并没有变化,还是指向之前的那个value

JDK6中的substring()方法引出的一个问题

如果有一个很长很长的字符串,你每次只需要截取其中很短的一小段,但是如果你用substring()的话实际会得到很长很长的字符串的引用,所以当使用的时候必然导致性能降低! 

JDK7中的substring()

这个问题在JDK7中被改进了,创建了一个新的字符数组:

6DDD7F045F9C419EA591624E084741BDT1503469715027W650H389.jpg

String类在JDK7中的源码:

//JDK 7  
public String(char value[], int offset, int count) {  
    //check boundary  
    this.value = Arrays.copyOfRange(value, offset, offset + count);  
}  
   
public String substring(int beginIndex, int endIndex) {  
    //check boundary  
    int subLen = endIndex - beginIndex;  
    return new String(value, beginIndex, subLen);  
}

可以看到,在构造函数中,创建了一个全新的字符数组value,并且String类中不再使用offsetcount做为属性了。

String & StringBuilder & StringBuffer的区别

String vs StringBuilderStringBuilder是可变的,这就意味你在创建对象之后还可以去修改它的值。StringBuilder vs StringBufferStringBuffer是同步的,意味着它是线程安全的,但是就会比StringBuilder慢些。


版权声明

本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。 本站博文除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。