String
String是JAVA中最常用的对象,让我们一起来学习它吧!
在JAVA语言中有八种基本类型包装类和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。因为String不可变的性质,因此Java内部实现了String常量池。当一个String被创建时,会先去常量池查看有没有值相同的示例,有的话直接返回。节省了内存,加快了字符串的加载速度。不可变的对象也可以保证在并发中保持线程安全
其中String类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的String对象会直接存储在常量池中。
- 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
构造器


特性
- 字符串常量,实际上也是String对象
- 所有不是通过new创建的String都是放在常量池中
- String类型的对象是不可变的
- String实现了CharSequence接口
String对象创建方式
String str1 = "abcd";
String str2 = new String("abcd");
这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
只要使用new方法,便需要创建新的对象
连接表达式+(加号)
- 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
- 对于所有包含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(); // 创建一个对象。
常用方法







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,luoequals(Object anObject)
String.equals()代码逻辑:
判断传入的对象与当前对象是否为同一个对象,如果是就直接返回true;
判断传入的对象是否为String,若不是则返回false(如果为null也不成立);
判断传入的String与当前String长度是否一致,若不一致则返回false;
循环对比两个字符串的char[]数组,逐个对比字符是否一致,若不一致则直接返回false;
循环结束没有找到不匹配的则返回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格式化输出

FAQ
String str1 = "abc"; System.out.println(str1 == "abc");
步骤:
a> 栈中开辟一块空间存放引用str1;
b> String池中开辟一块空间,存放String常量"abc";
c> 引用str1指向池中String常量"abc";
d> str1所指代的地址即常量"abc"所在地址,输出为true;String str2 = new String("abc"); System.out.println(str2 == "abc");
步骤:
a> 栈中开辟一块空间存放引用str2;
b> 堆中开辟一块空间存放一个新建的String对象"abc";
c> 引用str2指向堆中的新建的String对象"abc";
d> str2所指代的对象地址为堆中地址,而常量"abc"地址在池中,输出为false;
注意:对于通过new产生的对象,会先去常量池检查有没有 “abc”,如果没有,先在常量池创建一个 “abc” 对象,然后在堆中创建一个常量池中此 “abc” 对象的拷贝对象;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”时,就是一个对象;其它
```
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 ##【深入解析String#intern -- 美团】
http://rednaxelafx.iteye.com/blog/774673