StringTable
作者:互联网
String的基本特性
- String:字符串,使用" "引起来表示
- String声明为final的,不能被继承
- String实现了Serializable接口,表示字符串是支持序列化的。实现了Comparable接口,表示String可以比较大小
- String在jdk8及以前内部定义了final char[ ] value用于存储字符串数据。jdk9时改成byte[ ]
- String代表不可变的字符序列。简称:不可变性
- 当对字符串重新赋值,需要重写指定内存区域赋值,不能使用原有的value进行赋值
- 当对现有的字符串进行拼接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
- 当调用String的replace()方法修改指定的字符串时,也需要重新指定内存区域,不能直接使用value进行赋值
- 通过字面量的方式(区别于new)给一个字符串赋值,此时字符串值声明在字符串常量池中
public class StringTest {
@Test
public void test1(){
String s1="abc";//字面量定义的方式,“abc”存储在字符串常量池中
String s2="abc";
System.out.println(s1==s2);//地址判断
s2="hello";//此时,是在字符串常量池中重新造一个“hello”,不是在原来的“abc”的基础好上修改
System.out.println(s1==s2);//地址判断
System.out.println(s1);
System.out.println(s2);
}
@Test
public void test2(){
String s1="abc";//字面量定义的方式,“abc”存储在字符串常量池中
String s2="abc";
s2+="def";//这里也不是在原来的“abc”的基础上修改,而是重新造一个
System.out.println(s1==s2);//地址判断
System.out.println(s1);
System.out.println(s2);
}
@Test
public void test3(){
String s1="abc";//字面量定义的方式,“abc”存储在字符串常量池中
String s2=s1.replace("a","x");//生成一个新的字符串常量“xbc”,而不是在“abc”的基础上修改
System.out.println(s1==s2);//地址判断
System.out.println(s1);
System.out.println(s2);
}
}
- 字符串常量池中是不会存储相同的内容的字符串的
- String的String Pool是一个固定大小的Hashtable,默认长度是1009。如果放进String Pool中的Stirng非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了之后直接会造成的影响就是当调用String.initern( )时性能会大幅下降
- 使用-XX:StringTableSize可设置StringTable的长度
- 在jdk6中StringTable是固定的,就是1009的长度,所以常量池中的字符串过多时就会导致效率下降很快。StringTable设置没有要求
- 在jdk7中,StirngTable的默认长度的值是60013
- jdk8开始,设置StringTable的长度的话,1009是可设置的最小值
如图,设置虚拟机启动参数(我使用的jdk8):-XX:StringTableSize=1008
得到系统错误提示:
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
StringTable size of 1008 is invalid; must be between 1009 and 2305843009213693951
String的内存分配
- 在Java语音中有8种基本数据类型和一种特殊的类型:String。这些类型为了使它们在运行时速度更快、更加节省内存,提供了一种常量池的概念
- 常量池就类似一个Java系统级别的缓存。8种基本数据类型的常量池是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的String对象会直接存储到常量池中。
- 比如:String info="hello";
- 如果不是使用双引号申明的String对象,可以使用String提供的intern( )方法
- 直接使用双引号声明出来的String对象会直接存储到常量池中。
- Java6以及前,字符串常量池存放在永久代(看下面示例代码,报错信息)
- Java7中,将字符串常量池的位置调到了Java的堆内存中
- Java8中,字符串常量池还是存放在堆中(看下面示例代码,报错信息)
事例代码:
/**
* 在jdk6中:
* -XX:PermSize=6m -XX:MaxPermSize=6m -Xmx6m -Xms6m
*
* 在jdk8中:
* -XX:MetaspaceSize=6m -XX:MaxMetaspaceSize=6m -Xmx6m -Xms6m
*/
public static void main(String[] args) {
//使用Set保持对常量的应用,避免发生Full GC时常量被回收
Set<String> set=new HashSet<String>();
int i=0;
while (true){
set.add(java.lang.String.valueOf(i++).intern());
}
}
将jdk设置成1.6,永久代最大内存设置成6m:
抛出异常:系统提示是永久代的内存不足了
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.booyue.tlh.StringTest.main(StringTest.java:51)
接下来我们把jdk设置成1.8,然后把元空间和堆内存设置的6m:
抛出异常:系统提示是堆空间的内存不足了
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:703)
at java.util.HashMap.putVal(HashMap.java:662)
at java.util.HashMap.put(HashMap.java:611)
at java.util.HashSet.add(HashSet.java:219)
at com.booyue.tlh.StringTest.main(StringTest.java:51)
字符串的拼接操作
- 常量与常量的拼接结果在常量池,原理是编译器优化
- 常量池中不会出现相同的常量
- 只要其中一个是变量,结果就在堆中(jdk8的常量池也在堆中,这里的堆不是指的常量池)。变量拼接的原理是StringBuilder,最后调用StringBuilder的toString( )方法,最后其实是一个new String( )方法
- 如果拼接调用intern()方法,则主动将常量池中还没有的字符串放入池中,并返回此对象地址
实例代码
public class StringTableTest {
@Test
public void test1() {
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//编译器优化,等同于javaEEhadoop
//如果拼接符号前后出现了变量,则需要在堆空间中new 一个StringBuilder,具体的内容为拼接之后的结果:javaEEhadoop
/**
* 具体操作细节:
* 1.StringBuilder s5=new StringBuilder( );
* 2.s5.append("javaEE");
* 3.s5.append("hadoop");
* s5.toString( )//StringBuilder的toString()方法其实就是new 的一个String对象
*/
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//inern()方法会判断字符串常量池中是否存在:javaEEhadoop,如果存在就返回常量池javaEEhadoop的地址
//如果在常量池中不存在:javaEEhadoop,则在常量池中加载一份:javaEEhadoop,并返回对象的地址
String s8 = s6.intern();
System.out.println(s3 == s8);//true
}
}
Intern( )方法
- jdk1.6中,将这个字符串对象尝试放入字符串常量池
- 如果常量池汇中有,则不会放入。返回已有的常量池中对象的地址
- 如果没有,会把该对象复制一份(新的对象,地址不同),放入字符串常量池,并返回字符串常量池中的对象地址
- jdk1.7起,将这个字符串对象尝试放入常量池
- 如果常量池汇中有,则不会放入。返回已有的常量池中对象的地址
- 如果没有,则会把该对象的引用地址复制一份(地址相同),放入字符串常量池,并返回常量池中引用地址
其他
查看StringTable统计信息,JVM参数:-XX:+PrintStringTableStatistics
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 4021 = 96504 bytes, avg 24.000
Number of literals : 4021 = 358352 bytes, avg 89.120
Total footprint : = 934960 bytes
Average bucket size : 0.067
Variance of bucket size : 0.067
Std. dev. of bucket size: 0.259
Maximum bucket size : 3
标签:String,常量,StringTable,System,println,字符串,out 来源: https://blog.csdn.net/qq_27062249/article/details/117092729