Java String 对象超详细讲解,以后再也不怕别人问我String 对象问题了

一、String类的介绍

String类的定义和特点

String类是Java中提供的一个核心类,用于表示字符串的不可变序列。它属于Java标准库的一部分,定义在java.lang包中,并且是一个final类,即不可被继承。下面详细介绍String类的定义和特点:

  1. 定义:
    String类是一个引用类型(Reference Type),它用于表示由字符组成的字符串。在Java中,字符串被视为一个对象而不是基本数据类型。每个String对象实例都包含一个字符序列,该序列具有固定的长度和内容。

  2. 不可变性:
    String对象是不可变的,即一旦创建,其值就不能被改变。这意味着String对象的内容在创建后不可更改。对于任何对String对象的操作,都会返回一个新的String对象,原始对象保持不变。这种不可变性使得String对象具有线程安全性和内存安全性。

  3. 字符串常量池:
    Java中的字符串常量池(String Pool)是一种特殊的内存区域,用于存储字符串常量。通过字符串字面量创建的String对象会首先在字符串常量池中进行查找,如果存在相同值的字符串,则直接返回常量池中对应的引用。这种机制可以节约内存空间,提高字符串的重用性。

  4. 方法和操作:
    String类提供了丰富的方法和操作,用于处理字符串。常用的操作包括字符串连接、子串提取、字符查找、替换和比较等。String类还提供了对字符串长度、大小写转换、字符编码转换等常用操作的支持。

  5. 其他特点:

    • String对象是不可变的,因此在多线程环境中可以被安全地共享。
    • String类实现了Comparable接口,因此可以进行字符串的比较和排序操作。
    • String类在Java中广泛应用于各种场景,如文件处理、网络通信、数据库操作等。

需要注意的是,由于String对象的不可变性,每次对字符串进行修改时都会生成新的String对象,这可能在频繁操作大量字符串的情况下导致性能问题。为了避免这种情况,可以使用StringBuilder或StringBuffer类来进行字符串操作,并在最后转换为String对象。

创建String对象的方式

  1. 使用字符串字面量创建:
    这是最常见和简单的创建String对象的方式。通过将字符串文字放在引号中,Java编译器会自动将其转换为String对象。例如:

    String str1 = "Hello, World!";
    

    使用字符串字面量创建的String对象会首先在字符串常量池中查找是否存在相同值的字符串,如果存在,则返回常量池中的引用;如果不存在,则会创建一个新的String对象,并将其添加到字符串常量池中。

  2. 使用new关键字创建:
    通过使用new关键字和String类的构造方法,可以显式地创建一个新的String对象。例如:

    String str2 = new String("Hello");
    

    当使用这种方式创建String对象时,无论字符串常量池中是否已经存在相同值的字符串,都会创建一个新的String对象。因此,使用new关键字创建的String对象不会使用字符串常量池。

  3. 使用字符数组创建:
    还可以使用字符数组来创建String对象。可以通过传递字符数组作为参数给String类的构造方法,来创建一个包含字符数组内容的String对象。例如:

    char[] chars = {
          
          'H', 'e', 'l', 'l', 'o'};
    String str3 = new String(chars);
    

    可以传递整个字符数组或者数组的子集来创建String对象。

  4. 使用字符串拼接:
    使用字符串拼接运算符(+)可以将多个字符串拼接在一起,并创建一个新的String对象。例如:

    String str4 = "Hello" + ", " + "World!";
    

    在这种情况下,编译器会自动将字符串拼接的操作转换为使用StringBuilder或StringBuffer类来实现,最后将结果转换为String对象。

需要注意的是,使用字符串字面量创建的String对象会自动加入字符串常量池,而通过new关键字创建的String对象不会加入字符串常量池。此外,字符串常量池中的字符串对象在运行时是不可变的,而使用new关键字创建的String对象可以进行修改。

不可变性和字符串常量池的概念

  1. 不可变性(Immutability):
    String对象是不可变的,这意味着一旦创建了String对象,它的值就不能被改变。这种不可变性具体体现在以下几个方面:

    • 修改字符串:String对象的内容是固定的,不允许对其进行修改。任何对String对象的修改操作实际上都会返回一个新的String对象,而原始对象保持不变。例如:

      String str = "Hello";
      str = str + ", World!";
      

      在上面的例子中,str + ", World!"实际上创建了一个新的String对象,而原始的"Hello"字符串对象没有被改变。

    • 连接字符串:对于字符串连接操作,也是返回一个新的String对象。例如:

      String str1 = "Hello";
      String str2 = " World!";
      String result = str1 + str2;
      

      上述代码中,str1 + str2会创建一个新的String对象。

    • 替换字符:String对象的字符是无法直接替换的,需要通过字符串拼接或使用StringBuilder/StringBuffer类来实现。例如:

      String str = "Hello";
      str = str.replace('H', 'W');  // 无法直接替换,需要重新赋值
      

    不可变性的优势体现在多线程环境下,多个线程可以安全地共享String对象,而无需担心对其进行修改。此外,不可变性还有助于字符串常量池的实现。

  2. 字符串常量池(String Pool):
    字符串常量池是Java中一种特殊的内存区域,用于存储字符串常量。它具有以下几个特点:

    • 字符串常量池位于堆内存中,与普通的堆区分开。
    • 通过字符串字面量创建的String对象首先在字符串常量池中查找是否存在相同值的字符串。如果存在,则直接返回常量池中的引用,而不会创建新的对象;如果不存在,才会在常量池中创建一个新的String对象。
    • 字符串常量池的目的是节约内存空间和提高字符串的重用性。由于String对象的不可变性,可以安全地在多个String对象之间共享相同的值,从而减少了内存开销。

    使用字符串常量池可以避免在内存中重复创建相同值的String对象,提高了程序的性能和效率。这也是为什么使用字符串字面量创建String对象是最常见的方式之一。

总结起来,String对象的不可变性意味着一旦创建,其值就不能被改变。而字符串常量池是用于存储字符串常量的特殊内存区域,通过重用相同值的String对象来节约内存空间和提高性能。

二、字符串的基本操作

