Loading... ### String #### 基本特性 - 字符串,使用一对""引起来表示。引用数据类型 - 声明为final的,不可被继承。 - String实现了Serializable、Comparable、CharSequence - String存储数据的结构,在JDK1.8使用的是char数组,jdk9时改为了byte[]。 - 代表不可变的字符序列,简称:不可变性。 - 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。 - 当对现有的字符串进行连接操作的时候,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 - 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 - 通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中。 - 字符串常量池中是不会存储相同内容的字符串的。 针对String的不可变性,可以做如下测试: ```java public class StringTest { @Test public void test1(){ String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2); //true s1 = "def"; System.out.println(s1 == s2);//false System.out.println(s1);//def System.out.println(s2);//abc } @Test public void test2(){ String s1 = "abc"; String s2 = "abc"; s1 += "def"; System.out.println(s1 == s2);//false System.out.println(s1);//abcdef System.out.println(s2);//abc } @Test public void test3(){ String s1 = "abc"; String s2 = "abc"; String s3 = s1.replace('a','m'); System.out.println(s1 == s2);//true System.out.println(s1);//abc System.out.println(s2);//abc System.out.println(s3);//mbc } } ``` ```java String str = new String("good"); char[] ch = {'t','e','s','t'}; public void change(String str, char ch[]){ str = "test ok"; ch[0] = 'b'; System.out.println(str);//test ok } public static void main(String[] args) { StringTest stringTest = new StringTest(); stringTest.change(stringTest.str,stringTest.ch); System.out.println(stringTest.str);//good System.out.println(stringTest.ch);//best } ``` String的字符串常量池是一个固定大小的HashTable,默认值大小长度是1009。如果方静String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。 使用`-XX:StringTableSize`可设置StringTable的长度,在jdk6中StringTable的长度是固定的为1009。在jdk7中,StringTable的长度默认值为60013,JDK8后,1009是可设置的最小值。 #### String的内存分配 在Java语言中有8中基本数据类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。 常量池就类似一个Java系统级别提供的缓存,8中基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊,他的主要使用方法有两种。 - 直接使用双引号声明出来的String对象会直接存储在常量池中。比如:`String str = "string"`; - 如果不是使用双银行声明的String对象,可以使用String提供的`intern()`方法。 #### String的拼接操作 - 常量与常量的拼接结果在常量池,原理是编译期优化。 - 常量池中不会存在相同内容的常量 - 只要其中有一个是变量,结果就在堆(非常量池)中。变量拼接的原理是StringBuilder。 - 如果拼接的结果调用intern方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。 ```java @Test public void test1() { String s1 = "a" + "b" + "c";//三个字符串常量的拼接操作,在编译器就优化成了abc String s2 = "abc";//abc放在常量池中 System.out.println(s1 == s2);//true System.out.println(s1.equals(s2));//true } @Test public void test2(){ String s1 = "javaSE"; String s2 = "hadoop"; String s3 = "javaSEhadoop"; String s4 = "javaSE" + "hadoop";//在编译期就优化成了javaSEhadoop String s5 = s1 + "hadoop";//只要拼接符号前后有变量参与,则相当于在堆空间new String(),而非常量池 String s6 = "javaSE" + 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 String s8 = s5.intern(); System.out.println(s3 == s8);//true } @Test public void test3(){ String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; /* s1 + s2的过程 ① StringBuilder s = new StringBuilder() ② s.append("a"); ③ s.append("b"); ④ s.toString(); --约等于new String("ab"); */ System.out.println(s3 == s4); //false //上述代码创建了几个对象? 2个 } ``` 在使用字符串拼接时需要注意的点: - For循环内的字符串拼接,尽量通过在循环外创建StringBuilder,在循环内通过append方法进行拼接。 - 在确定添加字符串长度情况下,创建StringBuilder时指定其内部Char数组容量大小,避免频繁扩容,数组拷贝。 #### intern() String类中的`intern()`方法是一个native方法。其作用是,当调用intern方法时,如果字符串常量池中已存在值和当前字符串对象值相同的常量时,则返回字符串常量的地址。如果字符串常量中不存在时,则在字符串常量中放入该字符串常量,且返回字符串长来那个的地址。 如果不是用双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。 - JDK1.6中如果没有,会把此对象复制一份,放入字符串常量池中,并返回字符串常量池中的对象地址。 - JDK1.7起,如果没有,会把对象的引用地址复制一份,放入字符串常量池中,并返回这个引用地址。 也就是说,如果在任意字符串上调用String.intern方法,name其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。 Interned String就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)。 ```java String str = new String("ab");//会创建几个对象? //两个,对象1:"ab",对象2:str new String("a") + new String("b");//这个会创建几个对象呢? //对象1:StringBuilder //对象2:"a" //对象3:new String("a") //对象4:"b" //对象5:new String("b") //对象6:new String("ab"),来自于StringBuilder的toString()方法 ``` ```java public static void main(String[] args) { String s = new String("1");//生成两个对象,一个堆空间的"1",一个是常量池中的"1",这里的常量池中的1不是指向new String的 s.intern();//这里发现常量池中已存在"1",所以不做任何事情. String s2 = "1"; System.out.println(s == s2);//false String s3 = new String("1") + new String("1");//最终是new String("11") s3.intern();//JDK7后在常量池中生成一个"11",这个"11"指向的是堆空间中"11"对象的地址。jdk6是直接创建一个新的"11" String s4 = "11";//s4在常量池中的11,指向的地址就是new String("11")的地址 System.out.println(s3 == s4);//JDK6:false,JDK7/8:true String s5 = "abc"; String s6 = new String("abc"); System.out.println(s5 == s6);//false } ``` - intern时,如果常量池中不存在,则创建字符串常量引用,地址是原有对象在堆空间中的地址,所以s3和s4的地址是相同的。 - StringBuilder中的toString方法,是使用的char数组,并不是使用的显示字符串常量,所以不会再常量池中创建对应的字符串常量。 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 3 If you think my article is useful to you, please feel free to appreciate