String、StringBuilder、StringBuffer
String
String是只读字符串,String引用的字符串内容是不可更改的。当我们通过不同的方式创建一个String对象,这个对象就唯一确定了。
String str1 = “hello”;
String str2 = “hello”;
String str3 = new String(“hello”);
String str4 = new String(str1);
这里str1、str2是同一个对象,所以str1==str2的结果为true;
str1、str3、str4是不同的对象,但是他们指向String Pool中同一个字符串(一时之间也没有找到,通过怎样的标识来确定str1、str3、str4指向的是String Pool中的同一字符串,以后知道了,再来补充。ps:我看到网上有人说,通过hashCode()方法确定它们只想的是同一个对象,看了下String.hashCode()的源码,对这种说法持怀疑态度。)
str1在String Pool中,str2、str4在Java Heap中。
注:
1. Java运行时会维护一个String Pool, JavaDoc翻译为“字符串缓冲区”, String Pool用来存放运行中产生的各种字符串,并且Pool中的字符串内容不重复。创建的String对象放在Java堆中,对象指向String Pool中的某个字符串。
2. Java对象的创建:
- String s = X, Java运行是会拿这个X在String Pool中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串s,否则,不在池中添加。
- String s = new String(X), 对这种方式,Java一定会(在堆区)创建一个String对象。
String.hashCode()源码如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
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;
}
}
从上面源码可以看出,hashCode只是根据该String对象的值,对其每一位进行运算,最后得出的一个int值而已,并没有表示出它在内存中的唯一标识。
因为str1、str2、str3、str4它们的值(value)都是“hello”,因此它们的hashCode相同。
str1、str3、str4它们的值相同,所以用equals()方法比较时,都为true;
String str5 = new String(“hello”).intern();
简单的说,intern方法会先从String Pool中查找是否存在“hello”的字符串常量,如果有,则返回常量池中的这个字符串;如果没有,则把其添加到String Pool中,并返回此String对象的引用。ps:可能说的不是很清晰,直接贴上jdk里的注释(注:intern()方法是native method)
因此str1==str5为true,因为它们是同一个对象。
(好吧,水平、文笔有限,也不知道描述清楚了没有,若有误,欢迎指正)
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
测试代码:(还有很多case没有写进去)
public static void testString() {
String str1 = "hello";
System.out.println("str1 hashCode: " + str1.hashCode());
String str2 = "hello";
System.out.println("str2 hashCode: " + str2.hashCode());
String str3 = new String("hello");
System.out.println("str3 hashCode: " + str3.hashCode());
String str4 = new String(str1);
System.out.println("str4 hashCode: " + str4.hashCode());
String str5 = new String("hello").intern();
System.out.println("str1 == str2 : " + (str1 == str2));
System.out.println("str1 == str3 : " + (str1 == str3));
System.out.println("str1 == str4 : " + (str1 == str4));
System.out.println("str1 == str5 : " + (str1 == str5));
System.out.println("str1.equals(str2) : " + str1.equals(str2));
System.out.println("str1.equals(str3) : " + str1.equals(str3));
System.out.println("str1.equals(str4) : " + str1.equals(str4));
System.out.println("str1.equals(str5) : " + str1.equals(str5));
System.out.println("\nstr1 = \"world\".....");
str1 = "world";
System.out.println("str1 hashCode: " + str1.hashCode());
String str6 = "world";
System.out.println("str1 == str5 : " + (str1 == str6));
System.out.println("str1.equals(str5) : " + str1.equals(str6));
System.out.println("str1 = " + str1);
System.out.println("str6 = " + str6);
System.out.println("str2 = " + str2);
System.out.println("str3 = " + str3);
System.out.println("str4 = " + str4);
System.out.println("str5 = " + str5);
}
运行结果:
str1 hashCode: 99162322
str2 hashCode: 99162322
str3 hashCode: 99162322
str4 hashCode: 99162322
str1 == str2 : true
str1 == str3 : false
str1 == str4 : false
str1 == str5 : true
str1.equals(str2) : true
str1.equals(str3) : true
str1.equals(str4) : true
str1.equals(str5) : true
str1 = "world".....
str1 hashCode: 113318802
str1 == str6 : true
str1.equals(str5) : true
str1 = world
str6 = world
str2 = hello
str3 = hello
str4 = hello
str5 = hello
intern方法是一个native方法,
附 String类的equal()方法源码:(顺便带几个方法的源码吧)
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
......
......
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
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;
}
......
......
/**
* Returns the length of this string.
* The length is equal to the number of <a href="Character.html#unicode">Unicode
* code units</a> in the string.
*
* @return the length of the sequence of characters represented by this
* object.
*/
public int length() {
return value.length;
}
/**
* Returns {@code true} if, and only if, {@link #length()} is {@code 0}.
*
* @return {@code true} if {@link #length()} is {@code 0}, otherwise
* {@code false}
*
* @since 1.6
*/
public boolean isEmpty() {
return value.length == 0;
}
/**
* Returns the {@code char} value at the
* specified index. An index ranges from {@code 0} to
* {@code length() - 1}. The first {@code char} value of the sequence
* is at index {@code 0}, the next at index {@code 1},
* and so on, as for array indexing.
*
* <p>If the {@code char} value specified by the index is a
* <a href="Character.html#unicode">surrogate</a>, the surrogate
* value is returned.
*
* @param index the index of the {@code char} value.
* @return the {@code char} value at the specified index of this string.
* The first {@code char} value is at index {@code 0}.
* @exception IndexOutOfBoundsException if the {@code index}
* argument is negative or not less than the length of this
* string.
*/
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
从源码可以看出,其实String字符串本质上是一个char[]数组,将char[] value 封装成了一个对象,并给它一些操作method,方便我们直接使用。
补充一个纠结了半个多小时的问题
先上一段“诡异”的代码
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
String f = "hello" + 2;
System.out.println((a == c));
System.out.println((a == e));
System.out.println((a == f));
}
}
这段“诡异”的代码的运行结果:
true
false
true
想必很多人都不能够理解吧(如果你知道原因,那么可以不必往下看了,下面的你肯定都知道。)
首先,fianl关键字修饰变量(修饰类和方法就不说了)。当final作用于类的成员变量(局部变量只要保证在使用之前被初始化赋值即可)时,必须在定义时或者构造器中进行初始化赋值,而final变量一旦被初始化赋值之后,就不能再次被赋值了。OK,这是解决final修饰类成员变量的。
对于上面 a==c 和 a==e,它们的区别就是 c=b+2, b是final变量,e=d+2, d是普通变量。当final变量是基本数据类型以及String类型是,如果编译期间就能知道它的确切值,则编译期会把它当做编译期常量使用。也就是说在用到该final的地方,相当于直接访问常量,就如同:String f = “hello” + 2 一样,不需要在运行时确定。而对于e=d+2,对变量d的访问需要在运行时通过链接来完成、进行。(注:只有在编译期能确切知道final变量值得情况下,编译器才做进行这样的优化)。
这是其中的一个点。
其次,对于String c = d + 2, 这段代码 <=====> String c = “hello” + 2; 这句话在编译期就能够确切的得出 c的值。(注:使用只包含常量的字符串连接符,如:String str = “aa” + “bb”, 创建的也是常量,编译期就能够确定,并且放在String Pool中)。
总结:就是上面那段代码,在编译结束后,String Pool中会创建三个字符串常量,分别是: “hello”, “2”, “hello2”,能够确定 a, b, c, d, f的值。 e只有在运行时,才会知道,String e = d + 2, 在运行时,会被编译器,优化为:String e = new StringBuilder(d).append(2).toString(); StringBuilder的toString()方法里面做的 是 return new String(), 所以该句最终是通过new String()生成,通过前面的分析,得知,通过new一定会在堆中生成一个对象。
附:
String s1 = new String("111");
String s2 = "sss111";
String s3 = "sss" + "111";
String s4 = "sss" + s1;
System.out.println(s2 == s3); //true
System.out.println(s2 == s4); //false
System.out.println(s2 == s4.intern()); //true
总结:
- 单独使用”“引号创建的字符串都是常量,编译期就已经确定存储到String Pool中;
- 使用new String(“”)创建的对象会存储到Java堆中,是运行期新创建的;
- 使用包含常量的字符串连接符,如“aa” + “bb”创建的也是常量,编译期就能确定,已经确定存储到String Pool中;
- 使用包含变量的字符串连接符,如:“aa” + s1 创建的对象是运行期才创建的, 存储在Java堆中
参考:
StringBuilder & StringBuffer
StringBuilder 和 StringBuffer都是可变长度的字符串,字符串的值可以直接修改,并且还是原来的对象。
StringBuilder是Java 5引入的(StringBuffer从jdk1.0里就存在了),和StringBuffer的方法完全相同,区别仅仅是:StringBuilder是线程不安全的,因为StringBuilder的所有操作方法都没有被synchronized修饰,这样就: 非线程安全,但是StringBuiler的效率比StringBuffer要高。
下面就简单的比较一下两者;
StringBuffer
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
......
}
StringBuilder:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
......
}
StringBuilder、StringBuffer都实现了接口:java.io.Serializable, CharSequence, 同样的继承于 AbstractStringBuilder,
并且它们的构造方法基本类似。它们都是final类(String也是final类),所以它们是不可以被继承的。
它们的不同就是在一些操作method上(比如append()方法,StringBuffer有synchronize修饰。)
StringBuffer:
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
StringBuilder没有重写这些方法,而是直接用的父类的,父类的这些方法是没有synchronize修饰的。
StringBuffer:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
/**
* Appends the specified {@code StringBuffer} to this sequence.
* <p>
* The characters of the {@code StringBuffer} argument are appended,
* in order, to the contents of this {@code StringBuffer}, increasing the
* length of this {@code StringBuffer} by the length of the argument.
* If {@code sb} is {@code null}, then the four characters
* {@code "null"} are appended to this {@code StringBuffer}.
* <p>
* Let <i>n</i> be the length of the old character sequence, the one
* contained in the {@code StringBuffer} just prior to execution of the
* {@code append} method. Then the character at index <i>k</i> in
* the new character sequence is equal to the character at index <i>k</i>
* in the old character sequence, if <i>k</i> is less than <i>n</i>;
* otherwise, it is equal to the character at index <i>k-n</i> in the
* argument {@code sb}.
* <p>
* This method synchronizes on {@code this}, the destination
* object, but does not synchronize on the source ({@code sb}).
*
* @param sb the {@code StringBuffer} to append.
* @return a reference to this object.
* @since 1.4
*/
public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}
/**
* @since 1.8
*/
@Override
synchronized StringBuffer append(AbstractStringBuilder asb) {
toStringCache = null;
super.append(asb);
return this;
}
/**
* Appends the specified {@code CharSequence} to this
* sequence.
* <p>
* The characters of the {@code CharSequence} argument are appended,
* in order, increasing the length of this sequence by the length of the
* argument.
*
* <p>The result of this method is exactly the same as if it were an
* invocation of this.append(s, 0, s.length());
*
* <p>This method synchronizes on {@code this}, the destination
* object, but does not synchronize on the source ({@code s}).
*
* <p>If {@code s} is {@code null}, then the four characters
* {@code "null"} are appended.
*
* @param s the {@code CharSequence} to append.
* @return a reference to this object.
* @since 1.5
*/
@Override
public synchronized StringBuffer append(CharSequence s) {
toStringCache = null;
super.append(s);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized StringBuffer append(CharSequence s, int start, int end)
{
toStringCache = null;
super.append(s, start, end);
return this;
}
@Override
public synchronized StringBuffer append(char[] str) {
toStringCache = null;
super.append(str);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized StringBuffer append(char[] str, int offset, int len) {
toStringCache = null;
super.append(str, offset, len);
return this;
}
@Override
public synchronized StringBuffer append(boolean b) {
toStringCache = null;
super.append(b);
return this;
}
@Override
public synchronized StringBuffer append(char c) {
toStringCache = null;
super.append(c);
return this;
}
@Override
public synchronized StringBuffer append(int i) {
toStringCache = null;
super.append(i);
return this;
}
.......
StringBuilder:
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
/**
* Appends the specified {@code StringBuffer} to this sequence.
* <p>
* The characters of the {@code StringBuffer} argument are appended,
* in order, to this sequence, increasing the
* length of this sequence by the length of the argument.
* If {@code sb} is {@code null}, then the four characters
* {@code "null"} are appended to this sequence.
* <p>
* Let <i>n</i> be the length of this character sequence just prior to
* execution of the {@code append} method. Then the character at index
* <i>k</i> in the new character sequence is equal to the character at
* index <i>k</i> in the old character sequence, if <i>k</i> is less than
* <i>n</i>; otherwise, it is equal to the character at index <i>k-n</i>
* in the argument {@code sb}.
*
* @param sb the {@code StringBuffer} to append.
* @return a reference to this object.
*/
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
@Override
public StringBuilder append(char[] str) {
super.append(str);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder append(char[] str, int offset, int len) {
super.append(str, offset, len);
return this;
}
@Override
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
@Override
public StringBuilder append(int i) {
super.append(i);
return this;
}
@Override
public StringBuilder append(long lng) {
super.append(lng);
return this;
}
@Override
public StringBuilder append(float f) {
super.append(f);
return this;
}
@Override
public StringBuilder append(double d) {
super.append(d);
return this;
}
.......
总而言之,StringBuilder和StringBuffer的区别就是,StringBuilder是线程不安全的,StringBuffer是线程安全的。而,具体的实现就是,StringBuffer所有的method都没有synchronize修饰。虽然这样做,线程不安全,但是它的效率比StringBuilder高。在单线程的场景下,果断使用StringBuilder。
注:让我想起了 HashMap 和 HashTable ,这两者也是一个线程安全,一个线程不安全。(待会儿去看看源码。看看这两者是如何实现的。)