Java 字符串简介(学习 Java 编程语言 011)

从概念上讲,Java 字符串就是 Unicode 字符序列。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String。每个用双引号括起来的字符串都是 String 类的一个实例:

String e = ""; // 空字符串
String greeting = "Hello";

1. 子串

String 类的 substring 方法可以从一个较大的字符串提取出一个子串。

String greeting = "Hello";
String s = greeting.substring(0, 3); // 变量 s 为 "Hel"

字符串中的代码单元和代码点从 0 开始计算。substring 方法的第二个参数是不想复制的第一个位置。

substring 的工作方式有一个优点: 容易计算子串长度。字符串 s.substring(a, b) 的长度为 b - a

2. 拼接

Java 语言允许使用 + 号连接(拼接)两个字符串。

String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
System.out.println(message);        // 打印 Expletivedeleted

当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。

如果需要把多个字符串放在一起,用一个界定符分隔,可以使用静态 join 方法:

String all = String.join(" /", "S", "M", "L", "XL");
System.out.println(all);// 打印 S /M /L /XL

String [] arr = {"S", "M", "L", "XL"};
all = String.join(" /", arr);
System.out.println(all);// 打印 S /M /L /XL

arr = new String[]{"S", "M", "L", "XL"};
List list = Arrays.asList(arr);
all = String.join(" /", arr);
System.out.println(all);// 打印 S /M /L /XL

3. 字符串不可变

String 类没有提供用于修改字符串的方法。由于不能修改 Java 字符串,所以在 Java 文档中将 String 类对象称为是不可变的(immutable)。不可变字符串却有一个优点:编译器可以让字符串共享。

Java 的设计者认为共享带来的高效率远远胜过于提取子串、拼接字符串所带来的低效率。查看一下程序会发现:很少需要修改字符串,而是往往需要对字符串进行比较(有一种例外情况,将来自于文件或键盘的单个字符或较短的字符串汇集成字符串),Java 专门为此提供了一个单独的类。

4. 检测字符串是否相等

可以使用 equals 方法检测两个字符串是否相等。表达式:

s.equals(t);

如果字符串 s 与字符串 t 相等,返回 true;否则,返回 false。s 与 t 可以是字符串变量,也可以是字符串字面量。

想要检测两个字符串是否相等,而不区分大小写,可以使用 equalsIgnoreCase 方法。

"Hello".equalsIgnoreCase("hello"); // true

一定不要使用 == 运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否放置在同一个位置上。当然,如果字符串放置在同一个位置上,她们必然相等。但是,完全有可能将内容相同的多个字符串的拷贝放置在不同的位置上。

如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符检测是否相等。但实际上只有字符串常量是共享的。而 + 或 substring 等操作产生的结果并不是共享的。因此,千万不要使用 == 运算符测试字符串的相等性,以免在程序中出现糟糕的 bug。这种 bug 很像随机产生的间歇性错误。

5. 空串与 Null 串

空串是一个 Java 对象,有自己的长度(0)和内容(空)。空串 "" 是长度为 0 的字符串。

代码检查一个字符串是否为空:

if (str.length() == 0)
// 或
if (str.equals(""))

要检查一个字符串是否为 null:

if (str == null)

检查一个字符串既不是 null 也不为空串:

if (str != null && str.length() != 0)

6. 码点与代码单元

Java 字符串由 char 值序列组成。char 数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元。最常用 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。

length 方法将返回采用 UTF-16 编码表示的给定字符所需要的代码单元数量。

String greeting = "Hello";
int n = greeting.length(); // 变量 n 为 5

要想得到实际的长度,即码点数量,可调用:

String greeting = "Hello";
int n = greeting.codePointCount(0, greeting.length()); // 变量 n 为 5

调用 s.charAt(n) 将返回位置 n 的代码单元,n 介于 0 ~ s.length()-1 之间。

String greeting = "Hello";
char first = greeting.charAt(0); // first 为 'H'
char last = greeting.charAt(4); // last 为 'o'

要想得到第 i 个代码点,应该使用下列语句

String greeting = "Hello";
int i = 3;

int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
// 或者
int cp = greeting.codePointAt(index);

Java 对字符串中的代码单元和码点从 0 开始。

为什么对代码单元如此大惊小怪?考虑下列语句:

 is the set of octonions.

使用 UTF-16编码表示字符 (U+1D546) 需要两个代码单元。调用

char ch = sentence.charAt(1);

