字符串
字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
1.创建字符串常见的两种方式
① 直接赋值创建:会在字符串常量池中
String str1 = "hello";
② 通过构造方法创建:在堆内存中创建
String str2 = new String("hello");
两种方式的比较
String str1 = new String("hello");
String str2 = "hello";
String str3 = "hello";
System.out.println(str1 == str2); //false
System.out.println(str2 == str3); //true
System.out.println(str1.intern() == str3); //true
内存结构图:
注意点:直接赋值方式只会开辟一块内存空间,并且会存在常量池中,而构造方法创建的方式会开辟两块内存空间,其中一块会被垃圾回收,不能进入常量池中,可以通过intern()方法进行手工入池,
2.String的不可变性
String的源码分析:
String的不可变性不只是因为value[]不可变,它仅仅是value[]的引用地址不可变,内部的数据还是可以被改变的,final的修饰只是value数组不能被继承,以免被外部修改,但主要的是String类中的所有操作都没有改变过value数组。
//String被final修饰,说明String不可以被继承
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; //底层维护了一个private的不可变的char型数组
}
String类重写了Object类的equals()方法,首先比较两个对象的地址值,如果相同则直接返回true,否则将会比较两个对象的值是否相等
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;
}
不可变性的好处:
- 可以实现多个变量引用堆内存中的同一个字符串实例,避免内存的开销
- HashMap中经常用String作为key,计算hash值只用计算一次,而且确保了key的唯一性
- String天生线程安全,可以用在多线程场景
3.String,StringBuffer,StringBuilder
下面拿StringBuffer源码举例:
/** char[] value; 继承了父类AbstractStringBuilder的可变char[] 默认大小为16 **/
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
//调用父类AbstractStringBuilder的构造方法,
public StringBuffer() {
super(16);
}
//append方法重写了AbstractStringBuilder的append方法
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull(); //如果str==null则返回一个值null的AbstractStringBuilder对象
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
**扩容:**如果新的字符串长度大于value[]的长度则扩容
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
//扩容后长度为原来的两倍+2,如果扩容后的长度还小于新的字符串的长度,则将新字符串的长度赋值给扩容的长度
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
//如果扩容的长度超过了数组允许的最大长度MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),则进行如果新字符串长度大于int最大值则抛异常,否则判断新字符串的长度是否大于数组最大长度,大于则将维持新字符串长度,否则将容量扩为数组最大的长度
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
三者区别:
String的+运算符(拼接字符串的操作):
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;
}
反编译class文件后:
public static void main(String args[])
{
String s1 = "a";
String s2 = "b";
String s3 = (new StringBuilder(String.valueOf(s1))).append(s2).toString();
}
可以看到String在字符串拼接的时候底层用的是StringBuilder的append()方法后通过toString()返回新的字符串
总结:
- 在不常操作和改变字符串的情况下用String比较合适,因为大量的字符串操作会new出大量的StringBuider对象,造成系统的开销
- 因为StringBuffer和StringBuilder底层的实现几乎一样,只是StringBuffer用了synchronized修饰保证了线程安全,所以StringBuffer效率相对于StringBuilder较低
- 多线程场景下大量操作字符串下使用StringBuffer,单线程使用StringBuilder
参考链接:
https://www.cnblogs.com/zhangyinhua/p/7689974.html#_lab2_4_0
https://www.oschina.net/question/129471_37540