概念
String从概念上来说就是Unicode字符序列,且Java没有内置的字符串类型,而是在Java类库中提供了一个预定义类String,每个用双引号括起来的字符串都是String类的一个实例。
子串
String类提供了一个substring可以从一个较大的字符串提取出一个子串,例如:
String str = "Hello";
String s = str.substring(0,3); //Hel
substring的第一个参数是复制的起始位置(最小是0),第二个参数是复制的截止位置,复制长度为第二个参数减第一个参数得到的值。
拼接
1. Java允许使用+号拼接字符串
2. 当将一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串,例如:
int age = 13;
String rating ="PG" + age;
这种特性通常体现在输出语句中:
System.out.println("The answer is " + answer);
3. 如果需要把多个字符串放在一起,用一个界定符分隔,可以使用静态join方法:
String all = String.join("/","S","M","L","XL");
// all is the string "S/M/L/XL"
字符串相等判断
可以使用equals方法检测两个字符串是否相等。对于表达式:
s.equals(t);
如果字符串s 与字符串t相等,则返回 true;否则,返回false。需要注意的是,s与t可以是字符串变量,也可以是字符串字面量。例如,以下表达式是合法的:
"Hello".equals(greeting);
要想检测两个字符串是否相等,而不区分大小写,可以使用equalsIgnoreCase方法
"Hello".equalsIgnoreCase("hello");
一定不要使用-==运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否存放在同一个位置上。当然,如果字符串在同一个位置上,它们必然相等。但是,完全有可能将内容相同的多个字符串副本放置在不同的位置上。
String greeting = "Hello"; // initialize greeting to a string
if (greeting =="Hello") ...
//probably true
if(greeting.substring(0,3) == "Hel") ...
//probably false
如果虚拟机始终将相同的字符串共享,就可以使用=运算符检测是否相等。但实际上只有字符串字面量是共享的,而+或substring等操作得到的字符串并不共享。因此,千万不要使用=运算符测试字符串的相等性,以免在程序中出现这种最糟糕的 bug,看起来这种bug就像随机产生的间歇性错误。
空串与Null串
空串""是长度为0的字符串。可以调用以下代码检查一个字符串是否为空:
if (str.length() == 0)
或
if (str.equals(""))
空串是一个Java对象,有自己的串长度(0)和内容(空)。不过,String变量还可以存放一个特殊的值,名为null,表示目前没有任何对象与该变量关联。要检查一个字符串是否为null,要使用以下条件:
if (str == null)
有时要检查一个字符串既不是null也不是空串,这种情况下就需要使用以下条件:
if (str != null && str.length() != 0)
首先要检查str不为null,如果在一个null值上调用方法,会出现错误。
码点与代码单元
Java字符串由char值序列组成。从3.3.3节已经看到,char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元。最常用的Unicode字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。
length方法将返回采用UTF-16编码表示给定字符串所需要的代码单元数量。例如:
String greeting = "Hello";
int n = greeting.length(); //is 5
要想得到实际的长度,即码点数量,可以调用:
int cpCount = greeting.codePointCount(0,greeting.length);
调用s.charAt(n)将返回位置n的代码单元,n介于0~s.length()-1之间。例如:
char first = greeting.charAt(0); //first is "H"
char last = greeting.length(); //last is 'o'
要想得到第i个码点,应该使用下列语句
int index = greeting.offsetByCodePoint(0,i);
int cp = greeting.codePointAt(index);
为什么会对代码单元如此大惊小怪?请考虑以下语句:
𝕆 is the set of octonions.
使用UTF-16编码表示字符𝕆(U+10546)需要两个代码单元。调用:
char ch = sentence.charAt(1);
返回的不是一个空格,而是的第二个代码单元。为了避免这个问题,不要使用char类型。这太底层了。
如果想要遍历一个字符串,并且依次查看每一个码点,可以使用下列语句:
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) i += 2;
else i ++;
可以使用下列语句实现反向遍历:i --;
if(Character.isSurrogate(sentence.charAt(i))) i --;
int cp = sentence.codePointAt(i);
显然,这很麻烦。更容易的办法是使用codePoints方法,它会生成一个int值的“流”,每个int值对应一个码点。可以将它转换为一个数组,再完成遍历。
int[] codePoints = str.codePoints().toArray();
反之,要把一个码点数组转换为一个字符串,可以使用构造器(我们将在第4章详细讨论构造器和 new操作符)。
String str = new String(codePoints,0,codePoints.length);
StringAPI
char charAt(int index)
//返回给定位置的代码单元。除非对底层的代码单元感兴趣,否则不需要调用这个方法。int codePointAt(int index)5
int codePointAt(int index)
//返回从给定位置开始的码点。
int offsetByCodePoints(int startIndex, int cpCount)
//返回从startIndex码点开始,cpCount个码点后的码点索引
int compareTo(String other)
//按照字典顺序,如果字符串位于other之前,返回一个负数;如果字符串位于other之后,返回一个正数;如果两个字符串相等,返回0。
IntStream codePoints()
//将这个字符串的码点作为一个流返回。调用toArray将它们放在一个数组中
new String(int[] codePoints, int offset, int count)
//用数组中从offset开始的count个码点构造一个字符串
boolean empty()
boolean blank()
//如果字符串为空或者由空格组成,返回true
boolean equals(Object other)
//如果字符串与other相等,返回true
boolean equalsIgnoreCase(String other)
//如果字符串与other 相等(忽略大小写),返回true
boolean startWith(String prefix)
boolean endsWith(String suffix)
//如果字符串以prefix开头或以 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,则返回-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)
//返回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()
//返回一个新字符串。这个字符串将删除原始字符串头部和尾部小于等于U+0020的字符(trim)或空格(strip)
String join(CharSequence delimiter, CharSequence... elements)
//返回一个新字符串,用给定的定界符连接所有元素
String repeat(int count)
//返回一个字符串,将当前字符串重复count次
注意:在API注释中,有一些CharSequence类型的参数。这是一种接口类型,所有字符串都属于这个接口。现在只需要知道,当看到一个 CharSequence形参(parameter)时,完全可以传入 String类型的实参(argument)。
StringBuilder
有些时候,需要由较短的字符串构建字符串,例如,按键或来自文件中的单词。如果采用字符串拼接的方式来达到这个目的,效率会比较低。每次拼接字符串时,都会构建一个新的String对象,既耗时,又浪费空间。使用StringBuilder类就可以避免这个问题的发生。
如果需要用许多小段的字符串来构建一个字符串,那么应该按照下列步骤进行。首先,构建一个空的字符串构建器:
StringBuilder builder = new StringBuilder();
当每次需要添加一部分内容时,就调用append 方法
builder.append(ch); //appends a single character
builder.append(str); //appends a string
在字符串构建完成时就调用toString方法,将可以得到一个String对象,其中包含了构建器中的字符序列。
String completedString = builder.toString();
StringBuffer
StringBuilder类在Java 5中引入。这个类的前身是StringBuffer,它的效率稍有些低,但允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行(通常都是这样),则应该使用StringBuilder。这两个类的API是一样的。
StringBuilderAPI
StringBulder()
//构造一个空的字符串构建器
int length()
//返回构建器或缓冲器中的代码单元数量
StringBuilder append(String str)
//追加一个字符串并返回this
StringBuilder append(char c)
//追加一个代码单元并返回this
StringBuilder appendCodePoint(int cp)
//追加一个码点,并将其转换为一个或两个代码单元并返回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()
//返回一个与构建器或缓冲器内容相同的字符串