ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
### String 在JAVA语言中有八种基本类型包装类和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。因为String不可变的性质,因此Java内部实现了String常量池。当一个String被创建时,会先去常量池查看有没有值相同的示例,有的话直接返回。节省了内存,加快了字符串的加载速度。不可变的对象也可以保证在并发中保持线程安全 其中String类型的常量池比较特殊。它的主要使用方法有两种: * 直接使用双引号声明出来的String对象会直接存储在常量池中。 * 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中 ### 构造器 ![](https://img.kancloud.cn/6f/1a/6f1a725fa8e4b35231c6957501dfcb30_809x130.png) ![](https://img.kancloud.cn/37/bf/37bfbdc1d623d9e4b49b955369e2d1d5_807x278.png) ### 特性 * 字符串常量,实际上也是String对象 * 所有不是通过new创建的String都是放在常量池中 * String类型的对象是不可变的 * String实现了CharSequence接口 ### String对象创建方式 ``` String str1 = "abcd"; String str2 = new String("abcd"); ``` 这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。 只要使用new方法,便需要创建新的对象 ### 连接表达式+\(加号\) 1. 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。 2. 对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中 ``` String str1 = "str"; String str2 = "ing"; String str3 = "str" + "ing"; String str4 = str1 + str2; System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true ``` ``` 1、 Sting s; //定义了一个变量s,没有创建对象; 2、 = // 赋值,将某个对象的引用(句柄)赋给s ,没有创建对象; 3、 “abc” //创建一个对象; 4、 new String(); // 创建一个对象。 ``` ### 常用方法 ![](https://img.kancloud.cn/5a/b8/5ab8d6a8f55ca52b9770a83d29d2e910_803x51.png) ![](https://img.kancloud.cn/4b/37/4b37298a0b771efcb49ff55e3d8761fc_806x82.png) ![](https://img.kancloud.cn/a0/05/a00569e48e560aec9f0ade81babc77e4_815x230.png) ![](https://img.kancloud.cn/f7/2a/f72a63d0e5f7a4ef4bd605273054180e_801x134.png) ![](https://img.kancloud.cn/07/19/07192e2aed8468e62dce058f9a1ad07c_847x618.png)![](https://img.kancloud.cn/e8/c0/e8c094e181420e87ccd4d5f8315a91dc_837x562.png)![](https://img.kancloud.cn/48/f6/48f6654f1f4bde0ef7dff769a029cf5f_818x145.png) * length 返回字符串长度 * isEmpty 判断字符串是否为空 * charAt 根据索引位置获取char * getChars 复制对应位置范围的char到数组中 * equals, equalsIgnoreCase 对比顺序依次为引用地址,char数组长度,char数组内容 * compareTo 对比字符串大小 * startsWith, endsWith 判断前后缀 * hashCode 计算hash值, 公式为s\[0\]\*31^\(n-1\) + s\[1\]\*31^\(n-2\) + ... + s\[n-1\] * indexOf 查找首次出现的位置 * lastIndexOf 查找最后出现的位置 * substring 返回子串(旧版本是返回一个引用在父串的一个新串,节省重新分配内存。但实际如果子串引用了一个占用极大的父串,会因为子串一直被使用导致父串没法被垃圾回收,新版本substring每次重新复制char数组) * concat 拼接字符串(拼接char数组,重新创建字符串) * replace 用新字符替换所有的旧字符(会先遍历一次char数组,寻找时候存在,再去替换,避免每次都要分配char数组) * matches 判断是否符合正则 (复用Pattern.matches\(\)方法) * contains 判断是否包含子串(复用indexOf\(\)方法) * replaceFirst 只替换一次 * replaceAll 替换所有正则符合的地方 * split 按照正则分割字符串 * toLowerCase 返回小写 * toUpperCase 返回大写 * trim 去除前后空格 * toCharArray 重新复制char数组返回 * join\(CharSequence delimiter, CharSequence... elements\) ``` String.join(",", "you", "bao", "luo"); //out: you,bao,luo ``` * equals\(Object anObject\) String.equals\(\)代码逻辑: 1. 判断传入的对象与当前对象是否为同一个对象,如果是就直接返回true; 2. 判断传入的对象是否为String,若不是则返回false\(如果为null也不成立\); 3. 判断传入的String与当前String长度是否一致,若不一致则返回false; 4. 循环对比两个字符串的char\[\]数组,逐个对比字符是否一致,若不一致则直接返回false; 5. 循环结束没有找到不匹配的则返回true; ``` JDK8源码: public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } ``` * intern\(\):naive方法,直接返回常量池中的引用 当调用intern\(\)方法时,JVM会在常量池中通过equals\(\)方法查找是否存在等值的String,如果存在则直接返回常量池中这个String对象的地址;如果不存在则会创建等值的字符串放入常量池\(即等值的char\[\]数组字符串,但是char\[\]是新开辟的一份拷贝空间\),然后再返回这个新创建空间的地址; 注意:String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009 在常量池查找等值String时,通常不止一个字符串而是多个字符串因此效率会比较低,另外为保证唯一性,需要有锁的介入; ``` String str1 = "ab"; String str2 = new String("ab"); System.out.println(str1== str2);//false System.out.println(str2.intern() == str1);//true System.out.println(str1== str2);//false str2 = str2.intern(); System.out.println(str1== str2);//true ``` ### 知识点 * 在调用x.toString\(\)的地方可以用""+x替代; * 字符串的+拼接操作 ``` public static void main(String[] args) throws InterruptedException { String s = "a"; String st = s + "b" + "c"; } javap out====> Code: stack=3, locals=3, args_size=1 0: ldc #19 // String a 2: astore_1 3: new #21 // class java/lang/StringBuilder 6: dup 7: aload_1 8: invokestatic #23 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 11: invokespecial #29 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 14: ldc #32 // String b 16: invokevirtual #34 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #38 // String c 21: invokevirtual #34 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #40 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore_2 28: return ``` * StringBuffer是线程安全操作 ``` public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } ``` * StringBuilder非线程安全 ``` public StringBuilder append(String str) { super.append(str); return this; } ``` ``` System.err.println("hello,world"); ##hello,world实际是String对象 ``` ### printf格式化输出 ![](https://img.kancloud.cn/2d/27/2d2767f1acc42cc4544c1ace11ec4e7c_761x471.png) **FAQ** 1. String str1 = "abc"; System.out.println\(str1 == "abc"\); 步骤: a&gt; 栈中开辟一块空间存放引用str1; b&gt; String池中开辟一块空间,存放String常量"abc"; c&gt; 引用str1指向池中String常量"abc"; d&gt; str1所指代的地址即常量"abc"所在地址,输出为true; 1. String str2 = new String\("abc"\); System.out.println\(str2 == "abc"\); 步骤: a&gt; 栈中开辟一块空间存放引用str2; b&gt; 堆中开辟一块空间存放一个新建的String对象"abc"; c&gt; 引用str2指向堆中的新建的String对象"abc"; d&gt; str2所指代的对象地址为堆中地址,而常量"abc"地址在池中,输出为false; 注意:对于通过new产生的对象,会先去常量池检查有没有 “abc”,如果没有,先在常量池创建一个 “abc” 对象,然后在堆中创建一个常量池中此 “abc” 对象的拷贝对象; 2. String s2 = new String\(“Hello”\); 产生几个对象? 首先,在jvm的工作过程中,会创建一片的内存空间专门存入string对象。我们把这片内存空间叫做string池; String s2 = new String\(“Hello”\);jvm首先在string池内里面看找不找到字符串"Hello",如果找到不做任何事情;否则创建新的string对象,放到string池里面。由于遇到了new,还会在内存Heap上(不是string池里面)创建string对象存储"Hello",并将内存上的(不是string池内的)string对象返回给s2。 Re: 如果常量池中原来没有“Hello”, 则创建两个对象。如果原来的常量池中存在“Hello”时,就是一个对象; 3. 其它 \`\`\` String str1 = "a"; String str2 = "b"; String str3 = str1 + "b"; //str1 和 str2 是字符串常量,所以在编译期就确定了。 //str3 中有个 str1 是引用,所以不会在编译期确定。 //又因为String是 final 类型的,所以在 str1 + "b" 的时候实际上是创建了一个新的对象,在把新对象的引用传给str3 final String str1 = "a"; String str2 = "b"; String str3 = str1 + "b"; //这里和\(3\)的不同就是给 str1 加上了一个final,这样str1就变成了一个常量。 //这样 str3 就可以在编译期中就确定了 \`\`\` 【参考资料】 [https://tech.meituan.com/in\_depth\_understanding\_string\_intern.html](https://tech.meituan.com/in_depth_understanding_string_intern.html) \#\#【深入解析String\#intern -- 美团】 [http://rednaxelafx.iteye.com/blog/774673](http://rednaxelafx.iteye.com/blog/774673)