文章目录
- 一、认识 String 类
- 二、String 类的常用方法
- 2.1 构造方法
- 2.2 String 类对象之间的比较
- 2.3 字符串查找
- 2.4 字符串的转换
- 2.5 字符串替换
- 2.6 字符串拆分
- 2.7 字符串截取
- 2.8 字符串常量池
- 2.9 字符串的不可变性
- 三、StringBuilder 和 StringBuffer
- 3.1 StringBuilder
- 3.2 StringBuffer
- 3.3 String、StringBuffer 和 StringBuilder 之间的区别
一、认识 String 类
在C语言中如果想要表示字符串,就只能使用字符数组或者字符指针,然后通过标准库提供的一系列字符串操作函数对字符串进行操作。但是这是数据与操作数据的方法分离的方式显然不符合面向对象的思想。因此在Java中,String
类就是一种符合面向对象思想的字符串类,String
类是用于表示字符串的类。它是Java的核心类库中的一部分,因此无需特殊导入就可以在任何Java程序中使用。
String
类是不可变的,这意味着一旦创建了一个字符串对象,就不能修改其内容。每当对字符串进行修改操作时,实际上是创建了一个新的字符串对象。这种设计是为了确保字符串的不可变性,以提高性能和安全性。因此如果在频繁的字符串拼接或修改场景中,建议使用StringBuilder
类或StringBuffer
类,它们提供了可变的字符串操作方法,可以避免不必要的对象创建。
二、String 类的常用方法
2.1 构造方法
String
类提供了多个构造方法来创建字符串对象。下面是一些常用的构造方法:
String()
:创建一个空字符串对象。
String str = new String();
String(String original)
:通过将指定的字符串复制一份来创建一个新的字符串对象。
String str = new String("Hello");
String(char[] value)
:通过字符数组创建一个新的字符串对象。
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str = new String(chars);
String(char[] value, int offset, int count)
:通过字符数组的指定部分创建一个新的字符串对象。offset
表示起始索引,count
表示要复制的字符数量。
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str = new String(chars, 1, 3); // 结果为 "ell"
String(byte[] bytes)
:通过字节数组创建一个新的字符串对象,使用默认的字符集解码字节数组。
byte[] bytes = {72, 101, 108, 108, 111};
String str = new String(bytes); // 结果为 "Hello"
String(byte[] bytes, int offset, int length)
:通过字节数组的指定部分创建一个新的字符串对象,使用默认的字符集解码字节数组。offset
表示起始索引,length
表示要复制的字节数量。
byte[] bytes = {72, 101, 108, 108, 111};
String str = new String(bytes, 1, 3); // 结果为 "ell"
- 通过直接赋值一个常量字符串创建一个新的字符串对象。
String str = "Hello";
这些是String
类的一些常用构造方法,更多的方法可见Java的在线文档。
另外需要注意的是:
String
类型是引用类型,内部储存的并不是字符串本身,在String
类的源码中,String
类实例变量的结构如下:
在String
类的内部,最主要的两个成员变量是value
数组和hash
变量。在早期的Java版本中,value
数组的类型是char[]
类型的数组,现在新版本的都是byte[]
类型的数组,其作用就是储存字符串的字符内容,数组的长度就是字符串的长度,并且有final
修饰,表明字符串对象被初始化后就不能再修改了。
而hash
是一个缓存的哈希码值,用于快速获取字符串的哈希码。哈希码是由字符串的内容计算得到的一个整数,用于在哈希表等数据结构中进行快速查找。hash
的值在字符串对象创建时被计算并缓存起来,以避免每次调用 hashCode()
方法时都重新计算。
public static void main(String[] args) {
// s1和s2引用的是不同对象 s1和s3引用的是同一对象
String s1 = new String("hello");
String s2 = new String("world");
String s3 = s1;
System.out.println(s1.length()); // 获取字符串长度---输出5
System.out.println(s1.isEmpty()); // 如果字符串长度为0,返回true,否则返回false
}
- 在Java中使用
""
引起来的字符串也是String类型对象。
比如:
public static void main(String[] args) {
System.out.println("Hello".getClass());
}
其输出结果是:class java.lang.String
,表明使用""
引起来的字符串的类型就是String
。
2.2 String 类对象之间的比较
字符串的比较是一种常见的操作,在Java中提供了四种字符串的比较方式:
- 使用
equals()
方法
equals()
方法用于比较两个字符串的内容是否相等。它是在Object
类中定义的,因此所有的Java对象都可以使用该方法。在String
类中,equals()
方法已经被重写,以便比较字符串的内容而不是引用。例如:
String str1 = "Hello";
String str2 = "World";
if (str1.equals(str2)) {
System.out.println("Strings are equal.");
} else {
System.out.println("Strings are not equal.");
}
- 使用
compareTo()
方法
compareTo()
方法用于比较两个字符串的大小关系。它是在Comparable
接口中定义的,String
类实现了Comparable
接口,因此可以使用该方法进行比较。compareTo()
方法返回一个整数值,表示字符串之间的比较结果。
- 如果返回值为负数,则表示当前字符串小于被比较字符串;
- 如果返回值为零,则表示两个字符串相等;
- 如果返回值为正数,则表示当前字符串大于被比较字符串。
例如:
String str1 = "Apple";
String str2 = "Banana";
int result = str1.compareTo(str2);
if (result < 0) {
System.out.println("str1 is less than str2.");
} else if (result > 0) {
System.out.println("str1 is greater than str2.");
} else {
System.out.println("str1 is equal to str2.");
}
- 使用
equalsIgnoreCase()
方法
equalsIgnoreCase()
方法用于比较两个字符串的内容是否相等,但忽略大小写。例如:
String str1 = "Hello";
String str2 = "hello";
if (str1.equalsIgnoreCase(str2)) {
System.out.println("Strings are equal (ignoring case).");
} else {
System.out.println("Strings are not equal.");
}
- 使用
==
运算符
==
运算符用于比较两个字符串对象的引用是否相等。它判断两个字符串对象是否指向同一个内存地址。如果两个字符串对象引用的是同一个对象,则返回true
,否则返回false
。例如:
String str1 = "Hello";
String str2 = "Hello";
if (str1 == str2) {
System.out.println("References are equal.");
} else {
System.out.println("References are not equal.");
}
需要注意的是,对于字符串的比较,通常使用equals()
方法进行内容比较,而不是使用==
运算符比较引用。因为==
运算符比较的是对象的引用,而不是内容,只有在比较字符串的引用时才会使用==
运算符。对于字符串的内容比较,应该使用equals()
方法。
2.3 字符串查找
String
类提供了多个用于字符串查找的方法。下面是一些常用的字符串查找方法:
indexOf(String str)
:返回指定子字符串在原字符串中第一次出现的索引位置。如果找不到子字符串,则返回-1。
String str = "Hello, World!";
int index = str.indexOf("World");
System.out.println("Index of 'World': " + index); // 输出:Index of 'World': 7
indexOf(String str, int fromIndex)
:从指定的起始索引位置开始,在原字符串中查找指定子字符串的第一次出现位置。
String str = "Hello, World!";
int index = str.indexOf("o", 5);
System.out.println("Index of 'o' starting from index 5: " + index); // 输出:Index of 'o' starting from index 5: 8
lastIndexOf(String str)
:返回指定子字符串在原字符串中最后一次出现的索引位置。如果找不到子字符串,则返回-1。
String str = "Hello, World!";
int lastIndex = str.lastIndexOf("o");
System.out.println("Last index of 'o': " + lastIndex); // 输出:Last index of 'o': 8
lastIndexOf(String str, int fromIndex)
:从指定的起始索引位置开始,从后往前在原字符串中查找指定子字符串的最后一次出现位置。
String str = "Hello, World!";
int lastIndex = str.lastIndexOf("o", 7);
System.out.println("Last index of 'o' starting from index 7: " + lastIndex); // 输出:Last index of 'o' starting from index 7: 4
startsWith(String prefix)
:检查原字符串是否以指定的前缀开头,返回布尔值。
String str = "Hello, World!";
boolean startsWithHello = str.startsWith("Hello");
System.out.println("Starts with 'Hello': " + startsWithHello); // 输出:Starts with 'Hello': true
endsWith(String suffix)
:检查原字符串是否以指定的后缀结尾,返回布尔值。
String str = "Hello, World!";
boolean endsWithWorld = str.endsWith("World!");
System.out.println("Ends with 'World!': " + endsWithWorld); // 输出:Ends with 'World!': true
charAt(int index)
:返回index
位置上字符,如果index
为负数或者越界,抛出IndexOutOfBoundsException
异常。
String str = "Hello, World!";
char ch = str.charAt(1);
System.out.println(ch); // 输出:e
这些方法可以用于在字符串中查找特定的子字符串、前缀或后缀、获取指定位置的字符等。它们提供了灵活的查找功能,可以帮助处理字符串中的特定内容。
2.4 字符串的转换
String
类提供了多个用于字符串转换的方法。下面是一些常用的字符串转换方法:
toCharArray()
:将字符串转换为字符数组,返回一个新的char[]
数组。
String str = "Hello";
char[] charArray = str.toCharArray();
System.out.println(charArray); // 输出:Hello
getBytes()
:将字符串转换为字节数组,使用默认的字符集编码。
String str = "Hello";
byte[] byteArray = str.getBytes();
System.out.println(Arrays.toString(byteArray)); // 输出:[72, 101, 108, 108, 111]
toLowerCase()
:将字符串中的所有字符转换为小写形式,返回一个新的字符串。
String str = "Hello";
String lowercase = str.toLowerCase();
System.out.println(lowercase); // 输出:hello
toUpperCase()
:将字符串中的所有字符转换为大写形式,返回一个新的字符串。
String str = "Hello";
String uppercase = str.toUpperCase();
System.out.println(uppercase); // 输出:HELLO
trim()
:去除字符串开头和结尾的空白字符(空格、制表符、换行符等),返回一个新的字符串。
String str = " Hello ";
String trimmed = str.trim();
System.out.println(trimmed); // 输出:Hello
valueOf()
:将其他类型的数据转换为字符串。valueOf()
是一个静态方法,可以通过类名直接调用。它接受不同类型的参数,包括基本数据类型和对象类型,返回相应类型的字符串表示。
int num = 42;
String str = String.valueOf(num);
System.out.println(str); // 输出:42
boolean flag = true;
String boolStr = String.valueOf(flag);
System.out.println(boolStr); // 输出:true
这些方法可以用于将字符串与其他数据类型进行转换,或者对字符串进行大小写转换、去除空白字符等操作。它们提供了灵活的字符串转换功能,帮助处理字符串与其他数据类型之间的转化需求。
2.5 字符串替换
String
类提供了多个用于字符串替换的方法。下面是一些常用的字符串替换方法:
replace(char oldChar, char newChar)
:将字符串中所有的指定字符oldChar
替换为新的字符newChar
,并返回替换后的新字符串。
String str = "Hello, World!";
String replaced = str.replace('o', '0');
System.out.println(replaced); // 输出:Hell0, W0rld!
replace(CharSequence target, CharSequence replacement)
:将字符串中所有的指定子字符串target
替换为新的子字符串replacement
,并返回替换后的新字符串。CharSequence
是一个接口,String
类实现了该接口,因此可以接受字符串作为参数。
String str = "Hello, World!";
String replaced = str.replace("World", "Java");
System.out.println(replaced); // 输出:Hello, Java!
replaceAll(String regex, String replacement)
:使用正则表达式regex
匹配字符串中的内容,并将匹配到的部分替换为指定的字符串replacement
,并返回替换后的新字符串。正则表达式可以用于更复杂的模式匹配和替换。
String str = "Hello, 123 World!";
String replaced = str.replaceAll("\\d", "*");
System.out.println(replaced); // 输出:Hello, *** World!
replaceFirst(String regex, String replacement)
:使用正则表达式regex
匹配字符串中的第一个匹配项,并将其替换为指定的字符串replacement
,并返回替换后的新字符串。
String str = "Hello, World!";
String replaced = str.replaceFirst("o", "O");
System.out.println(replaced); // 输出:HellO, World!
这些方法可以用于替换字符串中指定的字符、子字符串或模式匹配项,并返回替换后的新字符串。
2.6 字符串拆分
String
类提供了多个用于字符串拆分的方法。下面是一些常用的字符串拆分方法:
split(String regex)
:根据正则表达式regex
将字符串拆分成子字符串数组,并返回拆分后的结果。拆分时将字符串按照匹配正则表达式的部分进行分割。
String str = "Hello, World!";
String[] parts = str.split(", ");
System.out.println(Arrays.toString(parts)); // 输出:[Hello, World!]
split(String regex, int limit)
:根据正则表达式regex
将字符串拆分成子字符串数组,并返回拆分后的结果。拆分时将字符串按照匹配正则表达式的部分进行分割。limit
参数指定了拆分的限制,即最多拆分出多少个子字符串。如果超过限制,剩余的部分将作为最后一个元素包含在结果中。
String str = "one,two,three,four,five";
String[] parts = str.split(",", 3);
System.out.println(Arrays.toString(parts)); // 输出:[one, two, three,four,five]
这些方法可以根据指定的分隔符或正则表达式将字符串拆分成多个子字符串,并返回拆分后的结果。
2.7 字符串截取
String
类提供了多个用于字符串截取的方法。下面是一些常用的字符串截取方法:
substring(int beginIndex)
:从指定的索引位置开始截取字符串,截取从beginIndex
到字符串末尾的部分,并返回截取后的新字符串。
String str = "Hello, World!";
String substr = str.substring(7);
System.out.println(substr); // 输出:World!
substring(int beginIndex, int endIndex)
:截取字符串中从beginIndex
开始到endIndex
(不包括endIndex
)之间的部分,并返回截取后的新字符串。
String str = "Hello, World!";
String substr = str.substring(7, 12);
System.out.println(substr); // 输出:World
substring(int beginIndex, int endIndex)
:截取字符串中从beginIndex
开始到endIndex
(不包括endIndex
)之间的部分,并返回截取后的新字符串。
String str = "Hello, World!";
String substr = str.substring(7, 12);
System.out.println(substr); // 输出:World
split(String regex)
:根据正则表达式regex
将字符串拆分成子字符串数组,并返回拆分后的结果。拆分时将字符串按照匹配正则表达式的部分进行分割。
String str = "Hello, World!";
String[] parts = str.split(", ");
System.out.println(parts[0]); // 输出:Hello
System.out.println(parts[1]); // 输出:World!
这些方法可以根据指定的索引或正则表达式来截取字符串的特定部分,并返回截取后的新字符串或拆分后的子字符串数组。
2.8 字符串常量池
字符串常量池是Java中的一种特殊存储区域,用于存储字符串常量。在Java中,字符串是不可变的,即创建后不能被修改。为了提高性能和节省内存空间,Java使用了字符串常量池来共享字符串常量对象。
当我们创建一个字符串常量时(例如,使用双引号括起来的字符串字面值),Java会首先检查字符串常量池中是否存在相同值的字符串。如果存在,则返回常量池中的引用,而不会创建新的对象。这样可以避免重复创建相同内容的字符串,节省了内存空间。
例如,下面的代码中,虽然使用了两个不同的字符串对象,但它们的值相同,因此只会在字符串常量池中创建一个对象:
String str1 = "Hello";
String str2 = "Hello";
在Java中,字符串常量池位于堆内存中的特殊区域,与其他对象的存储不同。可以通过调用intern()
方法来将一个字符串对象添加到字符串常量池中,或者获取字符串常量池中相同值的字符串对象的引用。intern()
方法会返回字符串常量池中的引用,如果常量池中已经存在相同值的字符串,则直接返回该引用。
例如,下面的代码中,通过intern()
方法将字符串对象添加到字符串常量池中,并且后续的字符串对象调用equals()
方法进行比较时,将返回true
:
String str1 = new String("Hello").intern();
String str2 = new String("Hello");
System.out.println(str1 == str2); // 输出:true
需要注意的是,字符串常量池中的字符串是不可变的,一旦创建就不能修改。如果对字符串进行修改操作,将会创建新的字符串对象,而不会在常量池中进行修改。
字符串常量池在Java中起到了重要的作用,可以提高字符串的共享和重用,有助于节省内存空间,并提高字符串操作的效率。
2.9 字符串的不可变性
在Java中,String
类的对象是不可变的,也就是说一旦创建了一个字符串对象,它的值就不能被修改。这意味着对字符串对象进行操作时,实际上是创建了一个新的字符串对象,而原始的字符串对象保持不变。
这种不可变性的特性有以下几个方面的影响和好处:
-
线程安全:由于字符串是不可变的,多个线程可以同时访问和共享字符串对象,而不需要担心数据的修改和同步的问题。
-
缓存哈希值:由于字符串的值不变,可以在创建字符串对象时计算并缓存其哈希值,以便后续的哈希操作更高效。
-
安全性:字符串作为参数传递给方法时,不可变性确保方法内部无法改变传递的字符串对象,避免了潜在的安全漏洞。
-
字符串常量池:字符串常量池利用字符串的不可变性,可以实现字符串的共享,节省内存空间。
由于字符串的不可变性,对字符串进行拼接、替换、截取等操作时,实际上是创建了一个新的字符串对象,而不是在原始对象上进行修改。这样的设计决策可以避免意外的修改和数据竞争,同时提供了更可靠和可预测的行为。
需要注意的是,尽管字符串对象本身是不可变的,但可以通过引用变量重新指向新的字符串对象来实现对字符串的修改。例如:
String str = "Hello";
str = str + ", World!"; // 创建了一个新的字符串对象,并将新的引用赋值给str变量
在这个例子中,原始的字符串对象"Hello"保持不变,而str
变量指向了一个新创建的字符串对象"Hello, World!"。这也是字符串不可变性的一种表现形式。
总之,字符串的不可变性在Java中是一个重要的特性,它带来了线程安全性、安全性、哈希缓存等好处,并为字符串常量池的实现提供了基础。
三、StringBuilder 和 StringBuffer
3.1 StringBuilder
StringBuilder
是Java中提供的一个可变的字符串类,它允许对字符串进行动态修改,而不会创建多个对象。与String
类不同,StringBuilder
对象的值是可变的,可以进行插入、追加、替换、删除等操作。
以下是StringBuilder
类的一些常用方法:
append(String str)
:将指定的字符串追加到当前字符串的末尾。
StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!");
System.out.println(sb.toString()); // 输出:Hello, World!
insert(int offset, String str)
:在指定的偏移量位置插入指定的字符串。
StringBuilder sb = new StringBuilder("Hello");
sb.insert(5, ", World!");
System.out.println(sb.toString()); // 输出:Hello, World!
replace(int start, int end, String str)
:用指定的字符串替换从指定起始位置到结束位置的子字符串。
StringBuilder sb = new StringBuilder("Hello, World!");
sb.replace(7, 12, "Java");
System.out.println(sb.toString()); // 输出:Hello, Java!
delete(int start, int end)
:删除从指定起始位置到结束位置的子字符串。
StringBuilder sb = new StringBuilder("Hello, World!");
sb.delete(7, 12);
System.out.println(sb.toString()); // 输出:Hello, !
reverse()
:反转当前字符串。
StringBuilder sb = new StringBuilder("Hello");
sb.reverse();
System.out.println(sb.toString()); // 输出:olleH
StringBuilder
类提供了许多其他方法来操作和处理字符串,如查询长度、获取指定位置的字符、获取子字符串等。它的使用类似于String
,但具有可变性的特点,适合在需要频繁修改字符串的场景下使用。
需要注意的是,StringBuilder
是非线程安全的,如果在多线程环境下使用,应考虑使用线程安全的StringBuffer
类代替。
3.2 StringBuffer
StringBuffer
是Java中提供的一个可变的、线程安全的字符串类,它与StringBuilder
类似,可以进行动态修改字符串的操作。与StringBuilder
不同的是,StringBuffer
的方法是线程安全的,适用于多线程环境。
以下是StringBuffer
类的一些常用方法:
append(String str)
:将指定的字符串追加到当前字符串的末尾。
StringBuffer sb = new StringBuffer("Hello");
sb.append(", World!");
System.out.println(sb.toString()); // 输出:Hello, World!
insert(int offset, String str)
:在指定的偏移量位置插入指定的字符串。
StringBuffer sb = new StringBuffer("Hello");
sb.insert(5, ", World!");
System.out.println(sb.toString()); // 输出:Hello, World!
replace(int start, int end, String str)
:用指定的字符串替换从指定起始位置到结束位置的子字符串。
StringBuffer sb = new StringBuffer("Hello, World!");
sb.replace(7, 12, "Java");
System.out.println(sb.toString()); // 输出:Hello, Java!
delete(int start, int end)
:删除从指定起始位置到结束位置的子字符串。
StringBuffer sb = new StringBuffer("Hello, World!");
sb.delete(7, 12);
System.out.println(sb.toString()); // 输出:Hello, !
reverse()
:反转当前字符串。
StringBuffer sb = new StringBuffer("Hello");
sb.reverse();
System.out.println(sb.toString()); // 输出:olleH
StringBuffer
类提供了许多其他方法来操作和处理字符串,类似于StringBuilder
和String
。不同的是,StringBuffer
的方法是同步的,它使用了内部的同步机制来确保线程安全,因此适用于多线程环境下的操作。
由于StringBuffer
的同步性,它在性能上可能比StringBuilder
稍慢。因此,如果在单线程环境下进行字符串操作,推荐使用StringBuilder
;而在多线程环境下,或需要保证线程安全性时,应使用StringBuffer
。
3.3 String、StringBuffer 和 StringBuilder 之间的区别
在Java中,String
、StringBuffer
和StringBuilder
是用于操作字符串的三个类,它们之间有以下区别:
-
不可变性:
String
是不可变的,一旦创建就不能修改,任何对String
的操作都会创建新的字符串对象。StringBuffer
和StringBuilder
是可变的,允许对字符串进行动态修改。StringBuffer
是线程安全的,而StringBuilder
是非线程安全的。
-
线程安全性:
String
是不可变的,因此在多线程环境下是线程安全的。StringBuffer
是线程安全的,它的方法使用了同步机制来保证线程安全性。StringBuilder
是非线程安全的,它的方法没有同步机制,适用于单线程环境。
-
性能:
String
的不可变性带来了一些性能优化,例如字符串常量池和缓存的哈希值。然而,频繁的字符串拼接和修改会导致大量的临时对象创建,性能较低。StringBuffer
和StringBuilder
的可变性使得字符串的操作更高效,避免了不必要的对象创建和拷贝。由于StringBuffer
的线程安全性,它在性能上可能稍慢于StringBuilder
。
-
适用场景:
String
适用于字符串不需要修改的情况,例如常量、参数传递等。StringBuffer
适用于多线程环境或需要线程安全的场景,例如多线程共享字符串的修改。StringBuilder
适用于单线程环境,不需要线程安全的场景,例如字符串的拼接、修改等操作。
综上所述,String
适用于不需要修改的字符串,StringBuffer
适用于多线程环境或需要线程安全的字符串操作,StringBuilder
适用于单线程环境下的字符串操作。根据具体的需求和性能要求,选择适合的类来操作字符串。