文章目录
参考链接: 探秘Java中的String StringBuild StringBuffer
1.String
- String:是不可变对象,对象的任何改变都不影响到原对象,即对象一旦生成,就不能被更改。对String对象的改变会引发新的String对象的生成。
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str); //hello word!!!
分析代码:生成了一个新的String对象。然后让引用指向新的String对象。所以内容经常改变的字符串不要使用String类型,由于这样会造成内存中大量的无引用对象,然后JVM的GC就会开始工作。
1.1为什么要把String设计成不可变的?
String str1 = "hehe"; String str2 = "hehe";
//假设字符串是可变对象,通过 str1 修改了 ”hehe“ 之后,会影响到 str2
- 为了避免相互影响,把 String 设计成不可变的。
- 不可变对象是线程安全的。
- 不可变对象的 hash code 也是不变的, 作为 key 时可以更高效的保存到 HashMap 中。
不可变很大程度是为了实现“常量池”
1.2为什么说String是不可变的?
String类被 final
修饰,是不可以被继承,方法不可以被重写的;底层是由final
修饰的 char[]
组成的,说明该数组的引用地址不是可以发生改变的。并且并没有提供该数组的 get
, set
操作,char 数组内的数据因此也不可以被改变。所以说 String 是不可变的。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
......
}
注意:通过反射可以改变。
补充:通过反射修改字符串
反射是通过
- 反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”。
- 指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 “认清自己”。
/*
String.class
类比喻成一张图纸,对象则就是盖出来的房子,房子消耗空间,图纸本身也对应着一个“类对象”。
每个类都有一个自己对应的类对象。
*/
String str = "hello";
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
// 源码中 private final char value[]
Field valueField = String.class.getDeclaredField("value");
// 将这个字段的访问属性设为 true
valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) valueField.get(str);
// 修改 value 的值
value[0] = 'h';
System.out.println(str);
//执行结果
hello
1.3String类中两种对象实例化的区别
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern()方法手工入池。
1.4 解释字符串 += 操作
每次执行 += 操作,都会构造一个新的长字符串,比较低效。在代码中尽量少 +=操作。
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
// 执行结果
hello world!!!
// 不是 String 对象本身发生改变, 而是 str 引用到了其他的对象
1.5字符串常量池
String类的设计使用了共享设计模式。
理解“池”(pool),它是计算机中的一个重要术语,目的是为了提高程序效率。
在JVM底层实际上会自动维护一个对象池(字符串常量池):
- 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中。
- 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
- 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。
public static void main(String[] args) {
String str1 = "hehe";
String str2 = "hehe";
String str3 = new String("hehe");
// == 判断地址是否一致
// equals 判断内容是否相同
// 对于内容相同的字符串常量,在内存中没有必要存两份,只需要存一份即可
// JVM 中是通过“字符串常量池”方式来实现的
System.out.println(str1 == str2);// true
System.out.println(str1.equals(str2));// true
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str3)); // true
}
2.StringBuffer
- StrinhBuffer:每次都对对象本身进行操作,而不是生成新的对象。所以在字符串内容不断改变的情况,建议使用StringBuffer。
- StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的类。
- String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这些时候String对象的速度并不会比StringBuffer慢。
例如:如下代码,String的效率远比StringBuffer快。
String string = “I am a” + “ little” + “ girl.”;
StringBuffer stringBuffer = new StringBuilder(“I am a”).append(“ little”).append(“ girl.”);
解释:在JVM眼里:String srting = “I am a” + “ little” + “ girl.”;
就是String stringBuffer = “I am a little girl.”;
。
3.StringBuild
- StringBuild 是 JDK1.5 新增加的一个类,与 StringBuffer 具有相同的操作(方法和功能完全是等价)。
- StringBuild 是线程不安全的 类。
- 在单线程程序下,StringBuild 效率更快,因为它不需要加锁;而 StringBuffer 则每次都需要判断锁,效率相对更低。
三者适用场景
String:适用于少量的字符串操作的情况,即创建复制后,修改的比较少的情况。
StringBuffer:适用于多线程下字符串缓冲区进行大量操作的情况。
StringBuilder:适用于单线程下字符串缓冲区进行大量操作的情况。