字符串的连接和拼接

  1. 使用"+“运算符:
    使用”+"运算符可以将多个字符串连接在一起,生成一个新的字符串。例如:

    String str1 = "Hello";
    String str2 = " World!";
    String result = str1 + str2;
    

    在这个例子中,使用"+"运算符将str1str2连接在一起,生成了一个新的字符串result,其值为"Hello World!"。这种方式非常简洁易懂,适用于少量字符串的连接。

  2. 使用concat()方法:
    concat()方法用于将指定的字符串连接到字符串的末尾。例如:

    String str1 = "Hello";
    String str2 = " World!";
    String result = str1.concat(str2);
    

    在上述例子中,str1.concat(str2)str2连接到str1的末尾,并将结果赋值给result。同样,这种方式也适用于少量字符串的连接。

  3. 使用StringBuilder或StringBuffer类:
    如果需要进行大量的字符串连接操作或者在多线程环境下进行字符串操作,推荐使用StringBuilder或StringBuffer类,它们提供了更高效和可变的字符串操作方法。例如:

    StringBuilder sb = new StringBuilder();
    sb.append("Hello");
    sb.append(" World!");
    String result = sb.toString();
    

    在这个例子中,使用StringBuilder的append()方法将多个字符串逐个添加到StringBuilder对象中,并最后通过toString()方法将StringBuilder对象转换为String对象。

    需要注意的是,StringBuilder类是非线程安全的,而StringBuffer类是线程安全的。因此,在单线程环境下,建议使用StringBuilder类执行字符串连接操作。

无论使用哪种方式进行字符串的连接和拼接,都会生成一个新的字符串对象。这是因为在Java中,String对象是不可变的,无法就地修改。因此,每次字符串连接或拼接操作都会创建一个新的String对象。

字符串的比较

在Java中,字符串的比较是常见且重要的操作,用于判断两个字符串是否相等或者确定它们的顺序。Java提供了多种方法来比较字符串:

  1. 使用equals()方法:
    equals()方法用于判断两个字符串的内容是否相同。例如:

    String str1 = "Hello";
    String str2 = "hello";
    boolean isEqual = str1.equals(str2);
    

    在上述例子中,str1.equals(str2)会返回false,因为这两个字符串的内容不完全相同。需要注意的是,equals()方法区分大小写。

  2. 使用equalsIgnoreCase()方法:
    equalsIgnoreCase()方法用于忽略字符串的大小写,判断两个字符串的内容是否相同。例如:

    String str1 = "Hello";
    String str2 = "hello";
    boolean isEqual = str1.equalsIgnoreCase(str2);
    

    在这个例子中,str1.equalsIgnoreCase(str2)将返回true,因为它忽略了大小写。

  3. 使用compareTo()方法:
    compareTo()方法用于按照字典顺序比较字符串。它返回一个整数,表示两个字符串之间的关系。具体规则如下:

    • 如果字符串相等,返回0。
    • 如果当前字符串小于参数字符串,返回一个负数。
    • 如果当前字符串大于参数字符串,返回一个正数。

    例如:

    String str1 = "apple";
    String str2 = "banana";
    int result = str1.compareTo(str2);
    

    在上述例子中,str1.compareTo(str2)将返回一个负数,表示str1在字典顺序中位于str2之前。

  4. 使用compareToIgnoreCase()方法:
    compareToIgnoreCase()方法与compareTo()方法类似,但忽略字符串的大小写。例如:

    String str1 = "Apple";
    String str2 = "banana";
    int result = str1.compareToIgnoreCase(str2);
    

    在这个例子中,str1.compareToIgnoreCase(str2)将返回一个负数,表示str1在字典顺序中位于str2之前(忽略大小写)。

需要注意的是,字符串的比较方法都是基于Unicode值进行的。此外,可以使用==运算符比较两个字符串的引用是否相等,但它不比较字符串的内容,仅判断两个字符串对象是否指向同一块内存地址。

字符串的提取和截取

在Java中,可以使用不同的方法对字符串进行提取和截取操作。这些操作允许您从原始字符串中选择特定部分并创建新的字符串。

  1. 使用substring()方法:
    substring()方法用于从原始字符串中提取子字符串。它接受一个或两个参数,第一个参数指定子字符串的起始索引(包括),第二个参数(可选)指定子字符串的结束索引(不包括)。例如:

    String str = "Hello World";
    String substring1 = str.substring(6); // 从索引6开始截取到字符串末尾
    String substring2 = str.substring(0, 5); // 从索引0开始截取到索引5(不包括)
    

    在上述例子中,substring1的值为"World",而substring2的值为"Hello"

  2. 使用split()方法:
    split()方法用于将字符串按照指定的分隔符切分成多个子字符串,并返回一个字符串数组。例如:

    String str = "apple,banana,orange";
    String[] substrings = str.split(",");
    

    在这个例子中,split(",")将字符串str按照逗号分隔符切分成三个子字符串,存储在字符串数组substrings中。即substrings[0]"apple"substrings[1]"banana"substrings[2]"orange"

  3. 使用charAt()方法:
    charAt()方法用于返回字符串中指定索引位置的字符。索引从0开始。例如:

    String str = "Hello";
    char ch = str.charAt(1); // 获取索引为1的字符,即"e"
    

    在上述例子中,ch的值为'e'

  4. 使用substring()方法和indexOf()方法:
    通过结合使用substring()方法和indexOf()方法,可以提取字符串中的特定部分。例如:

    String str = "The quick brown fox";
    int startIndex = str.indexOf("quick"); // 获取子字符串"quick"的起始索引
    int endIndex = str.indexOf("fox"); // 获取子字符串"fox"的起始索引
    String result = str.substring(startIndex, endIndex);
    

    在这个例子中,startIndex的值为4,endIndex的值为16,因此result的值为"quick brown"

需要注意的是,字符串的提取和截取操作都会生成一个新的字符串对象。

字符串的查找和替换

