基本特性
存储结构变更
- jdk8及之前的jdk版本中,String的内存存储结构是char[]字符数组,但是在Jdk9及之后改成了byte[]字节数组。
- 原因是,堆空间中大部分的字符串内容都是latin字符,基本上一个byte就可以表示,但是char占两个字节,导致一半的内存浪费得不到合理使用。
- 对于需要两个字节表示的字符串,String中添加了一个字符编码集标识,如果是lation/ISO-8859-1就使用一个byte去存在,gbk等字符集使用两个字节去存储。
- 对于和String相关的类也做了修改,使用byte[]去保存数据,比如StringBuffer,StringBuilder等等。
- 结论:String再也不要char[]来存储啦,改成了使用byte[]加上编码标记的方式,节约了一下空间。
不可变性
- 当对字符串重新赋值时,需要重写指定内存区域的赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
- 字符串常量池在Jdk7之后存放在堆空间,字符串常量池不能存储在相同的字符串。如果不同的变量指向相等的字符串字面量,那么它们指向相同的字符串。
- 每个类都有一个运行时常量池,和每个类的字节码文件一致,只不过一个是静态的一个加载到内存中的,供运行时调用。
字符串常量池中是不会存储相同内容的字符串的
因为字符串常量池底层的存储结构是HashTable,键肯定不能重复,键就是字符串内容。
- String的String pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表常量以后直接会造成的影响就是当调用String.intern是性能大幅下降。
- 使用-XX:StringTableSize可设置StringTable的长度。
- 在Jdk6中StringTable是固定的,就是1009的长度,所有如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求。
jdk6中StringTable的长度
- 在jdk7中,StringTable的长度默认值是60013,jdk8开始1009是StringTable可设置的最小值。
String的内存分配
- 在java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
- 常量池就类似于一个java系统级别提供的缓存。8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。
- 直接使用双引号声明出来的String对象会直接存储在常量池中。
比如:String info = “hello”; - 如果不是使用双引号声明的String对象,可以使用String提供的intern()方法。
- Java6及以前,字符串常量池存放在永久代。
- java7中oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
- 所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用是仅需要调整堆大小就可以了。
- 字符串常量池概念原本使用的比较多,但是这个改动使我们有足够的理由让我们重新考虑在java7中使用String.intern()。
- Java8元空间,字符串常量也在堆中。
- StringTable为什么要调整
- permsize(永久代)通常默认比较小,如果大量的存放大量的字符串,会很容易导致OOM。
- 永久代垃圾回收频率低,不能及时清除不用的字符串,大量字符串会很容易导致Full GC,降低程序性能,同时又容易导致OOM。
字符串拼接操作
- 常量与常量的拼接结果在常量池,原理是编译期优化(编译成字节码后就已经直接拼接好结果,放在常量池,运行时从常量池中获取)。
字节码文件反编译的结果如下,说明编译期就已经优化成同一个字面量。
- 常量池中不会存在相同内容的常量,因为底层类似hashMap结构,以字符串做key值,导致无法重复。
- 只要其中有一个变量(非final的变量),结果就在堆中(堆中非常量池区域)。变量拼接的原理是StringBuilder(拼接参数中如果出现一个变量,就会生成StringBuilder对象,通过append的方式拼接字符串)。
但如果是常量就是不是创建StringBuilder追加的方式
- 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址。
字符串变量拼接和StringBuilder方式比较
==================================================耗时4014ms========================
String str = "";
for(int i = 0; i < 10000; i++) {
str += i;
}
==================================================耗时7ms===========================
StringBuilder str = new StringBuilder();
for(int i = 0; i < 10000; i++) {
str.append(i);
}
- 效率
通过StringBuilder的append()的方式添加字符串的效率要远远高于String字符串拼接的方式。 - 详情
- StringBuilder的append()的方式:至始至终只会创建一个StringBuilder的对象;使用String的字符串拼接方式(因为包含变量每次拼接都会创建StringBuilder,String对象):创建多个StringBuilder和String对象。
- 使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String对象,内存占用更大;如果进行GC,需要额外的时间。
- StringBuilder追加字符串优化空间
在实际开发中,如果基本确定前前后后的字符串长度不超过某个限定值highLevel的情况下,建议使用代参构造器创建对象:
StringBuilder s = new StringBuilder(highLevel);
备注:相当于底层创建一个限定长度的数组 new char[highLevel],不会因为长度不够不同的拷贝复制生成新数组扩展,导致性能降低。
intern的使用
面试题
- String str = new String(“ab”);生成几个对象
答:两个,分别是堆中的new String()和字符串常量池中的“ab”,可以通过查看字节码佐证(需要从常量池中载入”ab”字符串,并初始化new String()对象)。
- String str = new String(“a”) + new String(“b”);生成几个对象?
答:6个对象,具体对象如下:
因为涉及字符串的拼接,需要生成StringBuilder()对象。
每个new String(“xxx”)需要两个对象,一个堆中,一个字符串常量池中。
StringBuilder.toString()会new String()对象,但是通过查看字节码发现toString()不会在从字符串常量池中获取字符串值,说明不会在字符串常量池中生成对象。
- 面试题之intern
- 代码流程图
intern总结
- jdk6中,将这个字符串对象尝试放入字符串常量池中。
- 如果池中有,则并不会放入。返回已有的池中的对象的地址。
- 如果没有,会把调用Intern的String对象复制一份,放入常量池中,并返回池中的对象地址。
- jdk7起,将这个字符串对象尝试放入字符串常量池中。
- 如果池中有,则并不会放入。返回已有的池中的对象的地址。
- 如果没有,则会把调用intern的String对象的引用地址复制一份,放入池中(池中存放的是堆中String对象的地址),并返回池中的引用地址(堆中String对象的地址)。
练习
使用intern的优点
- 如果存在大量相同的字符串,使用intern会节约内存空间。
- String s = new String(“xxx”); // 堆空间和常量池都会创建一个对象,但返回的是堆空间的对象地址,如果有大量相同的字符串,那么内存中会维护大量的堆空间中对象的地址,造成空间浪费。
- String s = new String(“xxx”).intern(); // 堆空间和常量池都会创建一个对象,但是返回的是常量池中对象的地址,那么堆空间的字符串对象地址就不需要维护,会被MinorGC即时清除,又因为常量池中不会有相同的字符串,那么空间就会得到很大程度的节省。
- 如果存在大量相同的字符串,使用intern会提高程序执行速度,提高效率。
StringTable垃圾回收
- 测试代码
执行代码,可以看到有GC回收出现,同时StringTable中的字符串条目小于100000,证明StringTable中存在垃圾回收行为。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100423.html