目录
String 类
String 概述
Java.lang.String 类代表字符串,Java 程序中的所有字符串文字(例如 “abc”)都为此类的对象。
String str1 = "你好";
String str2 = "java";
String 注意点
字符串的内容是不会发生改变的,它的对象在创建后不能被更改,如果更改则会产生新的字符串。
String str1 = "你好";
str1 = "hello ";
String str2 = "java";
System.out.println(str1 + str2);// 字符串拼接产生一个新的字符串 "hello java"
- String 是引用数据类型,所以当创建了一个 String 类对象后,用变量 str1 指向对象值 “你好”。
- 将 "hello " 赋给 str1,并不是修改了原对象的值,而是创建了一个新的对象,并将 str1 指向新对象的值 "hello ",原对象的值依然存在于内存之中。
字符串相关类(如
String
、StringBuilder
、StringBuffer
)重写了toString()
,直接返回字符串内容(而非内存地址或其他属性)。
创建 String 对象
直接赋值
String str = "Hello Java";
new 关键字使用构造方法
public String()
创建空白字符串,不含任何内容public String(String original)
根据传入的字符串,创建字符串对象public String(char[] chs)
根据字符数组,创建字符串对象public String(byte[] bys)
根据字节数组,创建字符串对象
String s1 = new String();// 输出 ""
String s2 = new String("abc");// 输出 abc
char[] chs = {'a','b','c'};
String s3 = new String(chs);// 输出 abc
byte[] bys = {97,98,99};
String s4 = new String(bys);//输出 abc
内存原理
直接赋值
原理
首先要了解一个概念:字符串常量池(串池)是 Java 虚拟机(JVM)为了节省内存和提高性能而设计的一块特殊内存区域,用于存储字符串字面量和使用 intern()
方法手动添加的字符串对象。在 Java 7 及之后的版本中,串池位于堆内存中。
以下是通过直接赋值创建 String 对象的代码示例:
public class StringDemo {
public static main(String[] args) {
String s1 = "abc";
String s2 = "abc";
}
}
- 在这段代码中,main 方法执行入栈,然后创建变量 s1,接着看串池中是否存在 s1 指向的值 “abc”,如果没有,就在串池中创建 “abc” 并将地址值赋值给 s1。
- 然后创建变量 s2,因为串池中已经存在 “abc”,就直接将其地址值赋值给 s2。
总结
当使用双引号直接赋值时,系统会检查该字符串在串池中是否存在:不存在则创建新的,存在则复用。
new 构造方法
原理
当使用 new String()
构造方法创建字符串对象时,JVM 会在堆内存中开辟一块新的空间来存储这个字符串对象,而不管串池中是否已经存在相同内容的字符串。
以下是通过 new 关键字使用构造方法创建 String 对象的代码示例:
public class StringDemo {
public static main(String[] args) {
char[] chs = {'a','b','c'};
String s1 = new String(chs);
String s2 = new String(chs);
}
}
- main 方法执行入栈,先执行
char[] chs = {'a','b','c'};
,在堆内存中开辟一个空间存放 ‘a’,‘b’,‘c’,然后将地址值赋值给变量 chs。
- 然后执行
String s1 = new String(chs);
,在堆内存中开辟一个空间存放 “abc”,将其地址值赋值给 s1。
- 然后执行
String s2 = new String(chs);
,在堆内存中开辟一个空间存放 “abc”,将其地址值赋值给 s2。
总结
每用一次 new String()
都会再堆内存中开辟一个新的空间,这样会很占内存,所以还是推荐直接赋值的方法创建 String 对象。
字符串比较
“==” 比较
在了解字符串比较方法前,先了解一下 Java 中用 “==” 的比较,分为基本数据类型和引用数据类型:
基本数据类型使用 “==”
“==” 在基本数据类型中比较的是数据值,以下面代码为例:
int a = 10;
int b = 20;
System.out.println(a == b); // false
引用数据类型使用 “==”
“==” 在引用数据类型中比较的是地址值,有 3 种情况,以下面代码为例:
// 1.比较的两个字符串都是通过直接赋值创建的
String s1 = "abc"; // 在串池中开辟的内存空间的地址值
String s2 = "abc"; // 复用已开辟内存空间的地址值
System.out.println(s1 == s2); // true
// 2.比较的两个字符串,一个是直接赋值创建,另一个是通过 new 关键字创建的
String s1 = new String("abc"); // 在堆内存中开辟内存空间的地址值
String s2 = "abc"; // 在串池中开辟的内存空间的地址值
System.out.println(s1 == s2); // false
// 3.比较的两个字符串都是通过 new 关键字创建的
String s1 = new String("abc"); // 在堆内存中开辟内存空间的地址值
String s2 = new String("abc"); // 在堆内存中开辟另一个内存空间的地址值
System.out.println(s1 == s2); // false
注意事项:
先看以下代码:
import java.util.Scanner;
public class StringCompare {
public static main(String[] args) {
Scanner input = new Scanner(System.in);
String s1 = input.next(); // 输入abc
String s2 = "abc";
System.out.println(s1 == s2); // false
}
}
导致两个字符串地址值不相等的原因:由 Scanner 类输入的内容是通过 new 关键字创建的对象。
字符串比较方法
“==” 只能通过比较地址值是否相等来判断内容是否一样,下面的两个方法可以直接比较字符串内容是否相等:
-
boolean equals()
两个字符串相比较,完全一样为 true,否则为 false。 -
boolean equalsIgnoreCase()
忽略大小写的比较
public class StringCompare {
public static main(String[] args) {
String s1 = "abc";
String s2 = "Abc";
String s3 = new String("abc");
System.out.println(s1.equals(s3)); // true
System.out.println(s1.equalsIgnoreCase(s2)); // true
}
}
规范化表示形式
在 Java 中,字符串对象的规范化表示形式通常指通过字符串池机制,确保相同内容的字符串指向内存中的唯一实例。这是通过 intern()
方法实现的,其核心目的是优化内存使用和比较效率。
关键概念
-
字符串池(String Pool)
-
JVM 维护了一个字符串池(位于堆内存中),用于存储字符串字面量(如
"hello"
)。 -
当直接使用字面量创建字符串时(如
String s = "hello"
),JVM 会先检查池中是否存在该字符串:- 若存在,直接引用池中的对象。
- 若不存在,将字符串添加到池中,再返回引用。
-
-
new String()
与字面量的区别- 通过
new String("hello")
创建的字符串对象不会直接放入池中,而是直接在堆中生成新对象。 - 即使内容相同,多次
new String()
会产生不同对象,导致==
比较失败。
- 通过
-
intern()
方法的作用- 调用
intern()
会强制将字符串对象添加到字符串池(如果池中不存在该字符串),并返回池中的引用。 - 若池中已存在,直接返回池中对象的引用。
- 调用
String s1 = new String("hello"); // 堆中的对象,常量池已存在"hello"(因字面量)
String s2 = s1.intern(); // 返回常量池的引用
System.out.println(s1 == s2); // false(s1在堆,s2在池)
System.out.println("hello" == s2); // true(均指向池中的"hello")
String s3 = new String("world"); // 堆中的对象,常量池尚无"world"(假设首次创建)
String s4 = s3.intern(); // 将"world"加入常量池,返回池引用
System.out.println(s3 == s4); // false(s3在堆,s4在池)
适用场景
- 减少重复字符串的内存占用
适合处理大量内容相同的字符串(如日志处理),通过intern()
共享唯一实例。 - 加速字符串比较
“==” 比较常量池引用比equals()
更高效,但需确保所有字符串都经过intern()
处理。
注意事项
- 性能权衡
频繁调用intern()
可能导致哈希表查询开销,需结合场景测试。 - 慎用于动态字符串
对动态生成的字符串(如拼接结果)使用intern()
可能导致常量池膨胀。
总结
intern()
通过复用常量池中的字符串,优化内存和比较效率,但需权衡性能与场景。在需要处理大量重复字符串时,合理使用它可显著提升程序效率。
遍历字符串
-
public char charAt(int index)
根据索引返回字符。 -
public int length()
返回字符串的长度。
以下是一个遍历字符串的代码示例:
public class printString {
public static main(String[] args) {
String s = "hello java";
for(int i = 0;i < s.length();i++){
System.out.println(s.charAt(i));
}
}
}
空格在字符串中也占内存,所以输出结果如下:
h
e
l
l
o
j
a
v
a
StringJoiner 类
StringJoiner 类概述
- StringJoiner 类跟 StringBuilder 类一样,可以看作对字符串的操作类,创建之后里面的内容是可变的。
- 作用:提高字符串的操作效率,而且代码编写特别简洁。
- JDK 8 新特性。
StringJoiner 类的构造方法
public StringJoiner(间隔符号)
创建一个 StringJoiner 对象,指定拼接时的间隔符号。public StringJoiner(间隔符号,开始符号,结束符号)
创建一个 StringJoiner 对象,指定拼接时的间隔符号、开始符号、结束符号。
import java.util.StringJoiner;
public class StringJoinerDemo {
public static main(String[] args){
StringJoiner sj = new StringJoiner(",","[","]");
sj.add("a").add("b").add("c");
System.out.println(sj); // 输出 [a,b,c]
}
}