在Java中,可以使用不同的方法来进行字符串的查找和替换操作。这些操作使您能够在字符串中查找特定的字符或子字符串,并将其替换为新的字符或字符串。

  1. 使用indexOf()方法:
    indexOf()方法用于查找指定字符或子字符串在原始字符串中第一次出现的位置索引。如果找到匹配,返回第一个匹配的索引;如果没有找到匹配,返回-1。例如:

    String str = "Hello World";
    int index = str.indexOf("o"); // 查找字符"o"在字符串中的位置
    

    在上述例子中,index的值为4,因为第一个字符"o"在索引4处。

  2. 使用lastIndexOf()方法:
    lastIndexOf()方法与indexOf()方法类似,但是它从字符串的末尾开始查找指定字符或子字符串最后一次出现的位置索引。例如:

    String str = "Hello World";
    int lastIndex = str.lastIndexOf("o"); // 查找字符"o"在字符串中最后一次出现的位置
    

    在这个例子中,lastIndex的值为7,因为最后一个字符"o"在索引7处。

  3. 使用contains()方法:
    contains()方法用于检查字符串中是否包含指定的字符或子字符串。返回值为布尔类型。例如:

    String str = "Hello World";
    boolean contains = str.contains("World"); // 检查字符串中是否包含子字符串"World"
    

    在上述例子中,contains的值为true,因为字符串包含子字符串"World"。

  4. 使用replace()方法:
    replace()方法用于将指定字符或子字符串替换为新的字符或字符串。例如:

    String str = "Hello World";
    String newStr = str.replace("World", "Universe"); // 将字符串中的"World"替换为"Universe"
    

    在这个例子中,newStr的值为"Hello Universe"

  5. 使用replaceAll()方法:
    replaceAll()方法与replace()方法类似,但它使用正则表达式来进行模式匹配和替换。例如:

    String str = "Hello123World456";
    String newStr = str.replaceAll("\\d+", ""); // 用空字符串替换所有的数字
    

    在这个例子中,newStr的值为"HelloWorld",因为所有数字都被替换为空字符串。

需要注意的是,字符串的查找和替换操作都会生成一个新的字符串对象,原始字符串本身不会受到改变。

三、字符串的常用方法

获取字符串的长度

在Java中,可以使用length()方法来获取字符串的长度。这个方法返回字符串中字符的数量(包括空格和特殊字符)。

下面是一个简单的示例:

String str = "Hello World!";
int length = str.length();

在这个例子中,length()方法被调用并将返回值存储在整型变量length中。最终,length的值为12,因为字符串"Hello World!"由12个字符组成。

需要注意的是,length()方法是字符串对象的方法,因此需要在字符串变量后使用点操作符来调用方法。这意味着您必须先创建一个字符串对象,然后才能调用length()方法。

此外,length()方法还适用于空字符串,它将返回0:

String emptyStr = "";
int emptyLength = emptyStr.length(); // 结果为0

对于包含Unicode字符的字符串,length()方法将返回代码单元数量,而不是Unicode字符数量。Java使用UTF-16编码表示字符串,其中某些字符需要使用多个代码单元表示。因此,如果字符串包含了这样的字符,那么返回的长度可能会大于Unicode字符的实际数量。

转换大小写

在Java中,可以使用toUpperCase()toLowerCase()方法来将字符串转换为大写或小写。

  1. toUpperCase()方法:该方法将字符串中的所有字符转换为大写字母,并返回一个新的字符串。例如:
String str = "Hello World!";
String upperCaseStr = str.toUpperCase();

在这个例子中,toUpperCase()方法被调用,并将返回的大写字符串存储在upperCaseStr变量中。最终,upperCaseStr的值为"HELLO WORLD!"。

  1. toLowerCase()方法:该方法将字符串中的所有字符转换为小写字母,并返回一个新的字符串。例如:
String str = "Hello World!";
String lowerCaseStr = str.toLowerCase();

在这个例子中,toLowerCase()方法被调用,并将返回的小写字符串存储在lowerCaseStr变量中。最终,lowerCaseStr的值为"hello world!"。

需要注意的是,这两个方法都会生成一个新的字符串对象,原始字符串本身不会受到改变。

另外,这两个方法只能将字母字符转换为大写或小写,对于非字母字符(如数字、特殊字符等),它们将保持不变。

下面是一个示例,演示如何在字符串中仅针对字母进行大小写转换:

String str = "Hello 123 World!";
String convertedStr = "";
for (int i = 0; i < str.length(); i++) {
    
    
    char c = str.charAt(i);
    if (Character.isLetter(c)) {
    
    
        if (Character.isUpperCase(c)) {
    
    
            convertedStr += Character.toLowerCase(c);
        } else {
    
    
            convertedStr += Character.toUpperCase(c);
        }
    } else {
    
    
        convertedStr += c;
    }
}

在这个示例中,我们遍历字符串中的每个字符,并使用Character.isLetter()方法来检查字符是否为字母。如果是字母,则使用Character.isUpperCase()方法来判断字符的大小写,并进行相应的转换。对于非字母字符,我们将其保持不变。

上述代码的输出将是"hELLO 123 wORLD!",其中字母的大小写被反转了。

去除首尾空白字符

在Java中,可以使用trim()方法来去除字符串的首尾空白字符。这个方法会返回一个新的字符串,其中删除了原始字符串开头和结尾的空格。

下面是一个简单的示例:

String str = "   Hello World!   ";
String trimmedStr = str.trim();

在这个例子中,trim()方法被调用,并将返回的去除空格后的字符串存储在trimmedStr变量中。最终,trimmedStr的值为"Hello World!"。

需要注意的是,trim()方法只能去除字符串开头和结尾的空格,而不能去除字符串中间的空格。例如,对于字符串" Hello World! “,trim()方法只会去除开头和结尾的空格,返回"Hello World!”。

此外,trim()方法还可以去除其他类型的空白字符,例如制表符、换行符等。

如果您想要去除字符串中间的空格,可以使用replaceAll()方法来替换所有空格字符:

String str = "   Hello    World!   ";
String noSpaceStr = str.replaceAll("\\s+", "");

在这个例子中,replaceAll()方法被调用,并使用正则表达式"\s+“来匹配一个或多个连续的空白字符,然后用空字符串替换它们。最终,noSpaceStr的值为"HelloWorld!”。

需要注意的是,replaceAll()方法也会返回一个新的字符串,原始字符串本身不会受到改变。

分割字符串

在Java中,可以使用split()方法将字符串分割成子字符串。split()方法基于给定的分隔符将字符串拆分为一个字符串数组。