返回不是一个空格,而是 的第二个代码单元。为了避免这个问题,不要使用 char 类型。这太底层了。

String greeting = "Hello";
// 获得实际的长度,即码点数量
int n = greeting.codePointCount(0, greeting.length());
System.out.println(n); // 打印 5

// 辅助字符 (U+1D546)
String str = "\ud835\udd46";
System.out.println(str); // 打印 

// 获得辅助字符  实际的长度,即码点数量:2
n = str.length();
System.out.println(n); // 打印 2
System.out.println(str.charAt(0)); // 打印 ?
System.out.println(str.charAt(1)); // 打印 ?


// 打印辅助字符  的码点
System.out.println(str.codePointAt(0)); // 打印 120134
// 打印辅助字符 
System.out.println(new String(Character.toChars(120134))); // 打印 

// 打印辅助字符  的 unicode 字符:\ud835\udd46
for(int i = 0; i < str.length(); i++) {
    String unicode = Integer.toHexString(str.charAt(i));
    System.out.print("\\u");
    System.out.print(unicode);
} 

如果想要遍历一个字符串,并且依次查看每一个码点,可以使用下列语句:

String str = "\ud835\udd46 is the set of octonions.";
int i = 0;
while(i < str.length()) {
    int cp = str.codePointAt(i);
    System.out.print(new String(Character.toChars(cp)));

    if(Character.isSupplementaryCodePoint(cp)) {
        i += 2;
    }else {
        i++;
    }
}

打印: is the set of octonions.

可以使用下列语句实现回退操作:

String str = "\ud835\udd46 is the set of octonions.";
int i = str.length();
while(i > 0) {
    i--;
    if (Character.isSurrogate(str.charAt(i))) {
        i--;
    }
    int cp = str.codePointAt(i);
    System.out.print(new String(Character.toChars(cp)));
}

打印:.snoinotco fo tes eht si

显然,这很麻烦。更容易的办法是使用 codePoints 方法,它会生成一个 int 值 “流”,每个 int 值对应一个码点。可以将它转换为一个数组,再完成遍历。

int[] codePoints = str.codePoints().toArray();

反之,要把一个码点数组转换为一个字符串,可以使用构造函数。

String str = new String(codePoints, 0, codePoints.length);

虚拟机不一定吧字符串实现为代码单元序列。在 Java 9 中,只包含单字节代码单元的字符串使用 byte 数组实现,所有其他字符串使用 char 数组。

// str =  is the set of octonions. 
String str = "\ud835\udd46 is the set of octonions. \ud83c\udf7a\ud83c\udf7a\ud83c\udf7a";
System.out.println(str);

// 正向遍历字符串 1
for(int i = 0; i < str.length();) {
    int cp = str.codePointAt(i);
    if(Character.isSupplementaryCodePoint(cp)) {
        i += 2;
    }else {
        i++;
    }
    char[] chars = Character.toChars(cp);
    String code = new String(chars);
    System.out.print(code);
}
System.out.println();

// 正向遍历字符串 2
int[] codePoints = str.codePoints().toArray();
for(int i = 0; i < codePoints.length; i++) {
    String code = new String(Character.toChars(codePoints[i]));
    System.out.print(code);
}
System.out.println();

// 将码点数组转化为字符串
String newStr = new String(codePoints, 0, codePoints.length);
System.out.println(newStr);

// 反向遍历字符串
for(int i = str.length(); i > 0;) {
    i--;
    
    char ch = str.charAt(i);
    if(Character.isSurrogate(ch)) {
        i--;
    }
    int cp = str.codePointAt(i);
    char[] chars = Character.toChars(cp);
    String code = new String(chars);
    System.out.print(code);
}

7. 构建字符串

有时需要由较短的字符串构建字符串,例如,按键或来自文件中的单词。采用字符串连接的方式达到此目的效率比较低。每次连接字符串,都会构建一个新的 String 对象,即耗时,又浪费空间。使用 StringBuilder 类就可以避免上述问题的发生。

// 构建一个空的字符串构建器
StringBuilder builder = new StringBuilder();

// 向字符串构造器对象中追加小段字符串
builder.append("Welcome");
builder.append(" ");
builder.append("to");
builder.append(" ");
builder.append("xiang017");
builder.append("!");

// 构造字符串对象
String str = builder.toString();

// 打印 Welcome to xiang017!
System.out.println(str);

StringBuilder 类的前身是 StringBuffer,它的效率稍有些低,但允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行(通常都是这样),则应该使用 StringBuilder。这两个类的 API 是一样的。

