Java字符串
Java String数据类型可以包含字符序列(字符串),例如字符串上的珍珠。字符串是我们在Java中处理文本的方式。创建Java字符串后,我们可以其中进行搜索,从中创建子字符串,基于第一个字符串创建新的字符串,但是替换了某些部分以及许多其他内容。
内部字符串表示
Java字符串(在Java 9之前)在Java VM中内部使用字节表示,编码为UTF-16. UTF-16使用2个字节表示一个字符。因此,Java字符串的字符使用一个char数组表示。
UTF是一种字符编码,可以表示来自许多不同语言(字母)的字符。这就是为什么必须每个字符使用2个字节,以便能够在单个字符串中表示所有这些不同的字符。
紧凑型字符串
从Java 9开始,Java VM可以使用称为紧凑字符串的新Java函数来优化字符串。紧凑字符串函数使Java VM可以检测字符串是否仅包含ISO-8859-1 / Latin-1字符。如果是这样,则字符串在内部每个字符将仅使用1个字节。因此,紧凑型Java字符串的字符可以由"字节"数组而不是"字符"数组表示。
创建字符串时,将检测字符串是否可以表示为紧凑字符串。字符串一旦创建便是不可变的,因此可以安全地进行操作。
创建一个字符串
Java中的字符串是对象。因此,我们需要使用" new"运算符来创建一个新的Java String对象。这是一个Java字符串实例化(创建)示例:
String myString = new String("Hello World");
引号内的文本是String对象将包含的文本。
Java字符串文字
Java有一种创建新String的较短方法:
String myString = "Hello World";
无需将文本" Hello World"作为参数传递给String构造函数,我们只需将文本本身写在双引号字符内即可。这称为字符串文字。 Java编译器将在内部找出如何创建表示给定文本的新Java String。
转义字符
Java Strings文字接受一组转义字符,这些转义字符在创建的String中转换为特殊字符。这些转义字符是:
转义字符 | 描述 |
\ | 在字符串中转换为单个\字符 |
转换为字符串中的单个制表符 | |
\ r | 转换为字符串中的单个回车符 |
\ n | 转换为字符串中的单个换行符 |
这是使用转义符创建Java String的示例:
String text = "\tThis text is one tab in.\r\n";
此String文字将产生一个以制表符开头,以回车符和换行符结尾的字符串。
字符串文字为常量或者单例
如果在其他String变量声明中使用相同的字符串(例如" Hello World"),则Java虚拟机可能仅在内存中创建单个String实例。字符串文字因此成为事实上的常数或者单例。初始化为相同常量字符串的各种不同变量将指向内存中的同一String实例。这是一个Java String常量/单例示例:
String myString1 = "Hello World"; String myString2 = "Hello World";
在这种情况下,Java虚拟机将使" myString1"和" myString2"都指向同一个String对象。
更准确地说,表示Java String文字的对象是从Java虚拟机内部保留的常量String池中获得的。这意味着,即使来自不同项目的类也被单独编译,但在同一应用程序中使用,它们可能共享常量String对象。共享在运行时发生。它不是编译时函数。
如果要确保两个String变量指向单独的String对象,请使用new
运算符,如下所示:
String myString1 = new String("Hello World"); String myString2 = new String("Hello World");
即使创建的两个Java字符串的值(文本)相同,Java虚拟机也会在内存中创建两个不同的对象来表示它们。
Java文本块
Java文本块(也称为Java多行字符串)是Java 13(预览版)中添加的一项函数,使我们可以更轻松地声明跨越Java代码中多行的String文字。为了解释Java文本块语法,请看以下Java文本块示例:
String textblock = """ This is a text inside a text block """;
注意第一行和最后一行的两组定界符("""
),这3个连续的引号字符告诉Java编译器这是一个声明的Java文本块。
两组引号字符应位于要包含在文本块中的实际文本上方和下方的自己的行上。结果Java字符串中仅包含带有定界符的行之间的行中的文本。
在引号定界符之间,我们可以编写多行字符串,而无需转义新行和引号字符。这是一个Java文本块示例,说明了在文本块声明的文本部分内使用引号字符的情况:
String textblock = """ This is a text inside a text block. You can use "quotes" in here without escaping them. """;
注意单词" quotes"周围的引号字符。在普通的Java String文字中,我们必须转义这些字符,但这在Java文本块内部不是必需的。除非我们要在文本块中的文本中包含3个连续的引号字符,否则不可以。然后,我们需要转义至少一个引号字符,以使Java编译器能够将这些字符与文本块结尾定界符区分开来。
Java文本块缩进
在前面显示的Java文本块示例中,带有3个引号分隔符的两行之间的文本被缩进为与分隔符水平位于同一位置。换句话说,定界符之间的文本与定界符在相同的水平位置开始。这样做纯粹是出于代码格式化的原因!我们可能实际上并不希望所有这些缩进字符(空格或者制表符)都成为从此文本块创建的实际String的一部分!
实际发生的是,Java编译器从Java文本块声明产生的String中删除了所有缩进字符。 Java编译器知道要删除多少个缩进字符的方式是查看文本块的最后一行,该行包含最后3个定界符引号字符。最后一行上的引号字符的缩进确定Java编译器从文本块内的文本中去除多少个缩进字符。这是3个Java文本块示例,它们使用由最后3个定界符引号的缩进控制的不同缩进级别:
String textblock1 = """ This is a Java text block """; String textblock2 = """ This is a Java text block """; String textblock3 = """ This is a Java text block """; System.out.println(textblock1); System.out.println(textblock2); System.out.println(textblock3);
请注意最后3个引号分隔符字符的不同位置。在声明的第一个Java文本块中,引号分隔符与文本本身位于相同的缩进位置。这将导致所有缩进字符从结果Java字符串中剥离。
在第二个示例中,最后3个引号分隔符位于比文本早(水平)两个字符的位置。这意味着Java编译器将在结果Java字符串中保留2个缩进字符。其余的缩进字符将被删除。
在最后一个示例中,最后3个引号分隔符位于比文本块声明内的文本早(水平)4个字符的位置。 Java编译器将在生成的Java字符串中保留4个缩进字符。
这是此Java文本块缩进示例输出的输出:
This is a Java text block This is a Java text block This is a Java text block
如我们所见,生成的字符串包含不同程度的缩进。
如我们现在可能已经弄清楚的那样,文本块内文本的最后3个定界符开始位置与文本的最左字符之间的差异决定了Java文本块声明产生的Java字符串中还剩下多少缩进。换句话说,Java编译器将在文本的缩进处与文本块的最后3个定界符引号进行比较,以确定要包括的缩进。
连接字符串
串联字符串意味着将一个字符串添加到另一个字符串。 Java中的字符串是不可变的,这意味着一旦创建它们就无法更改。因此,当将两个Java String对象彼此串联时,结果实际上将放入第三个String对象中。
这是一个Java String连接示例:
String one = "Hello"; String two = "World"; String three = one + " " + two;
变量" 3"引用的字符串的内容将是" Hello World";其他两个String对象保持不变。
字符串串联性能
连接字符串时,必须注意可能的性能问题。 Java编译器将连接Java中的两个String转换为如下形式:
String one = "Hello"; String two = " World"; String three = new StringBuilder(one) .append(two).toString();
如我们所见,创建了一个新的StringBuilder,将第一个String传递给其构造函数,然后将第二个String传递给其append()方法,最后才调用toString()方法。这段代码实际上创建了两个对象:一个StringBuilder实例和一个从toString()方法返回的新String实例。
当单独作为单个语句执行时,此额外的对象创建开销微不足道。但是,在循环内执行时,情况就不同了。
这是一个包含上述类型的String连接的循环:
String[] strings = new String[]{"one", "two", "three", "four", "five"}; String result = null; for(String string : strings) { result = result + string; }
这段代码将被编译成类似于以下内容的代码:
String[] strings = new String[]{"one", "two", "three", "four", "five"}; String result = null; for(String string : strings) { result = new StringBuilder(result) .append(string).toString(); }
现在,对于该循环中的每次迭代,都会创建一个新的StringBuilder
。另外,一个字符串对象是由toString()
方法创建的。这导致每次迭代的对象实例化开销很小:一个StringBuilder对象和一个String对象。但是,这本身并不是真正的性能杀手。但是还有其他与这些对象的创建有关的东西。
每次执行新的StringBuilder(result)代码时,StringBuilder构造函数会将所有字符从result字符串复制到StringBuilder中。循环的迭代次数越多,结果字符串增长的越大。结果字符串增长得越大,将字符从其中复制到新的StringBuilder中,然后再将字符从StringBuilder复制到由toString()方法创建的临时字符串中所花费的时间就越长。换句话说,迭代次数越多,每次迭代就变得越慢。
连接字符串的最快方法是一次创建一个" StringBuilder",然后在循环内重用同一实例。看起来是这样的:
String[] strings = new String[]{"one", "two", "three", "four", "five"}; StringBuilder temp = new StringBuilder(); for(String string : strings) { temp.append(string); } String result = temp.toString();
这段代码避免了循环内的" StringBuilder"和" String"对象实例化,因此也避免了两次复制字符,首先是将其复制到" StringBuilder"中,然后又复制到了String中。
字符串长度
我们可以使用length()方法获取字符串的长度。字符串的长度是字符串包含的字符数,而不是用来表示字符串的字节数。这是一个例子:
String string = "Hello World"; int length = string.length();
子串
我们可以提取字符串的一部分。这称为子字符串。我们可以使用String类的substring()
方法执行此操作。这是一个例子:
String string1 = "Hello World"; String substring = string1.substring(0,5);
执行此代码后," substring"变量将包含字符串" Hello"。
substring()方法有两个参数。第一个是要包含在子字符串中的第一个字符的字符索引。第二个是要包含在子字符串中的最后一个字符之后的字符索引。记住这一点。参数表示"从包括到排除"。在记住之前,这可能会有些混乱。
字符串中的第一个字符的索引为0,第二个字符的索引为1,依此类推。字符串的最后一个字符的索引为" String.length()1"。
使用indexOf()搜索字符串
我们可以使用indexOf()
方法在字符串中搜索子字符串。这是一个例子:
String string1 = "Hello World"; int index = string1.indexOf("World");
执行此代码后,index
变量将包含值'6'。 indexOf()方法返回在第一个匹配子字符串中找到第一个字符的位置的索引。在这种情况下,匹配的子字符串" World"的" W"位于索引" 6"处。
如果在字符串中未找到子字符串,则indexOf()方法将返回-1;
有一个indexOf()方法的版本,该版本采用一个索引,从该索引开始搜索。这样,我们可以搜索字符串以查找多个子字符串。这是一个例子:
String theString = "is this good or is this bad?"; String substring = "is"; int index = theString.indexOf(substring); while(index != -1) { System.out.println(index); index = theString.indexOf(substring, index + 1); }
此代码在字符串"is this good or is this bad?"中搜索子字符串" is"的出现。它是使用indexOf(substring,index)方法实现的。 index参数告诉字符串中从哪个字符索引开始搜索。在此示例中,搜索将在找到上一个匹配项的索引之后的第一个字符开始。这样可以确保我们不只是继续查找相同的事件。
此代码输出的输出为:
0 5 16 21
子串"is
"在四个地方都可以找到。在单词" is"中两次,在单词" this"中两次。
Java String类还具有一个lastIndexOf()
方法,该方法查找子字符串的最后一次出现。这是一个例子:
String theString = "is this good or is this bad?"; String substring = "is"; int index = theString.lastIndexOf(substring); System.out.println(index);
从该代码打印的输出将为21
,这是子字符串" is"的最后一次出现的索引。
使用matches()将字符串与正则表达式匹配
Java String matches()方法将一个正则表达式作为参数,如果正则表达式与字符串匹配,则返回true,否则返回false。
这是一个matches()示例:
String text = "one two three two one"; boolean matches = text.matches(".*two.*");
比较字符串
Java字符串还有一组用于比较字符串的方法。这些方法是:
- equals()
- equalsIgnoreCase()
- startsWith()
- endsWith()
- compareTo()
equals()
" equals()"方法测试两个字符串是否彼此完全相等。如果是,则equals()方法返回true。如果不是,则返回" false"。这是一个例子:
String one = "abc"; String two = "def"; String three = "abc"; String four = "ABC"; System.out.println( one.equals(two) ); System.out.println( one.equals(three) ); System.out.println( one.equals(four) );
两个字符串"一个"和"三个"相等,但是"一个"不等于"两个"或者"四个"。字符的大小写也必须完全匹配,因此小写字符不等于大写字符。
从上面的代码打印的输出将是:
false true false
equalsIgnoreCase()
String类还具有一个名为" equalsIgnoreCase()"的方法,该方法比较两个字符串,但忽略字符的大小写。因此,大写字符被认为等于它们的小写等效项。
startsWith()和endsWith()
startsWith()和endsWith()方法检查String是否以某个子字符串开头。这里有一些例子:
String one = "This is a good day to code"; System.out.println( one.startsWith("This") ); System.out.println( one.startsWith("This", 5) ); System.out.println( one.endsWith ("code") ); System.out.println( one.endsWith ("shower") );
本示例创建一个String并检查它是否以各种子字符串开头和结尾。
第一行(在String声明之后)检查String是否以子字符串" This"开头。既然如此,startsWith()
方法将返回true。
第二行从索引为5的字符开始比较时,检查String是否以子字符串" This"开头。结果为false,因为索引5的字符为" i"。
第三行检查字符串是否以子字符串" code"结尾。既然这样,那么endsWith()方法将返回true。
第四行检查字符串是否以子字符串" shower"结尾。由于没有,endsWith()方法返回false。
compareTo()
compareTo()方法将String与另一个String进行比较,并返回一个int,告诉该String小于,等于还是大于另一个String。如果字符串的排序顺序早于另一个字符串,则compareTo()返回一个负数。如果字符串在排序顺序上与另一个字符串相等,则compareTo()返回0。如果字符串在排序顺序上在另一个String之后,则compareTo()方法返回一个正数。
这是一个例子:
String one = "abc"; String two = "def"; String three = "abd"; System.out.println( one.compareTo(two) ); System.out.println( one.compareTo(three) );
本示例将"一个"字符串与其他两个字符串进行比较。此代码输出的输出为:
-3 -1
数字为负数,因为one
字符串的排序顺序比其他两个String的排序顺序更早。
compareTo()方法实际上属于Comparable接口。在我有关排序的教程中对此接口进行了更详细的描述。
我们应该注意,对于非英语语言的字符串,compareTo()方法可能无法正常工作。要使用特定语言正确地对字符串进行排序,请使用整理器。
用trim()修剪字符串
Java String类包含一个名为trim()
的方法,该方法可以修剪字符串对象。修剪是指删除字符串开头和结尾的空格字符。空格字符包括空格,制表符和换行符。这是一个Java字符串trim()
的示例:
String text = " And he ran across the field "; String trimmed = text.trim();
执行完此代码后," trimmed"变量将指向具有值的String实例。
"And he ran across the field"
String对象开头和结尾的空格字符已被删除。字符串中的空白字符尚未被触摸。 "内部"是指第一个和最后一个非空白字符之间。
trim()方法不会修改String实例。相反,它返回一个新的Java String对象,该对象与创建该对象的String对象相同,但是删除了String开头和结尾的空白。
trim()方法对于修剪用户在输入字段中键入的文本非常有用。例如,用户可能会键入他或者她的名字,并意外地在最后一个单词之后或者第一个单词之前添加了多余的空格。 trim()方法是删除此类多余空格字符的简便方法。
用replace()替换字符串中的字符
Java String类包含一个名为replace()
的方法,该方法可以替换String中的字符。 replace()
方法实际上并不替换现有String中的字符。相反,它返回一个新的String实例,该实例与从其创建的String实例相同,但是替换了给定的字符。这是一个Java Stringreplace()
示例:
String source = "123abc"; String replaced = source.replace('a', '@');
执行此代码后,replaced
变量将指向带有文本的字符串:
123@bc
replace()
方法将替换与作为第一个参数传递给该方法的字符匹配的所有字符,并将第二个字符作为参数传递给replace()
方法的字符。
replaceFirst()
Java StringreplaceFirst()
方法返回一个新的String,该字符串具有作为第一个参数传递的正则表达式的第一个匹配项与第二个参数的字符串值。
这是一个replaceFirst()
的例子:
String text = "one two three two one"; String s = text.replaceFirst("two", "five");
本示例将返回字符串"one five three two one".
replaceAll()
Java StringreplaceAll()
方法返回一个新的String,该字符串具有作为第一个参数传递的正则表达式的所有匹配项以及第二个参数的字符串值。
这是一个replaceAll()
的例子:
String text = "one two three two one"; String t = text.replaceAll("two", "five");
本示例将返回字符串"one five three five one".
用split()分割字符串
Java String类包含一个split()
方法,该方法可用于将String拆分为String对象数组。这是一个Java Stringsplit()
示例:
String source = "A man drove with a car."; String[] occurrences = source.split("a");
执行此Java代码后," occurrences"数组将包含String实例:
"A m" "n drove with " " c" "r."
源字符串已被拆分为a
字符。返回的字符串不包含a
字符。字符" a"被视为分隔字符串的定界符,并且分隔符不会在结果String数组中返回。
传递给split()
方法的参数实际上是Java正则表达式。正则表达式可以非常高级。上面的正则表达式只匹配所有a
字符。它甚至只匹配小写的" a"字符。
Stringsplit()
方法存在于以limit
作为第二个参数的版本中。这是一个使用limit
参数的Java字符串split()
示例:
String source = "A man drove with a car."; int limit = 2; String[] occurrences = source.split("a", limit);
limit参数设置返回数组中可以包含的最大元素数。如果字符串中正则表达式的匹配项多于给定的"限制",则数组将包含"限制1"匹配项,最后一个元素将是字符串中"限制1"末尾的其余部分。火柴。因此,在上面的示例中,返回的数组将包含以下两个字符串:
"A m" "n drove with a car."
第一个String是a
正则表达式的匹配项。第二个字符串是第一次匹配后其余的字符串。
以限制3(而不是2)运行示例将导致在结果String数组中返回这些String:
"A m" "n drove with " " car."
注意最后一个字符串如何仍然在中间包含a
字符。这是因为该字符串表示最后一次匹配后字符串的其余部分(" n驾驶"后的" a")。
以4或者更高的限制运行上面的示例将导致仅返回Split字符串,因为在String中只有4个正则表达式" a"匹配。
使用valueOf()将数字转换为字符串
Java String类包含一组名为valueOf()
的重载静态方法,可用于将数字转换为String。以下是一些简单的Java StringvalueOf()
示例:
String intStr = String.valueOf(10); System.out.println("intStr = " + intStr); String flStr = String.valueOf(9.99); System.out.println("flStr = " + flStr);
此代码输出的输出为:
intStr = 10 flStr = 9.99
将对象转换为字符串
Object类包含一个名为toString()
的方法。由于所有Java类都扩展(继承自)Object类,因此所有对象都具有一个" toString()"方法。此方法可用于创建给定对象的String表示形式。这是一个JavatoString()
示例:
Integer integer = new Integer(123); String intStr = integer.toString();
注意:为了使toString()方法返回给定对象的合理的String表示形式,该对象的类必须已覆盖toString()方法。否则,将调用默认的toString()方法(从Object类继承)。默认的toString()
方法没有提供那么多有用的信息。许多内置的Java类已经有一个明智的toString()
方法。
获取字符和字节
使用charAt()方法可以在字符串的某个索引处获取字符。这是一个例子:
String theString = "This is a good day to code"; System.out.println( theString.charAt(0) ); System.out.println( theString.charAt(3) );
此代码将打印出来:
T s
因为这些是位于字符串索引0和3处的字符。
我们还可以使用getBytes()
方法获取String方法的字节表示形式。这是两个示例:
String theString = "This is a good day to code"; byte[] bytes1 = theString.getBytes(); byte[] bytes2 = theString .getBytes(Charset.forName("UTF-8");
第一个getBytes()调用使用计算机上的默认字符集编码返回String的字节表示形式。默认字符集是什么,取决于在其上执行代码的机器。因此,通常最好明确指定用于创建字节表示形式的字符集(如下一行所示)。
第二个" getBytes()"调用返回字符串的UTF-8字节表示形式。
转换为大写和小写
我们可以使用toUpperCase()和toLowerCase()方法将字符串转换为大写和小写形式。这是两个示例:
String theString = "This IS a mix of UPPERcase and lowerCASE"; String uppercase = theString.toUpperCase(); String lowercase = theString.toLowerCase();
字符串格式
从Java 13开始,Java String类获得了一个名为formatted()
的新方法,该方法可用于返回Stringformatted()
的格式化版本。 formatted()
方法只是在Java 13中与Java Text Blocks一起添加的预览函数,因此我们尚不知道它是否会保留在这里。这是使用Java字符串formatted()
的示例。方法:
String input = "Hello %s"; String output1 = input.formatted("World"); System.out.println(output1); String output2 = input.formatted("Hyman"); System.out.println(output2);
此示例将首先打印" Hello World",然后打印" Hello Hyman"。传递给formatted()
的参数值将被插入到返回的String中,位于input
String的%s位置。
stripIndent
从Java 13开始,Java String类获得了一个名为stripIndent()
的新方法,该方法可用于剥离缩进,类似于从Java文本块中剥离缩进的方式。 stripIndent()方法是一种预览函数,因此我们不知道它是否仍将保留在Java中。这是使用新的Java StringstripIndent()
方法的示例:
String input = " Hey \n This \n is \n indented."; String output = input1.stripIndent(); System.out.println(output);
从此示例打印的输出将是:
Hey This is indented.
请注意,如何删除每行缩进的前三个字符。
如果每行的缩进不同,则最短的缩进将从每行中去除。如果,例如输入字符串的最后一行仅缩进1个字符,其他行的缩进仅去除1个字符。
translateEscapes()
从Java 13开始,Java String类获得了一个名为" translateEscapes()"的新方法,该方法可以转换存在于String中的转义代码,其方式与Java编译器翻译它们的方式相同。目前," translateEscapes()"是一种预览函数,因此尚不确定它将保留在Java中。这是一个使用Java StringtranslateEscapes()
方法的示例:
String input = "Hey, \n This is not normally a line break."; System.out.println(input); String output = input.translateEscapes(); System.out.println(output);
Java编译器将转义符" \"解释为单个" "字符,因此输入String最终包含一个\ n作为2个文本字符,而不是换行符。
现在,当调用translateEscapes()
方法时,文本的\ n部分将被解释为换行符。
从上面的代码打印的输出将是:
Hey, \n This is not normally a line break. Hey, This is not normally a line break.
请注意,打印的第一行是如何将\ n显示为文本,第二行将其解释为换行符。
添加方法
除了本教程中介绍的方法外,String类还有其他几种有用的方法。我们可以在String JavaDoc中找到它们。