下面是一个简单的示例:

String str = "Hello,World,Java";
String[] parts = str.split(",");

在这个例子中,split()方法被调用,并使用逗号作为分隔符将字符串str拆分为多个子字符串。拆分后的结果存储在parts数组中。最终,parts数组的值为["Hello", "World", "Java"]

可以看到,split()方法返回一个字符串数组,其中的每个元素都是原始字符串根据分隔符拆分后的子字符串。

需要注意的是,分隔符可以是一个字符串,也可以是一个正则表达式。如果分隔符是一个正则表达式,需要使用双反斜杠进行转义。

另外,如果要限制拆分后的子字符串数量,可以使用带有第二个参数的split()方法。例如,下面的示例将字符串str拆分为最多两个子字符串:

String str = "Hello,World,Java";
String[] parts = str.split(",", 2);

在这个例子中,split()方法的第二个参数为2,表示最多拆分为2个子字符串。拆分后的结果存储在parts数组中。最终,parts数组的值为["Hello", "World,Java"]

如果字符串中存在连续的分隔符,split()方法将返回空字符串作为相邻分隔符之间的子字符串。如果要去除这些空字符串,可以结合使用split()trim()方法:

String str = "Hello,,World,Java";
String[] parts = str.split(",", -1);
for (int i = 0; i < parts.length; i++) {
    
    
    parts[i] = parts[i].trim();
}

在这个例子中,split()方法被调用,并指定第二个参数为-1,表示保留所有连续的分隔符。然后,使用循环和trim()方法去除每个子字符串的首尾空白字符。

字符串的格式化

在Java中,可以使用字符串的格式化来创建具有特定格式的字符串。字符串的格式化是通过使用格式化模式和参数替换来完成的。

Java中的格式化字符串主要依赖于String.format()方法和System.out.printf()方法。这两种方法都支持使用特殊的占位符和转换字符来实现字符串格式化。

下面是一个简单的示例:

String name = "Alice";
int age = 25;
double weight = 55.5;

String message = String.format("My name is %s, I'm %d years old, and my weight is %.2f kg.", name, age, weight);
System.out.println(message);

在这个例子中,使用了String.format()方法来创建一个格式化字符串message。其中,%s%d%.2f是占位符,分别代表一个字符串、一个整数和一个带有两位小数的浮点数。这些占位符会被后面的参数依次替换,形成最终的格式化字符串。

执行上述代码,输出结果为:“My name is Alice, I’m 25 years old, and my weight is 55.50 kg.”。

除了String.format()方法,还可以使用System.out.printf()方法来直接将格式化字符串输出到控制台:

String name = "Alice";
int age = 25;
double weight = 55.5;

System.out.printf("My name is %s, I'm %d years old, and my weight is %.2f kg.", name, age, weight);

执行上述代码,同样会输出:“My name is Alice, I’m 25 years old, and my weight is 55.50 kg.”。

除了常规的占位符,还可以使用转换字符来指定特殊格式,例如日期、时间等。下面是一个示例:

import java.time.LocalDateTime;

LocalDateTime now = LocalDateTime.now();

System.out.printf("Current date and time: %tF %tT", now, now);

在这个例子中,使用%tF%tT转换字符分别表示日期和时间的格式。now变量是一个LocalDateTime对象,包含当前的日期和时间信息。执行上述代码,会输出当前的日期和时间,例如:“Current date and time: 2023-06-29 14:30:00”。

需要注意的是,在格式化字符串中,还可以使用其他修饰符来调整输出的格式,例如宽度、精度、对齐方式等。

四、字符串的不可变性和性能问题

字符串的不可变性对程序的影响

  1. 线程安全性:由于字符串是不可变的,它们在多线程环境下是线程安全的。多个线程可以同时访问和共享相同的字符串对象,而不用担心数据被修改导致的并发问题。这使得字符串在并发编程中更加可靠和容易使用。

  2. 缓存哈希值:由于字符串不可变,其哈希值可以被缓存起来。在Java中,字符串的哈希值在第一次计算后会被缓存,以提高字符串在哈希集合、哈希映射等数据结构中的查找效率。如果字符串是可变的,那么每次计算哈希值都需要遍历字符串的字符数组,影响了哈希相关操作的性能。

  3. 方法传递安全:由于字符串是不可变的,它们可以被安全地作为方法参数进行传递。在方法内部,如果对字符串进行修改,实际上是创建了一个新的字符串对象,而不会影响到原始的字符串对象。这样可以避免在方法之间共享数据时出现意外的修改,增加了程序的可靠性。

  4. 安全性和可靠性:字符串的不可变性保证了程序中的字符串数据不会被意外修改。这对于安全性和可靠性非常重要,特别是在需要保护敏感信息(如密码)或进行数据操作时。

  5. 性能优化:尽管字符串的不可变性可能会带来一些性能问题(例如在大量拼接字符串时),但它也带来了一些性能优化的机会。由于字符串是不可变的,可以将多个字符串共享相同的内存空间,从而节省内存。此外,字符串的不可变性还使得字符串对象可以被缓存、重用,减少了频繁创建对象的开销,提高了程序的性能。

需要注意的是,虽然字符串不可变,但在Java的String类中,实际上通过使用char[]数组来存储字符串的字符。当对字符串执行修改操作(例如调用substring()方法)时,实际上会创建一个新的String对象,该对象引用相同的char[]数组,但索引的范围可能不同。这种设计使得在不修改原始String对象的情况下对字符串进行操作,进一步提高了程序的可靠性和安全性。

字符串拼接的效率问题

在Java中,字符串拼接操作可以通过多种方式实现,如使用"+"运算符、concat()方法、StringBuilder类或StringBuffer类等。

然而,由于字符串的不可变性,每次进行字符串拼接操作都会创建一个新的字符串对象。这可能导致以下效率问题:

  1. 内存开销:每次字符串拼接操作都涉及到创建新的字符串对象,这意味着需要分配额外的内存空间来存储新的字符串。如果执行大量的字符串拼接操作,将会产生大量的临时对象,增加了内存的开销。

  2. 性能损耗:字符串的不可变性导致每次拼接操作都要复制前面的字符串内容,并创建一个新的字符串对象。这种操作在频繁拼接大量字符串时会带来性能损耗,尤其是使用"+“运算符进行拼接时,因为每个”+"运算符都会触发一次字符串对象的复制和创建。