8. StringBuilder 类中的重要方法:

java.lang.StringBuilder

  • StringBuilder()

    构造一个空的字符串构建器。

  • int length()

    返回构建器或缓冲器中的代码单元数量。

  • StringBuilder append(String str)

    追加一个字符串并返回 this。

  • StringBuiler append(char c)

    追加一个代码单元病分会 this。

  • void setCharAt(int i, char c)

    将第 i 个代码单元设置为 c。

  • StringBuilder insert(int offset, String str)

    在 offset 位置插入一个字符串并返回 this。

  • StringBuilder insert(int offset, char c)

    在 offset 位置插入一个代码单元并返回 this。

  • StringBuilder delete(int startIndex, int endIndex)

    删除变异量从 startIndex 到 endIndex-1 的代码单元并返回 this。

  • String toString()

    返回一个与构建器或缓冲器内容相同的字符串。

9. String 类中的重要方法

Java 中的 String 类包含了 50 多个方法。它们绝大多数都很有用,使用的评率非常高。

java.lang.String

  • char charAt(int index)

    返回给定位置的代码单元。除非对底层的代码单元感兴趣,否则不需要调用这个方法。

  • int codePointAt(int index) 5

    返回从给定位置开始的码点。

  • int offsetByCodePoints(int startIndex, int cpCount) 5

    返回从 startIndex 码点开始,cpCount 个码点后的码点索引。

  • int compareTo(String other)

    按照字典顺序,如果字符串位于 other 之前,返回一个负数;如果为字符串 other 之后,返回一个整数;如果两个字符串相等,返回 0。

  • IntString codePoints() 8

    将这个字符串的码点作为一个流返回。调用 toArray 将它们放在一个数组中。

  • new String(int[] codePoints, int offset, int count) 5

    用数组中从 offset 开始的 count 个码点构造一个字符串。

  • boolean empty()

    如果字符串为空,返回 true。

  • boolean blank() 11

    如果字符串由空字符串组成,返回 true。

  • boolean equals(Other other)

    如果字符串与 other 相等,返回 true。

  • boolean equalsIgnoreCase(String other)

    如果字符串与 other 相等(忽略大小写),返回 true。

  • boolean startsWith(String prefix)

    如果字符串以 prefix 开头,返回 true。

  • boolean endWith(String suffix)

    如果字符串以 suffix 结尾,返回 true。

  • int indexOf(String str)
  • int indexOf(String str, int fromIndex)
  • int indexOf(int cp)
  • int indexOf(int cp, int fromIndex)

    返回与字符串 str 或码点 cp 匹配的第一个子串的开始位置。从索引 0 或 fromIndex 开始匹配。如果在原始字符串中不存在 str 或 cp,则返回 -1。

  • int lastIndexOf(String str)
  • int lastIndexOf(String str, int fromIndex)
  • int lastIndexOf(int cp)
  • int lastIndexOf(int cp, int fromIndex)

    返回与字符串 str 或码点 cp 匹配的最后一个子串的开始位置。从原始字符串末尾或 fromIndex 开始匹配。

  • int length()

    返回字符串代码单元的个数。

  • int codePointCount(int startIndex, int endIndex) 5

    返回 startIndex 和 endIndex-1 之间的码点个数。

  • String replace(CharSequence oldString, CharSequence newString)

    返回一个新的字符串。这个字符串用 newString 代替原始字符串中所有的 oldString。可以用 String 或 StringBuilder 对象作为 CharSequence 参数。

  • String substring(int beginIndex)
  • String substring(int beginIndex, int endIndex)

    返回一个新字符串。这个字符串包含原始字符串从 beginIndex 到字符串末尾或 endIndex-1 的素有代码单元。

  • String toLowerCase()

    返回一个新的字符串。这个字符串将原始字符串中的大写字母改为小写。

  • String toUpperCase()

    返回一个新的字符串。这个字符串将原始字符串中的小写字母改为大写。

  • String trim()
  • String strip() 11

    返回一个新字符串。这个字符串将删除原始字符串头部和尾部小于等于 U+0020 的字符(trim)或空格(strip)。

  • String join(CharSequence delimiter, CharSequence... elements) 11

    返回一个新字符串,用给定的定界符连接所有元素。

  • String repeat(int count) 11

    返回一个字符串,当前字符串重复 count 次。

你可能感兴趣的:(Java 字符串简介(学习 Java 编程语言 011))