第十三章 StringTable
作者:互联网
翻篇是很重要的能力,从悲伤中大方走出来,就是艺术家
1.String的基本特性
-
String字符串定义的两种方式
String s1 = “baidu”; //字面量的定义方式
String s2 = new String("hello");
-
String声明为final的,不可被继承
-
String实现了
Serializable
接口,表示字符串是支持序列化的 -
String实现了Comparable接口,表示string可以比较大小
-
String在
JDK8及以前
内部定义了 final char[] value用于存储字符串数据,jdk9
时改为byte[]
String在jdk9
中存储结构变更
动机
该类的当前实现String将字符存储在一个 char数组中,每个字符使用两个字节(十六位)。从许多不同应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数String对象仅包含 Latin-1 字符。此类字符仅需要一个字节的存储空间,因此此类对象的内部char数组中的 一半空间未使用。String
描述
我们建议将String类的内部表示从 UTF-16char数组更改为byte数组加上编码标志字段。新String类将根据字符串的内容存储编码为 ISO-8859-1/Latin-1(每个字符一个字节)或 UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。
与字符串相关的类(例如AbstractStringBuilder、StringBuilder和 )StringBuffer将被更新为使用相同的表示形式,HotSpot VM 的内在字符串操作也是如此。
这纯粹是一个实现更改,对现有的公共接口没有任何更改。没有计划添加任何新的公共 API 或其他接口。
迄今为止完成的原型设计工作证实了内存占用的预期减少、GC 活动的大幅减少以及在某些极端情况下的轻微性能回归。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
}
结论:String再也不用char[ ] 来存储了,改成了byte[ ] 加上编码标记,节约了一些空间。
String的基本特性
-
String:代表不可变的字符序列。简称:不可变性
-
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
-
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
-
当调用string的replace( )方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
-
-
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
-
字符串常量池是不会存储相同内容的字符串的
-
String的String Pool是一个固定大小的
Hashtable
,默认值大小长度是1009。如果放进String Pool的 -
String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用
String.intern
时性能会大幅下降 -
使用
-XX:StringTablesize
可设置StringTable
的长度 -
在
JDK6
中StringTable
是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize
设置没有要求 -
在
JDK7
中,StringTable
的长度默认值是60013,StringTablesize
设置没有要求 -
在
JDK8
开始,设置StringTable
长度的话,1009是可以设置的最小值
-
2.String的内存分配
-
在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
-
常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。
-
直接使用双引号声明出来的String对象会直接存储在常量池中。
-
比如:
String info = “baidu.com”
;
-
-
如果不是用双引号声明的String对象,可以使用String提供的intern( )方法。
-
-
Java 6及以前,字符串常量池存放在永久代
-
Java 7中 Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内
-
所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
-
字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7中使用
String.intern()
-
-
Java8元空间,字符串常量在堆空间中
StringTable为什么要调整?
-
permSize
默认比较小 -
永久代垃圾回收频率低
在JDK 7
中,内部字符串不再分配在Java堆的永久代中,而是分配在Java堆的主要部分(称为年轻代和老年代),与应用程序创建的其他对象一起。这种变化将导致更多的数据驻留在主Java堆中,而更少的数据在永久代中,因此可能需要调整堆的大小。大多数应用程序将看到由于这一变化而导致的堆使用的相对较小的差异,但加载许多类或大量使用String.intern( )
方法的大型应用程序将看到更明显的差异。
代码示例
/**
* jdk6中:
* -XX:PermSize=6m -XX:MaxPermSize=6m -Xms6m -Xmx6m
*
* jdk8中:
* -XX:MetaspaceSize=6m -XX:MaxMetaspaceSize=6m -Xms6m -Xmx6m
*/
public class StringTest3 {
public static void main(String[] args) {
//使用Set保持着常量池引用,避免full gc回收常量池行为
Set<String> set = new HashSet<String>();
//在short可以取值的范围内足以让6MB的PermSize或heap产生OOM了。
short i = 0;
while(true){
set.add(String.valueOf(i++).intern());
}
}
}
-
可以看到
OOM
是发生在堆空间中,所以字符串常量池在JDK8
中确实是存在堆空间中的
3.intern( )的使用
当调用intern方法时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。
由此可见,对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern( ) == t.intern( )为真。
所有字面字符串和以字符串为值的常量表达式都是interned。
返回一个与此字符串内容相同的字符串,但保证是来自一个唯一的字符串池。
-
intern()
是一个 native 方法,调用的是底层 C 的方法。
public native String intern();
-
如果不是用双引号声明的String对象,可以使用String提供的intern方法,它会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
String myInfo = new string("I love alibaba").intern();
-
也就是说,如果在任意字符串上调用
String.intern
方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true
("a"+"b"+"c").intern() == "abc"
-
通俗点讲,Interned string就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)
/**
* 如何保证变量s指向的是字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s = "shkstart";//字面量定义的方式
* 方式二: 调用intern()
* String s = new String("shkstart").intern();
* String s = new StringBuilder("shkstart").toString().intern();
*/
new String(“ab”)会创建几个对象
/**
* new String("ab") 会创建几个对象?
* 看字节码就知道是2个对象
*/
public class StringNewTest {
public static void main(String[] args) {
String str = new String("ab");
}
}
-
这里面就是两个对象
-
一个对象是:new 关键字在堆空间中创建
-
另一个对象:字符串常量池中的对象“ab”
-
new String(“a”) + new String(“b”) 会创建几个对象
/**
* new String("a") + new String("b") 会创建几个对象?
*/
public class StringNewTest {
public static void main(String[] args) {
String str = new String("a") + new String("b");
}
}
0 new #2 <java/lang/StringBuilder> //new StringBuilder()
3 dup
4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V>
7 new #4 <java/lang/String> //new String()
10 dup
11 ldc #5 <a> //常量池中的 “a”
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V> //new String("a")
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> //append()
19 new #4 <java/lang/String> //new String()
22 dup
23 ldc #8 <b> //常量池中的 “b”
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V> //new String("b")
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> //append()
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;> //toString()里面会new一个String对象
34 astore_1
35 return
我们创建了 6 个对象
-
对象1:
new StringBuilder()
-
对象2:
new String("a")
-
对象3:常量池中的 “a”
-
对象4:
new String("b")
-
对象5:常量池中的 “b”
-
对象6:toString 中会创建一个
new String("ab")
-
toString( )的调用,在字符串常量池中,没有生成"ab"
-
-
StringBuilder中toString( )源码
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
标签:String,对象,StringTable,intern,第十三章,字符串,new,常量 来源: https://www.cnblogs.com/l12138h/p/16615196.html