为了解决字符串拼接的效率问题,可以使用StringBuilder类或StringBuffer类。它们是可变的字符串类,提供了高效的字符串拼接操作。以下是它们的特点:

  • StringBuilder:非线程安全的可变字符串类,在单线程环境下使用。
  • StringBuffer:线程安全的可变字符串类,在多线程环境下使用。

这些类提供了一系列的方法,如append()insert()等,用于在原字符串基础上进行追加或插入操作,而不用创建新的字符串对象。这样可以避免频繁的对象创建和字符串复制,提高了拼接操作的效率。

以下是示例代码,演示了使用StringBuilder进行字符串拼接的方式:

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World!");

String result = sb.toString();

在上述示例中,StringBuilder对象sb通过多次调用append()方法来追加字符串内容。最后通过调用toString()方法将StringBuilder对象转换为不可变的String对象。

StringBuilder和StringBuffer的使用

StringBuilderStringBuffer是Java中用于高效处理可变字符串的类。它们提供了一系列方法,用于在原字符串基础上进行追加、插入、删除和替换等操作,而不会创建新的字符串对象。

  1. 创建对象:

    • 使用空参构造函数:StringBuilder sb = new StringBuilder();
    • 使用指定初始容量的构造函数:StringBuilder sb = new StringBuilder(int capacity);

    StringBuffer的用法与StringBuilder类似,只是它是线程安全的,适用于多线程环境。

  2. 基本操作:

    • 追加字符串:append(String str),用于在当前字符串后面追加指定字符串。
    • 插入字符串:insert(int offset, String str),在指定位置插入指定字符串。
    • 删除字符或字符串:delete(int start, int end),删除指定范围内的字符;deleteCharAt(int index),删除指定索引位置的字符。
    • 替换字符或字符串:replace(int start, int end, String str),用指定字符串替换指定范围内的字符。
  3. 链式调用:
    StringBuilderStringBuffer的方法都返回自身对象,因此可以通过链式调用来进行多个操作。例如:

    StringBuilder sb = new StringBuilder();
    sb.append("Hello").append(" ").append("World!");
    
  4. 转换为String:

    • 使用toString()方法将StringBuilderStringBuffer对象转换为不可变的String对象。
  5. 线程安全性:

    • StringBuilder是非线程安全的,适用于单线程环境下。
    • StringBuffer是线程安全的,适用于多线程环境下。

由于StringBuilderStringBuffer都是可变的字符串类,它们的效率通常高于使用"+"运算符进行字符串拼接。当需要频繁进行字符串操作或在多线程环境下使用时,建议使用StringBuilderStringBuffer

需要注意的是,尽管StringBuilderStringBuffer提供了高效的字符串操作,但在单线程环境下,推荐使用StringBuilder,因为它比StringBuffer稍微更轻量级。只有在多线程环境下才需要考虑使用StringBuffer的线程安全性。

五、字符串与正则表达式

正则表达式概述

正则表达式是一种强大的模式匹配工具,用于在文本中搜索、匹配和替换符合特定模式的字符串。它提供了一种灵活且高效的方式来处理字符串,并具有广泛的应用场景,包括文本处理、数据验证、数据提取等。

正则表达式由字符和特殊字符组成,用来描述字符串的模式。

  1. 普通字符:任何非特殊字符都表示它本身。例如,正则表达式abc将匹配字符串中的"abc"。

  2. 元字符:具有特殊含义的字符,用于构建更复杂的模式。

    • .:匹配任意单个字符,除了换行符。
    • *:匹配前面的元素零次或多次。
    • +:匹配前面的元素一次或多次。
    • ?:匹配前面的元素零次或一次。
    • ^:匹配字符串的开始位置。
    • $:匹配字符串的结束位置。
    • \:转义字符,用于将后面的特殊字符视为普通字符。例如,\.匹配句点字符"."。
    • []:字符类,匹配方括号中的任意一个字符。例如,[aeiou]匹配任意一个元音字母。
    • [^]:否定字符类,匹配除了方括号中的字符以外的任意一个字符。例如,[^0-9]匹配任意一个非数字字符。
    • ():分组,将其中的模式视为一个整体。
    • |:逻辑或,匹配两个模式中的任意一个。
  3. 量词:用于指定元素出现的次数。

    • {n}:精确匹配前面的元素出现 n 次。
    • {n,}:匹配前面的元素至少出现 n 次。
    • {n,m}:匹配前面的元素至少出现 n 次,最多出现 m 次。
    • ?*+{n,}{n,m}之后加上?,表示非贪婪匹配,尽可能少地匹配。
  4. 预定义字符类:一些常用字符类的预定义简写形式。

    • \d:数字字符,相当于[0-9]
    • \D:非数字字符,相当于[^0-9]
    • \w:单词字符,相当于[a-zA-Z0-9_]
    • \W:非单词字符,相当于[^a-zA-Z0-9_]
    • \s:空白字符,包括空格、制表符、换行符等。
    • \S:非空白字符。
  5. 贪婪与非贪婪匹配:默认情况下,正则表达式会尽可能多地匹配,也就是贪婪匹配。通过在量词后加上 ? 可以实现非贪婪匹配,尽可能少地匹配。

使用正则表达式的一般步骤如下:

  1. 定义正则表达式模式。
  2. 使用相应的方法进行匹配、搜索或替换操作,常见的方法有:
    • matches(String regex, CharSequence input):判断整个字符串是否匹配模式。
    • find():查找并返回下一个匹配的子串。
    • replaceAll(String regex, String replacement):用指定字符串替换所有匹配的子串。
    • split(String regex):根据正则表达式拆分字符串。

Java中使用正则表达式可以使用java.util.regex包下的相关类和方法,主要包括PatternMatcher两个类。其中,Pattern用于编译正则表达式,Matcher用于进行匹配操作。

下面是一个简单的Java示例代码,演示了使用正则表达式匹配和替换字符串的过程:

import java.util.regex.*;

