从Java 13开始,字符串可以用"""..."""表示多行字符串(Text Blocks)了
String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。
字符串的字符使用unicode字符编码,一个字符(不区分字母还是汉字)占用两个字节或多个。
String类结构
字符串的构造器比较多,常用的有以下几个:
String()初始化新创建的 String对象,使其表示空字符序列。 |
|---|
String(byte[] bytes)通过使用平台的默认字符集解码指定的字节数组来构造新的 String 。 |
String(byte[] bytes, Charset charset)构造一个新的String由指定用指定的字节的数组解码charset 。 |
String(byte[] bytes, int offset, int length)通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String 。 |
String(byte[] bytes, int offset, int length, Charset charset)构造一个新的String通过使用指定的指定字节子阵列解码charset 。 |
String(byte[] bytes, int offset, int length, String charsetName)构造一个新的 String通过使用指定的字符集解码指定的字节子阵列。 |
String(byte[] bytes, String charsetName)构造一个新的String由指定用指定的字节的数组解码charset 。 |
String(char[] value)分配一个新的 String ,以便它表示当前包含在字符数组参数中的字符序列。 |
String(char[] value, int offset, int count)分配一个新的 String ,其中包含字符数组参数的子阵列中的字符。 |
String(int[] codePoints, int offset, int count)分配一个新的 String ,其中包含 Unicode code point数组参数的子阵列中的 字符 。 |
String(String original)初始化新创建的String对象,使其表示与参数相同的字符序列; |
String(StringBuffer buffer)分配一个新的字符串,其中包含当前包含在字符串缓冲区参数中的字符序列。 |
String(StringBuilder builder)分配一个新的字符串,其中包含当前包含在字符串构建器参数中的字符序列。 |
字符串类是final的,所以不能被外部类继承, 字符串类中有一个final的属性为字节数组:
java
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
...
private final char value[];
...
}
上面代码,说明两点:
-
value主要用来存储字符串的值 -
字符串本质还是一个字符数组。
final保证value引用不变;String通过封装保证内容不可变(String的设计是不可变:value私有、类final、不暴露修改方法、操作返回新对象)。
字符串类关系图

