String对象的实现
在Java8里,String类里面只有两个属性 char[] 数组 和 hash ,用来存放字符串和hash值,通之前的版本比较,减少了offset 和 count 两个变量,String 对象占用的内存稍微少了些,同时,String.substring 方法也不再共享 char[],从而解决了使用该方法可能导致的内存泄漏问题
String对象的不可变性
不可变性
通过源码可以看出String类使用了 final 关键字进行修饰,表示该类不能被继承重写方法。同时,char[] 数据也被 private 和 final 关键词修饰了,表示这个属性不能被修改。Java 实现的这个特性叫作 String 对象的不可变性,即 String 对象一旦创建成功,就不能再对它进行改变
这样做的好处有哪些呢?
- 安全性 确保String对象不会被恶意修改
- 唯一性 保证对应的hash值不会频繁变更
- 可实现字符串常量池
创建对象的方式
String创建对象的方式有两种:
1.通过字符串常量量的形式创建:String s1 = “abc”
JVM会先检查该对象是否在常量池中存在,如果在,返回该对象的引用,否则在常量池中创建新的字符串。这样做的好处是可以节省内存空间
2.通过new对象生成:String s2 = new String(“abc”)
首先在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,“abc"将会在常量池中创建;其次,在调用 new 时,JVM 命令将会调用 String 的构造函数,同时引用常量池中的"abc” 字符串,在堆内存中创建一个 String 对象;最后,s2 将引用 String 对象
s2 -> 堆内存中的String对象 -> 常量池"abc"常量字符串
String对象的优化
字符串的 "+"拼接
在日常开发中,经常会使用到字符串拼接,由于String字符串的不可变性,那在拼接过程中是否会产生多个字符串呢?
str= "ab" + "cd" + "ef";
按照拼接逻辑,会产生"ab",“abcd”,"abcdef"三个对象,是非常占用空间和低性能的,针对这个问题JDK给出的优化是在编译过程中将这三个字符串合并成一个,反编译代码如下:
String str= "abcdef";
这样,就只用产生一个"abcdef"字符串了
String.intern是如何节省内存的
intern()是String的一个native方法
public native String intern();
在字符串常量中,默认会将对象放入常量池,在字符串变量中,对象是会创建在堆内存中,同时也会在常量池中创建一个字符串对象,String 对象中的 char 数组将会引用常量池中的 char 数组,并返回堆内存对象引用
当String对象调用intern()时,回去查看字符串常量池中是否有等于该对象的字符串的引用,如果没有,把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用,这样相当于一个字符串常量只有一个堆对象引用它,大大地节省了堆内存空间和创建对象的成本
举个例子:
String s1 = new String("abc").intern();
String s2 = new String("abc").intern();
System.out.println(s1 == s2);
输出结果为true,s1和s2的值都是"abc"常量字符串的引用对象,堆内存里值生成了一个对象引用"abc"
但如果s2不执行intern()
String s1 = new String("abc").intern();
String s2 = new String("abc");
System.out.println(s1 == s2);
输出结果为false,这里的s1的值是"abc"常量字符串的引用对象,但是s2的值是堆内存单独生成的一个堆对象,引用了常量池中的"abc"常量字符串
对intern()方法的详细解读和测试请阅读文章:深入解析String#intern
写在最后 欢迎关注微信公众号【小肖爱吃肉】和你一起记录生活的小美好