public class RegexExample {
    
    
    public static void main(String[] args) {
    
    
        String text = "The quick brown fox jumps over the lazy dog.";

        // 匹配包含 "fox" 的单词
        Pattern pattern = Pattern.compile("\\bfox\\b");
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
    
    
            System.out.println("Found match at index " + matcher.start());
        }

        // 替换所有的元音字母为 "*"
        String replacedText = text.replaceAll("[aeiou]", "*");
        System.out.println(replacedText);
    }
}

以上代码通过正则表达式匹配并输出包含 “fox” 的单词,并将文本中的元音字母替换为 “*”。

字符串与正则表达式的匹配和替换

字符串与正则表达式的匹配和替换是一种常见且非常有用的操作。通过使用正则表达式,我们可以在文本中查找、匹配和替换符合特定模式的字符串。

匹配操作是指在给定的字符串中查找满足特定模式的子串。Java提供了java.util.regex包,其中的PatternMatcher类可以用于进行匹配操作。

  1. Pattern类:用于编译正则表达式并创建Pattern对象。它提供了多个静态方法来编译正则表达式,并且还可以设置一些选项来调整匹配行为。例如,Pattern.compile(String regex)方法将正则表达式编译为Pattern对象。

  2. Matcher类:用于在给定的字符串中执行匹配操作。通过调用Pattern.matcher(CharSequence input)方法,我们可以获取一个Matcher对象。接下来,可以使用Matcher类的各种方法来执行匹配操作:

    • matches():判断整个字符串是否匹配正则表达式。
    • find():尝试在字符串中查找下一个匹配的子串。
    • start():返回当前匹配的子串的起始索引。
    • end():返回当前匹配的子串的结束索引。
    • group():返回当前匹配的子串。

下面是一个简单的示例代码,演示了如何使用正则表达式进行匹配操作:

import java.util.regex.*;

public class RegexMatchingExample {
    
    
    public static void main(String[] args) {
    
    
        String text = "The quick brown fox jumps over the lazy dog.";

        // 匹配包含 "fox" 的单词
        Pattern pattern = Pattern.compile("\\bfox\\b");
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
    
    
            System.out.println("Found match at index " + matcher.start());
        }
    }
}

以上代码使用正则表达式"\bfox\b"来匹配文本中包含 “fox” 的单词。通过调用Matcher.find()方法,我们可以找到每个匹配的位置,并输出其起始索引。

替换操作是指将匹配到的子串替换为新的字符串。Java中使用正则表达式进行替换操作有两种常见的方法:

  1. String.replaceAll(String regex, String replacement):该方法使用正则表达式在给定字符串中查找并替换所有匹配的子串。其中,regex参数指定正则表达式,replacement参数指定替换的字符串。

  2. Matcher.replaceAll(String replacement):该方法在Matcher对象的当前字符串中查找并替换所有匹配的子串。同样,replacement参数指定替换的字符串。

下面是一个示例代码,演示了如何使用正则表达式进行替换操作:

import java.util.regex.*;

public class RegexReplacementExample {
    
    
    public static void main(String[] args) {
    
    
        String text = "The quick brown fox jumps over the lazy dog.";

        // 将文本中的元音字母替换为 "*"
        String replacedText = text.replaceAll("[aeiou]", "*");
        System.out.println(replacedText);
    }
}

以上代码使用正则表达式"[aeiou]"来匹配文本中的元音字母,并将其替换为 “*”。通过调用String.replaceAll()方法,我们可以得到替换后的字符串并输出。

六、字符串的编码与解码

字符编码和字符集的概念

当我们处理文本时,字符编码和字符集是两个重要的概念。它们用于表示和处理字符和文本的方式。

字符集(Character Set)是一个字符的集合,每个字符都有一个唯一的编号。常见的字符集有ASCII、Unicode等。字符集定义了字符与数字之间的映射关系。

ASCII(American Standard Code for Information Interchange)是最早且最常用的字符集之一。它使用7位二进制数(0-127)来表示128个字符,包括英文字母、数字和一些标点符号。ASCII字符集是英语及其它西方国家所使用的字符集。

随着计算机技术的发展,出现了更多需要表示全球各种语言字符的需求。Unicode应运而生。Unicode是一个国际标准字符集,它为世界上几乎所有的字符提供了唯一的编号。Unicode使用多种编码方案来表示字符,其中最常用的是UTF-8和UTF-16。

UTF-8(Unicode Transformation Format-8)是一种可变长度的编码方案,它使用8位二进制数(0-255)来表示Unicode字符。UTF-8可以表示ASCII字符,同时也可以表示其他Unicode字符,因此它是向后兼容ASCII的。

UTF-16(Unicode Transformation Format-16)是一种固定长度的编码方案,它使用16位二进制数(0-65535)来表示Unicode字符。UTF-16适用于表示大部分的Unicode字符,但相对于UTF-8来说,它会占用更多的存储空间。

字符编码(Character Encoding)是将字符集中的字符转换为计算机可识别的二进制形式的规则。它使用一个编码表来表示字符和数字之间的映射关系。

在Java中,字符串的默认编码是UTF-16。当我们在Java程序中处理文本时,通常会使用String类来表示字符串,而该类使用UTF-16编码来存储和处理字符串数据。

在实际开发中,我们需要注意字符编码的正确使用和转换。如果在不同的环境中处理文本数据,可以使用getBytes()方法将字符串转换为指定的字节数组,或使用new String(byte[], charset)将字节数组转换为指定编码的字符串。

常见的字符编码方式