实现Serializalble接口的对象可以串行化,表示可以在网络上传输
实现Comparable接口的对象可以相互比较
CharSequence是一个描述字符串结构的接口
常用方法
| Return Type | Method and Description |
|---|---|
char |
charAt(int index)返回 char指定索引处的值。 |
int |
compareTo(String anotherString)按字典顺序比较两个字符串。 |
int |
compareToIgnoreCase(String str)按字典顺序比较两个字符串,忽略大小写。 |
String |
concat(String str)将指定的字符串连接到该字符串的末尾 |
boolean |
equalsIgnoreCase(String anotherString)将此 String与其他 String比较,忽略大小写。 |
String |
substring(int beginIndex, int endIndex)返回一个字符串,该字符串是此字符串的子字符串。重载方法 |
char[] |
toCharArray()将此字符串转换为新的字符数组。 |
String |
replace(char oldChar, char newChar)返回从替换所有出现的导致一个字符串 oldChar在此字符串 newChar 。 |
boolean |
isEmpty()返回 true如果,且仅当 length()为 0 。 |
int |
lastIndexOf(String str)返回指定子字符串最后一次出现的字符串中的索引。重载方法 |
int |
indexOf(String str)返回指定子字符串第一次出现的字符串内的索引。重载方法 |
String[] |
split(String regex)将此字符串分割为给定的 regular expression的匹配。重载方法 |
String |
trim()返回一个字符串,其值为此字符串,并删除任何前导和尾随空格 |
boolean |
contains(String findStr) 查看字符串中是否包含某段字符串 |
示例:
java
//substring
String name = "Hello 张三";
System.out.println(name.substring(0, 5));
//indexOf
String s1 = "sfsfd@ssdkd@.com";
System.out.println(s1.indexOf('@'));
System.out.println(s1.indexOf("fd"));
System.out.println(s1.lastIndexOf("@"));
//trim
String s2= " ssd hello 你好 ";
System.out.println(s2.trim()+ "\t" + s2);
//charAt
System.out.println(s1.charAt(1));
//concat
System.out.println(name.concat("; hello").concat(" 李四;"));
//replace
String s3 = "宝玉 and 林黛玉 and 林黛玉 and 宝玉";
System.out.println(s3.replace("宝玉", "泰森") + "\t" + s3);
//split
String s4 = "床前明月光;疑似地上霜;举头望明月;低头思故乡";
String[] s5 = s4.split(";");
for (int i = 0; i < s5.length; i++) {
System.out.println(s5[i]);
}
//某字符串是否包含字符串
System.out.println(s4.contains("明月"));
//compareTo
System.out.println("json".compareTo("Json"));
//toCharArray
String s6 = "import earth";
char[] c2 = s6.toCharArray();
for (int i = 0; i < c2.length; i++) {
System.out.println(c2[i]);
}
String对象
常量池
常量池就像一张哈希表/缓存表,表里保存的是 String 对象的引用,注意:常量池里面不存储字符串内容,保存的是字符串的引用,字符串内容保存在字符串对象的value属性中 。
在 HotSpot 里,字符串常量池可以理解成:
text
StringTable:一个哈希表
key -> 字符串内容(或哈希后的code)
value -> String 对象的引用
对象创建
分为两种方式
-
直接赋值如:
String s = "hello"先从常量池 查看是否有"light"数据对象的引用,如果有,直接指向;如果没有则重新创建,然后指向。
String s最终指向的是常量池的引用JVM执行过程:
- 去
StringTable里查有没有内容为hello的记录。 - 如果没有, 在堆 中创建一个 String 对象,假设它是
对象1。 - 把
对象1的引用登记到 StringTable。 - 把这个引用赋给
a。
textStringTable(表,存的是引用) "hello"/hash ──> 对象1 堆 对象1:内容 "hello" ↑ │ a ────────┘假设有语句
String s2 = "hello",JVM执行过程:- 去
StringTable查,查到了"hello",对应对象1。 - 直接取出
对象1的引用, 赋给b.
这一步不创建新对象 ,也不在
StringTable新增表记录。textStringTable "hello" ──> 对象1 堆 对象1:value ──┐ │ │ │ v │ char[] {'h','e','l','l','o'} a ───┘ b ───┘ - 去
-
调用构器
String s3 = new String("hello");JVM执行过程:
new String("hello")括号里的"hello"是字面量,JVM 先处理它:- 去 StringTable 查,已经有
"hello",对应对象1。如果没有要去创建。 - 直接用对象1。
这一步不创建新对象 ,也不在StringTable新增表记录。
- 去 StringTable 查,已经有
- 在执行
new操作- 在堆中创建"对象2",内容也是
"hello" - 用参数
对象1的内容初始化对象2
可以理解为:对象2的 value 引用指向对象1的 value 数组。两个可能指向同一数组。 - 把
对象2的引用赋给s3
对象2不会自动登记进 StringTable。 StringTable 里仍然只有一条"hello"记录,指向对象①。
- 在堆中创建"对象2",内容也是
textStringTable "hello" ──> 对象① 堆 对象1:value ──┐ │ │ │ v │ char[] {'h','e','l','l','o'} a ───┘ ^ b ───┘ │ │ 对象2:value ──┘ ↑ │ c ──┘
s2 = s2.intern() 返回字符串常量池中与 s3 内容相等的那个"规范实例"的引用。 intern() 做两件事之一:
- 池中已有内容为
hello的字符串, 直接返回池中那个对象的引用。 - 池中没有内容为
hello的字符串 , 把当前字符串登记到池中,然后返回池中对应引用。
下面示例:
java
String greeting = "Hello"; //initialize greeting to a string
if (greeting == "Hello") ...
// probably true
if (greeting.substring(0,3) == "Hel") ... //substring返回需要重新new String,所以重新创建对象。与"Hel"对应的引用不一样
// probably false
String变量还可以存放一个特殊的值,那就是null,这表示目前没有任何对象与该变量关联。要检查一个字符串是否为null,要使用以下条件:if (str == null)
有时要检查一个字符串既不是null也不为空串,这种情况下就需要使用以下条件:if (str != null && str.1ength()!= 0) 注意顺序, 先要判断不为null
总结:
== 比较引用:两个变量是否指向同一个对象
equals() 比较内容:两个字符串的字符序列是否一样
对象特性
- 描述下列语句的内存执行过程
java
String s1 = "hello";//先从常量池查看是否有"hello"数据引用,如果有,直接指向;如果没有则重新创建,然后指向。
s1 = "hahaha";//先从常量池查看是否有"hahaha"数据引用,如果有,先断开原来的指向地址,然后直接指向新的地址;如果没有则创建,然后先断开原来的指向地址,再直接指向新的地址;
-
面试题
String a = "hello"+"abc";创建了几个对象?只有1个对象,这里两个都是字符串字面量 ,属于编译期常量表达式,
javac会直接做常量折叠,编译后等价于:"helloabc" -
下面代码创建了几个对象?
javaString h = "hello"; String c = h + "abc";对于
String c = h + "abc"语句,java8采用StringBuilder对象调用append方法,具体为:-
先构建一个
StringBuilder对象。 -
调用
StringBuilder类中的public StringBuilder append(String str)这个方法,str参数为"hello" -
再次调用
StringBuilder类中的public StringBuilder append(String str)这个方法,str参数为"abc" -
调用
StringBuilder类中toString方法:java@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count);//这里的value值为helloabc } -
然后将创建的对象赋给c。
这样会产生的问题:
- 拼接策略在编译期就写死了。
- 可能创建
StringBuilder中间对象。 - 默认容量 16,可能多次扩容。
- JVM 想优化也难,因为字节码里已经是普通方法调用。
Java 9+ 对运行时字符串
+拼接使用invokedynamic,一句话总结:javac不再把+固定翻译成StringBuilder.append链,而是生成一条invokedynamic指令,运行时由StringConcatFactory决定怎么拼。运行时拼接结果
c通常会创建一个新的 String 对象 , 来接收拼接的字符串。所有最终 String 对象通常还是要创建的,优化的是中间过程。 -
-
下列程序运行结果是什么?尝试画出内存图
JAVApublic class String03 { String str = new String("light"); //使用final修饰的引用不能指向另一个引用 final char[] ch = { 'j', 'a', 'v', 'a'}; public static void main(String[] args) { String03 ex = new String03(); ex.change(ex.str, ex.ch); System.out.print(ex.str + "and "); //lightand System.out.println(ex.ch);//lava } public void change(String str, char ch[]) { str = "java"; ch[0] = 'l'; } }从内存分析中得出结果:
lightand lavach是一个引用变量,{ 'j', 'a', 'v', 'a' }是一个数组对象 。ch里存的不是四个字符,而是数组对象的引用/地址。堆里的数组对象大致包含:textchar[] 数组对象 ┌─────────────────────┐ │ 对象头 │ Mark Word + 类型指针 ├─────────────────────┤ │ 长度 length = 4 │ ├─────────────────────┤ │ [0] 'j' │ 2 字节 │ [1] 'a' │ 2 字节 │ [2] 'v' │ 2 字节 │ [3] 'a' │ 2 字节 └─────────────────────┘
上面代码的内存分析图:

StringBuffer
String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因此java设计这还提供了StringBuilder和StringBuffer来增强String的功能,并提供效率。
StringBuffer线程安全的可变字符序列。可以对字符串内容进行增删。StringBuffer也是一个容器。
StringBuffer特性

-
StringBuffer父类继承了抽象类AbstractStringBuilder。 -
StringBuffer实现了Serializable接口,实现了串行化。 -
StringBuffer的父类AbstractStringBufferBuilder中有属性value[]。与String不同,此value属性没有使用private final修饰。此属性用于存储字符串的内容,并且存放在堆中的。javaabstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; ... -
StringBuffer是一个用final修饰的类。
String Vs StringBuffer
String保存的是字符串常量,里面的值不能更改,每次String类更新实际上是更改地址,效率低。
StringBuffer保存的是字符串变量,里面的值是可以更改的。StringBuffer不用每次都更新地址,效率更高。

构造器
JAVA
//构造器的使用
//1.创建一个为16的char[],用于存放字符内容
StringBuffer stringBuffer = new StringBuffer();
//2.通过构造器指定char[]大小
StringBuffer stringBuffer1 = new StringBuffer(100);
//3.通过给一个String创建StringBuffer,char[]大小就是str.length()+16
StringBuffer hello = new StringBuffer("hello");
String与StringBuffer互转
JAVA
//String--->StringBuffer
String str = "hello tom";
//方式1:使用构造器; 注意:返回的才是StringBuffer对象,对str本身没有影响
StringBuffer stringBuffer2 = new StringBuffer(str);
//方式2:使用的是append方法
StringBuffer stringBuffer3 = new StringBuffer();
StringBuffer append = stringBuffer3.append(str);
//StringBuffer---->String
StringBuffer stringBuffer4 = new StringBuffer("XIAOSSS");
//方式1:使用StringBuffer提供的toString方法, 重写的toString里面其实也是return new String(StringBuffer);
String s = stringBuffer4.toString();
//方式2:使用构造器来搞定
String s1 = new String(stringBuffer4);
StringBuffer常用方法
| Return Type | Method and Description |
|---|---|
StringBuffer |
append(String str)将指定的字符串附加到此字符序列。类中此方法重载 |
StringBuffer |
insert(int offset, String str)将字符串插入到此字符序列中。类中此方法重载 |
StringBuffer |
replace(int start, int end, String str)将start----end间的内容替换掉,不含end 。 |
StringBuffer |
delete(int start, int end)删除此序列的子字符串中的字符。不含end 。 |
StringBuffer |
deleteCharAt(int index)删除 char在这个序列中的指定位置。 |
int |
indexOf(String str)找子在字符串第1次出现的索引,如果找不到返回-1。 |
int |
indexOf(String str, int fromIndex)找子在字符串第1次出现的索引,从指定的索引开始。如果找不到返回-1。 |
StringBuffer |
reverse()导致该字符序列被序列的相反代替。 |
int |
length()返回长度(字符数)。 |
int |
capacity()返回当前容量。 |
char |
charAt(int index)返回 char在指定索引在这个序列值。 |
String |
substring(int start)返回一个新的 String ,其中包含此字符序列中当前包含的字符的子序列。 |
String |
substring(int start, int end)返回一个新的 String ,其中包含此序列中当前包含的字符的子序列。 |
在java中,offset 是指偏移量/位置,而index指的是索引。start、end表示索引(而且一般这种表示的都是不包括end)
示例:
JAVA
//常用方法
StringBuffer s =new StringBuffer("hello");
//增
s.append(',');
s.append("张三丰");
s.append("赵敏").append(100).append(true).append(10.5);
System.out.println(s);// hello,张三丰赵敏100true10.5
//删 删除索引为[11, 14)处的字符
s.delete(11,14);
System.out.println(s);
//改 [9,11)位置的字符
s.replace(9,11, "周芷若");
System.out.println(s);
//查 查找指定的子串在字符串第一次出现的索引,如果找不到返回---1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);
//插 hello,张三丰周芷若true10.5
s.insert(9,"赵敏");
System.out.println(s);
//长度
System.out.println(s.length());
System.out.println(s);
StringBuilder
基本介绍
1)一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但不保证同步(StringBuilder不是线程安全)。该类被设计用作StringBuffer的一个简易
替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。
2)在StringBuiIder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。
类关系图及特性

特性:具有和StringBuffer相同的4点特性,但是StringBuiIder不是线程安全的,所有方法没有synchronized关键字。
String、StringBuffer、StringBuilder比较
1)StringBuiIder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样。
2)String:不可变字符序列,效率低,但是复用率高(可以共享)。
3)StringBuffer:可变字符序列、效率较高(增删)、线程安全
4)StringBuilder:可变字符序列、效率最高、线程不安全
String使用注意说明:
string s = "a"; //创建了一个字符串
s += "b"; //实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s += "b"(也就是"ab")。如果多次执行这些改变内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。结论:如果我们对String做大量修改,不要使用String。