Java String 字符串
Java 中的 String 字符串是 Java 中最常用的类之一,String 字符串是一个不可变的字符序列,用于表示文本。
Java String
String 字符串对象可以通过字面值或构造方法创建。例如:
// 通过字面值创建 String 对象
String str1 = "Hello, world!";
// 通过构造方法创建 String 对象
String str2 = new String("Hello, world!");
Java String 不可变
Java 中的 String 有一个很重要的特性,就是 Java String 一旦初始化,就不可改变。虽然 String 类中看起来有很多修改字符串内容的方法,但是其实都是生成新的字符串。比如像下面这样让 str1
拼接上 !!!
,会发现输出的还是 Hello world
.
String str1 = "Hello, world";
str1.concat("!!!");
System.out.println(str1);
// 输出:Hello, world
想要改变 str1
的值,只能对 str1
直接重新赋值。
String str1 = "Hello, world";
str1 = str1.concat("!!!");
System.out.println(str1);
为什么 String 值不能改变,从实现原因上,可以在源码中看到 final char value[]
的限制。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
//.....
}
Java String 方法
String 类提供了许多方法,用于操作字符串。以下是一些常用的方法:
方法 | 描述 |
---|---|
length() | 返回字符串的长度。 |
charAt(int index) | 返回字符串中指定位置的字符。 |
substring(int beginIndex) | 返回字符串中从指定位置开始到结尾的子串。 |
substring(int beginIndex, int endIndex) | 返回字符串中从 beginIndex 开始到 endIndex 的子串。 |
equals(Object obj) | 比较字符串是否相等。 |
equalsIgnoreCase(String anotherString) | 比较字符串是否相等,忽略大小写。 |
compareTo(String anotherString) | 比较字符串的大小关系。 |
compareToIgnoreCase(String str) | 比较字符串的大小关系,忽略大小写。 |
indexOf(int ch) | 返回指定字符在字符串中第一次出现的位置。 |
indexOf(int ch, int fromIndex) | 返回指定字符在字符串中从指定位置开始第一次出现的位置。 |
indexOf(String str) | 返回指定字符串在字符串中第一次出现的位置。 |
indexOf(String str, int fromIndex) | 返回指定字符串在字符串中从指定位置开始第一次出现的位置。 |
lastIndexOf(int ch) | 返回指定字符在字符串中最后一次出现的位置。 |
lastIndexOf(int ch, int fromIndex) | 返回指定字符在字符串中从指定位置开始最后一次出现的位置。 |
lastIndexOf(String str) | 返回指定字符串在字符串中最后一次出现的位置。 |
lastIndexOf(String str, int fromIndex) | 返回指定字符串在字符串中从指定位置开始最后出现的位置。 |
toCharArray() | 返回指定字符串的字符数组( "aabb" => {'a','a','b','b'}) |
trim() | 清空字符串收尾的空格( " aabb " => "aabb") |
Java String 接口
String 类实现了三个接口,从源码中可以看到。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
//.....
}
Comparable
String 类实现了 Comparable 接口,因此可以使用 compareTo() 方法比较两个字符串的大小关系。如果字符串相等,则返回 0;如果当前字符串大于另一个字符串,则返回正整数;如果当前字符串小于另一个字符串,则返回负整数。
String str1 = "abc";
String str2 = "abz";
System.out.println(str1.compareTo(str2));
System.out.println(str2.compareTo(str1));
// 输出:-23
// 输出:23
字符串的比较是字符的自然排序比较,并不会比较总体的数值,比如字符串 199999
是小于字符串 2
的。
System.out.println("19999".compareTo("2"));
// 输出:-1
CharSequence
CharSequence 用于表示一个字符序列的抽象。CharSequence 接口位于 java.lang 包中,是 Java 中的一个接口,CharSequence 接口定义了以下方法:
- charAt(int index):返回指定位置的字符。
- length():返回字符序列的长度。
- subSequence(int start, int end):返回从 start 到 end 的子序列。
- chars() : 返回字符串的 IntStream 流。
- codePoints(): 和 chars 类似,返回字符串的 IntStream 流。
codePoints() 方法返回的是 Unicode 代码点,而 chars() 方法返回的是字符的 UTF-16 编码。在大多数情况下,这两种方法的结果是相同的,但是对于一些特殊字符(如 Emoji 表情符号),它们的结果可能不同。因此,在处理字符序列时,应该根据具体情况选择使用哪种方法。
简单演示一下 chars()
方法,它用于返回一个 IntStream,其中包含此字符序列中的字符。例如:
String str = "Hello, world!";
str.chars().forEach(System.out::println);
输出:
72
101
108
108
111
这个示例代码使用 chars()
方法返回一个 IntStream,然后使用 forEach()
方法遍历 IntStream 中的每个元素,并将其打印到控制台上。但是这里的 IntStream 中的每个元素都是字符的 Unicode 编码。如果需要将其转换为字符,可以使用 (char)
进行强制类型转换。例如:
String str = "Hello, world!";
str.chars().mapToObj(c -> (char) c).forEach(System.out::print);
输出:
Hello, world!
这个示例代码使用 mapToObj()
方法将 IntStream 中的每个元素转换为字符,并将其打印到控制台上。
CharSequence 接口的一个重要特点是它的方法都是读取操作。这意味着,一旦创建了一个 CharSequence 对象,就不能修改它的值。如果需要修改字符序列,可以使用 CharSequence 的其他实现类,如 StringBuffer 、 StringBuilder 类。
Java String 内存分配
Java 中的 String 类是一个不可变的类,它的值在创建后不能被修改。String 对象的内存分配逻辑与其他对象相同,即在堆上分配一块内存空间来存储对象的实例变量。但是,String 对象的值是存储在常量池中的,而不是存储在对象的实例变量中的。
字符串对象存储在被称为字符串常量池的特殊内存区域中。
Java 中的常量池是一块特殊的内存区域,用于存储常量。在编译 Java 代码时,编译器会将所有的字符串常量都放入常量池中。在运行 Java 程序时,如果需要创建一个字符串对象,Java 虚拟机会先在常量池中查找是否已经存在一个值相同的字符串对象。如果存在,则直接返回该对象的引用;否则,创建一个新的字符串对象,并将其存储在常量池中。
由于 String 对象的值是存储在常量池中的,因此多个 String 对象可以共享同一个值。例如,下面的代码创建了两个 String 对象,但是它们的值是相同的,因此都会引用常量池中的同一个 Hello!
字符串:
String str1 = "Hello!";
String str2 = "Hello!";
System.out.println(str1 == str2);
// 输出:true
在这个例子中,Java 虚拟机只会在常量池中创建一个字符串对象,然后让 str1 和 str2 都指向这个对象。这种共享字符串对象的机制可以提高程序的性能和节省内存空间。
但是,如果使用 new
关键字创建字符串对象,则不会共享对象。例如:
String str3 = new String("Hello!");
String str4 = new String("Hello!");
System.out.println(str3 == str4);
// 输出:false
在这个例子中,Java 虚拟机会在堆上分配两个不同的字符串对象,它们的值相同但是地址不同。因此,输出结果为 false。
通过下面的图可以很好的展示原因。
可见,如果使用 new String
创建大量的对象,可能会占用较多内存。
注意,两个字符串直接拼接,会引用字符串常量池。但是两个 String 变量直接拼接,等于 new String
。
String str1 = "Hello";
String str2 = "world";
String str3 = str1 + str2;
String str4 = str1 + str2;
String str5 = "Hello" + "world";
String str6 = "Hello" + "world";
System.out.println(str3 == str4); // false
System.out.println(str5 == str6); // true
String 常见问题
由于 String 对象是不可变的,因此每次对字符串进行修改都会创建一个新的 String 对象。如果需要频繁修改字符串,可以使用 StringBuilder 或 StringBuffer 类。StringBuilder 和 StringBuffer 类提供了许多方法,用于修改字符串,例如 append() 方法用于在字符串末尾添加字符或字符串,insert() 方法用于在字符串中插入字符或字符串,等等。StringBuilder 和 StringBuffer 类的区别在于,StringBuilder 类是线程不安全的,而 StringBuffer 类是线程安全的。
一如既往,文章中代码存放在 Github.com/niumoo/javaNotes.