常见的字符编码方式有ASCII、UTF-8、UTF-16和ISO-8859-1等。

  1. ASCII(American Standard Code for Information Interchange):

    • 编码范围:使用7位二进制数表示,包括0-127个字符。
    • 特点:ASCII是最早和最常用的字符编码方式,主要用于英语及其它西方国家的字符表示。它只能表示基本的拉丁字母、数字和一些标点符号,不支持非拉丁字符和特殊字符。
    • 使用场景:适用于英语、数字和基本标点符号的表示。
  2. UTF-8(Unicode Transformation Format-8):

    • 编码范围:可变长度编码,使用8位二进制数表示Unicode字符。
    • 特点:UTF-8是Unicode标准的一种编码方式,它能够表示几乎所有的Unicode字符。对于ASCII字符,UTF-8与ASCII编码兼容。它在表示非拉丁字符时比UTF-16更节省存储空间,因为它采用可变长度编码,根据字符的不同而分配不同长度的字节。
    • 使用场景:通用的字符编码方式,适用于几乎所有的文本数据,尤其适合互联网传输和存储。
  3. UTF-16(Unicode Transformation Format-16):

    • 编码范围:固定长度编码,使用16位二进制数表示Unicode字符。
    • 特点:UTF-16同样是Unicode标准的一种编码方式,它能够表示几乎所有的Unicode字符。相对于UTF-8,UTF-16在表示非拉丁字符时会占用更多的存储空间,因为它使用固定长度的16位编码。
    • 使用场景:适用于需要固定长度编码和随机访问字符位置的场景,比如某些系统内部字符串处理和特定应用程序。
  4. ISO-8859-1(Latin-1):

    • 编码范围:使用8位二进制数表示,包括0-255个字符。
    • 特点:ISO-8859-1是国际标准化组织定义的一种字符编码方式,它是ASCII编码的扩展,能够表示更多的西欧字符。然而,ISO-8859-1只能表示一些欧洲语言字符,对于世界上其他大部分语言的字符不支持。
    • 使用场景:主要用于西欧语言文本的表示,不适用于多语言环境或全球化的应用场景。

除了上述常见的字符编码方式外,还有一些其他的编码方式,例如GBK、GB2312、Big5等,它们主要用于中文和东亚语言的字符表示。

在实际开发过程中,需要根据具体的需求和环境选择合适的字符编码方式来处理和存储文本数据。应确保使用的编码方式能够覆盖所需的字符范围,并注意字符编码的正确转换和处理,避免出现乱码或字符丢失的问题。

字符串的编码和解码方法介绍

  1. 字符串编码为字节序列:

    • 使用getBytes()方法:该方法可以将字符串按照指定的字符集编码为字节数组。例如,使用UTF-8编码方式将字符串编码为字节数组:byte[] bytes = str.getBytes("UTF-8");
    • 使用字符串编码转换类:Java提供了Charset和CharsetEncoder类来进行字符串编码转换。示例代码如下:
      Charset charset = Charset.forName("UTF-8");
      CharsetEncoder encoder = charset.newEncoder();
      ByteBuffer buffer = encoder.encode(CharBuffer.wrap(str));
      byte[] bytes = buffer.array();
      
  2. 字节序列解码为字符串:

    • 使用构造函数:可以使用指定的字符集创建字符串对象并进行解码。例如,使用UTF-8字符集将字节数组解码为字符串:String str = new String(bytes, "UTF-8");
    • 使用字符串编码转换类:Java提供了Charset和CharsetDecoder类来进行字符串解码转换。示例代码如下:
      Charset charset = Charset.forName("UTF-8");
      CharsetDecoder decoder = charset.newDecoder();
      CharBuffer buffer = decoder.decode(ByteBuffer.wrap(bytes));
      String str = buffer.toString();
      
  3. URL编码和解码:

    • 使用URLEncoder类进行URL编码:可以使用URLEncoder类对字符串进行URL编码,将特殊字符转换为URL安全的形式。示例代码如下:
      String encodedStr = URLEncoder.encode(str, "UTF-8");
      
    • 使用URLDecoder类进行URL解码:可以使用URLDecoder类对URL编码的字符串进行解码,还原为原始字符串。示例代码如下:
      String decodedStr = URLDecoder.decode(encodedStr, "UTF-8");
      
  4. Base64编码和解码:

    • 使用Base64类进行Base64编码:Java提供了Base64类,可以对字节数组进行Base64编码,得到Base64字符串表示。示例代码如下:
      byte[] encodedBytes = Base64.getEncoder().encode(bytes);
      String base64Str = new String(encodedBytes, StandardCharsets.UTF_8);
      
    • 使用Base64类进行Base64解码:可以使用Base64类对Base64编码的字符串进行解码,还原为原始的字节数组。示例代码如下:
      byte[] decodedBytes = Base64.getDecoder().decode(base64Str);
      

七、字符串与其他数据类型的转换

字符串与基本数据类型的转换

  1. 字符串转基本数据类型:

    • 使用包装类的静态方法:每个基本数据类型都有对应的包装类,比如IntegerDouble等。这些包装类提供了用于将字符串转换为对应类型的静态方法,如Integer.parseInt()Double.parseDouble()等。例如:
      String str = "123";
      int num = Integer.parseInt(str);
      double decimal = Double.parseDouble(str);
      
    • 使用valueOf方法:所有的包装类都提供了valueOf方法,可以将相应的字符串转换成对应类型的包装类对象。然后可以通过自动拆箱将包装类对象转换为基本数据类型。例如:
      String str = "123";
      Integer integer = Integer.valueOf(str);
      int num = integer.intValue();
      
  2. 基本数据类型转字符串:

    • 使用字符串连接符:可以使用字符串连接符(+)将基本数据类型转换为字符串。例如:
      int num = 123;
      String str = "" + num;
      
    • 使用String类的valueOf方法:所有的包装类和基本数据类型都实现了toString()方法,可以使用String.valueOf()或直接调用对象的toString()方法将基本数据类型转换为字符串。例如:
      int num = 123;
      String str = String.valueOf(num);
      

需要注意的是,当字符串无法正确转换为对应类型时,会抛出NumberFormatException异常。因此,在进行字符串到基本数据类型的转换时,需要确保字符串的格式正确。

另外,Java 8及更高版本中引入了基于流的新方法,可以更方便地处理字符串与基本数据类型之间的转换。例如,Integer类提供了parseInt()valueOf()方法的重载版本,允许在转换时指定基数(进制)。这些新方法提供了更灵活的转换方式。

字符串与日期时间类型的转换

  1. 字符串转日期时间类型:

    • 使用SimpleDateFormat类:SimpleDateFormat类是用于格式化和解析日期和时间的类。可以使用它的parse()方法将字符串解析为日期类型。例如:
      String str = "2023-06-29 12:34:56";
      SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      Date date = format.parse(str);
      
    • 使用DateTimeFormatter类(Java 8及更高版本):DateTimeFormatter类是Java 8引入的日期时间处理类。可以使用它的parse()方法将字符串解析为日期时间类型。例如:
      String str = "2023-06-29 12:34:56";
      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
      
  2. 日期时间类型转字符串:

    • 使用SimpleDateFormat类:可以使用SimpleDateFormat类的format()方法将日期类型格式化为字符串。例如:
      Date date = new Date();
      SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      String str = format.format(date);
      
    • 使用DateTimeFormatter类(Java 8及更高版本):可以使用DateTimeFormatter类的format()方法将日期时间类型格式化为字符串。例如:
      LocalDateTime dateTime = LocalDateTime.now();
      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      String str = dateTime.format(formatter);
      

需要注意的是,使用SimpleDateFormatDateTimeFormatter类时,需要使用正确的日期时间格式模式(pattern)来指定字符串的格式。例如,yyyy表示年份,MM表示月份,dd表示日期,HH表示小时(24小时制),mm表示分钟,ss表示秒等。

另外,Java 8引入了java.time包,提供了更加强大和灵活的日期时间处理类。除了LocalDateTime外,还有LocalDateLocalTimeZonedDateTime等类可用于处理不同粒度的日期时间。这些新的日期时间类也提供了与字符串之间的转换方法,使用方式类似于上述示例。

八、字符串的国际化和本地化

Java平台的国际化支持

ava平台提供了强大的国际化(Internationalization,简称i18n)支持,使得开发者能够轻松地编写具备多语言和地区特性的应用程序。

  1. 本地化(Localization):

    • Locale类:Locale类表示特定的语言和地区。它可以用来标识不同的语言、国家/地区和文化。通过Locale类,可以指定要使用的语言或地区,从而确保应用程序在不同的环境中展示正确的本地化内容。
    • 资源包(Resource Bundle):资源包是一种包含本地化信息的集合。每个语言或地区都可以有对应的资源包,其中包含了与该语言或地区相关的文本、图像、格式等信息。Java中的资源包通常以.properties文件存储,每个文件对应一个语言或地区的资源内容。
  2. 字符编码和文本处理:

    • Charset类:Charset类表示字符编码集。Java中使用Unicode字符集作为内部编码,但外部数据和文件可能使用不同的字符编码。通过Charset类,可以进行字符集的转换和处理,确保在不同字符编码间正确地转换和处理文本内容。
    • String类和MessageFormat类:Java的String类提供了许多用于本地化文本处理的方法,如字符串连接和格式化。MessageFormat类则提供了更高级的字符串格式化功能,能够根据指定的语言和地区进行复杂的消息格式化和替换操作。
  3. 日期和时间处理:

    • java.time包:Java 8引入了新的日期和时间API,位于java.time包中。它提供了一组强大的类和方法,用于处理不同粒度的日期和时间,以及与时区相关的操作。这些类可以根据指定的语言和地区格式化日期和时间信息。
    • DateFormat类:DateFormat类是旧版日期格式化类,位于java.text包中。它提供了基本的日期和时间格式化功能,并可根据指定的语言和地区进行本地化格式化。
  4. 数字和货币格式化:

    • NumberFormat类:NumberFormat类是用于格式化数字的类,位于java.text包中。它可以根据指定的语言和地区格式化数字、百分比和货币等内容。
    • Currency类:Currency类是表示货币的类,它提供了有关货币的信息,如货币代码、符号、小数位数等。通过Currency类,可以根据指定的语言和地区正确地格式化货币值。
  5. 时间和货币的格式化约定:

    • Locale类和java.util.spi.LocaleServiceProvider接口:通过使用Locale类,可以指定时间和货币的格式化约定。不同的语言和地区可能对时间、日期和货币的格式有不同的约定,而Java平台提供了一套基于Locale类的本地化约定接口来处理这些差异。

字符串的本地化处理方式

  1. 使用资源包(Resource Bundle):
    资源包是Java中一种常见的本地化处理方式,通过它可以将本地化相关的字符串存储在不同的属性文件中,每个属性文件对应一个语言或地区。资源包通常使用.properties文件,内容以键值对的形式表示。

    • 创建资源包:创建一个.properties文件,例如messages.properties,其中存储着默认语言的字符串。然后针对不同的语言和地区,创建相应的属性文件,如messages_zh_CN.properties表示简体中文。
    • 加载资源包:在Java代码中,使用ResourceBundle类加载对应的资源包文件。可以根据需要指定语言和地区,如果没有指定,则使用系统默认的语言和地区。
    • 获取本地化字符串:使用ResourceBundle类根据指定的键获取对应的本地化字符串。

    示例代码如下所示:

    // 加载资源包
    ResourceBundle bundle = ResourceBundle.getBundle("messages", new Locale("zh", "CN"));
    // 获取本地化字符串
    String localizedString = bundle.getString("hello.world");
    
  2. 使用MessageFormat类:
    MessageFormat类是Java提供的用于格式化字符串的工具类,它可以根据指定的语言和地区进行复杂的消息格式化和替换操作。通过在字符串中使用占位符和参数,可以达到动态替换的效果。

    示例代码如下所示:

    String pattern = "Hello, {0}! Today is {1}.";
    String name = "John";
    String date = DateFormat.getDateInstance(DateFormat.FULL).format(new Date());
    String formattedString = MessageFormat.format(pattern, name, date);
    

    在上述示例中,pattern是带有占位符的字符串,MessageFormat.format()方法将会根据指定的语言和地区,将传入的参数替换到对应的占位符位置。

  3. 使用StringFormat类:
    String.format()方法是Java中常用的字符串格式化方法,它也支持本地化处理。该方法通过使用格式化字符串和参数,可以实现字符串的格式化输出。

    示例代码如下所示:

    String name = "Alice";
    int age = 30;
    String localizedString = String.format("My name is %s and I'm %d years old.", name, age);
    

    在上述示例中,%s%d是字符串格式化的占位符,String.format()方法将会根据指定的语言和地区,将传入的参数替换到对应的占位符位置。

猜你喜欢

转载自blog.csdn.net/u012581020/article/details/131455658