承接上篇文章关于String源码的分析,我们继续总结String中的方法
方法汇总
4.subString方法
public String substring(int beginIndex)
public String substring(int beginIndex, int endIndex)
subString()有2个重载版本,beginIndex指定开始索引(包括),endIndex指定结束索引(不包括)。两个方法实现类似,我们关注一个即可。
public String substring(int beginIndex, int endIndex) {
/* 对传入索引验证 */
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
/* 若截取全部字符串,返回对象本身,否则新建一个String对象*/
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
注意:
new String(value, beginIndex, subLen)
方法是public String(char value[])
的重载版本,内部实现时重新创建一个char[]。而在java7之前的subString()只是简单地与源String共享同一字符数组,通过索引标明起始终止位置。这样虽高效却有内存泄漏的风险。
验证:
public void test(){
String str = "abcdefg";
/*
* 截取整个字符串,直接返回该字符串引用
* 由于字符串的不可变性,这样做是安全的
*/
String substr = str.substring(0);
System.out.println("substr = " + substr); //abcdeg
System.out.println(str==substr); //true
String substr2 = str.substring(1);
System.out.println("substr2 = " + substr2); //bcdefg
System.out.println(str==substr2); //false
}
知识点:
- subString(int fromIndex)方法用于截取子串。自java7后截取的子串是重新创建出来的字符数组,而不再与源字符串共用。
sub = str.subString(0)
这样完全截取的方式返回源字符串引用。相当于sub = str
。
5.valueOf()系列方法
valueOf系列方法在整个java API体系中的用途都很一致。用于将其他类型转为本类型。在这里自然是将其他类型转为String类型。
方法概览
public static String valueOf(Object obj)
public static String valueOf(char data[])
public static String valueOf(char data[], int offset, int count)
public static String valueOf(boolean b)
public static String valueOf(char c)
public static String valueOf(int i)
public static String valueOf(long l)
public static String valueOf(float f)
public static String valueOf(double d)
大多是基本数据类型向String的转化。貌似少了byte啊,不过byte是字节类型,其向String的转化需经解码处理。一般可用参数为byte的构造函数处理。
1??先来看看数值类型向String的转换。
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(float f) {
return Float.toString(f);
}
public static String valueOf(double d) {
return Double.toString(d);
}
调用的目标类的toString()方法。我们看一个就行
// Integer的toString()方法
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size]; //创建char数组
getChars(i, size, buf); //将每个数字作为一个字符填充到char[]
return new String(buf, true); //新对象共享char[],并返回新创建的String对象。
}
上面最后调用了一个受保护的构造函数:
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
这个构造函数之前我们没有提过,这是java7新添加的方法。从实现来看,创建的对象共享了传来的char[],之前提过,这样可能会破坏String的不可变性。不过这里之所以这样,一则因为是它是包访问级别的,对API使用者来说仍是不可见的。二则是这样能提高效率、节省内存,直接赋值给数组引用比重新创建数组要快得多。另外,share变量没有使用,此变量只是一个标识用于区别String(char[] value)
。
2??继续,看看char及char[]转String的实现
//char[]转String,调用`String(char[] value)`
public static String valueOf(char data[]) {
return new String(data);
}
//char转String,构造为char[]数组后参加String
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}
3??布尔值转String比较简单,相应返回”true”和”false”。
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
4?? Object对象转String
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
若对象不为空,返回他的toString()方法表示方式。
此方法判断了对象为空的情况,当对象为空时不想让其抛出异常可用此方法。
6.toCharArray()方法
这个方法挺重要,可获取String内部的char[],到目前为止,我们知道了byte与String的互转(String(byte[]value),getBytes()
),也知道char[]构造String(String(char[]value),valueOf(char[] value)
),而字符串转char数组就是此方法。
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
由源码可知,转换过程是重新创建了新字符数组,再将原字符串的char[]拷贝到新字符数组中。数组拷贝函数System.arraycopy
使用十分频繁,它是一个native方法。它主要作用就是将源数组的元素一一拷贝到目标数组。声明如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
7.replace()系列替换函数
replace()函数用于将字符串里的一个字符或一段字符序列替换成指定字符(序列)。按匹配方法,可分为普通查找匹配和正则表达式匹配。
// 替换一个字符
public String replace(char oldChar, char newChar);
//替换一个字符序列(底层仍使用正则匹配)
public String replace(CharSequence target, CharSequence replacement);
// 使用正则匹配,替换第一次匹配到的字符序列
public String replaceFirst(String regex, String replacement);
//使用正则匹配,替换掉所有匹配到的结果
public String replaceAll(String regex, String replacement);
先来看字符匹配的函数:
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
//1??先查找是否有要替换的字符
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) { //如果有
//新建字符数组,长度与源字符长度相同
char buf[] = new char[len];
//拷贝匹配前的字符
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
//2??从匹配到的第一个目标字符开始,后续若还有目标字符,替换
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
//3??利用新创建的字符数组构造字符串
return new String(buf, true);
}
}
return this;
}
字符串的单个字符的替换过程注释已经说的很明白了,这里又出现了String(char[] value, boolean share)
这个受保护的构造函数。
关于使用正则替换字符串的分析,等留到讲解正则时再说吧,这里仅贴下源码:
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
实例用法:
public void test(){
String str = "abcd1234abcd1234";
//替换一个字符
System.out.println(str.replace(‘b‘,‘B‘)); // aBcd1234aBcd1234
//将子字符串替换为指定字符串
System.out.println(str.replace("cd","CD")); // abCD1234abCD1234
//只替换一次正则匹配到的内容
System.out.println(str.replaceFirst("\\d{4}","-")); // abcd-abcd1234
//替换所有正则匹配到的内容
System.out.println(str.replaceAll("\\d{4}","-")); // abcd-abcd-
}
8. split()函数
split()
函数用于将字符串按指定字符规则(正则)分割成字符串数组,主要函数有:
//按正则表达式regex将字符串分割成长度为limit的字符串数组
public String[] split(String regex, int limit);
//按正则表达式regex将字符串分割成字符串数组
public String[] split(String regex);
试着解析源码:
public String[] split(String regex, int limit) {
/* 如果正则序列满足以下条件,则直接分割:
1. 只有一个字符,且该字符不是"$|()[{^?*+\\"其中之一。
2. 有两个字符,第一个为转义字符(‘\\‘)且第二个不是数字和字母。
*/
char ch = 0;
//判断是否符合这两个条件
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == ‘\\‘ &&
(((ch = regex.charAt(1))-‘0‘)|(‘9‘-ch)) < 0 &&
((ch-‘a‘)|(‘z‘-ch)) < 0 &&
((ch-‘A‘)|(‘Z‘-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
//此算法是该方法核心,使用indexOf(int,int)逐次查找位置并使用substring分割,
//将分割后的字符存入list集合中。
while ((next = indexOf(ch, off)) != -1) { //注意此时ch在if语句已赋值
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result); //将集合转为字符串数组
}
//如果不符合以上两个条件,进行正则匹配
return Pattern.compile(regex).split(this, limit);
}
注释都写在代码里了。从源码中大概可以知道:
- 当是单个字符且不是
.$|()[{^?*+\\
其中之一时,直接通过indexOf()及substring()分割字符串。 - 当是经转义的一些字符(如
.$|()[{^?*+\\
),就像\\.
、\\$
等时,直接分割字符串。 - 其他情况使用正则匹配。
- 综上即是:简单的单个字符或转义后是普通字符的直接分割,其余的交给正则处理。
举个??:
public void test2(){
String str = "abdr$./+1234";
//普通单个字符,直接匹配
System.out.println(Arrays.toString(str.split("d"))); //[ab, r$./+1234]
//以正则字符$(匹配结束位置)去匹配了
System.out.println(Arrays.toString(str.split("$"))); //[abdr$./+1234]
// 匹配"$"本身
System.out.println(Arrays.toString(str.split("\\$"))); //[abdr, ./+1234]
//以正则字符.(匹配任意字符)去匹配了
System.out.println(Arrays.toString(str.split("."))); //[]
//匹配"."本身
System.out.println(Arrays.toString(str.split("\\."))); //[abdr$, /+1234]
}
总结来说,可以这样理解,split(String regex)
方法参数regex
就是要求传入正则式,如果传入的是正则式的特殊字符本身,当然需要转义一下,不然split()怎么知道你要正则处理还是普通处理。
对了,还有split(String regex, int limit)
这个版本,limit
这个参数的意思是,至多分成这么多份,还是看例子吧:
public void test3(){
String str = "abc-abc-abc-abc";
//正好分割完成
System.out.println(Arrays.toString(str.split("-",4))); //[abc, abc, abc, abc]
//满足份数后不再分割
System.out.println(Arrays.toString(str.split("-",3))); //[abc, abc, abc-abc]
//最多分割4份,实在没有那么多啊
System.out.println(Arrays.toString(str.split("-",6))); //[abc, abc, abc, abc]
}
9.intern()方法
这个方法比较特殊,是一个native方法。定义如下:
public native String intern();
之所以把它单独拿出来讲,是因为它的特殊用途,翻译java doc API文档的说明如下:
当此方法被调用时,若字符串常量池中有被调用的这个对象,直接返回常量池的此对象,否则,在常量池添加此对象并返回此对象引用。
public void test4(){
/*
"abc"在常量池中,str引用指向常量池
*/
String str = "abc";
//判断"abc"在常量池,直接返回常量池引用
String str2 = str.intern();
//str和str2都指向常量池
System.out.println(str==str2); //true
}
10. 比较函数
除继承Object的equals()方法外,String提供了许多比较两字符序列的函数。
//字符串与其他字符序列的比较
public boolean contentEquals(StringBuffer sb);
public boolean contentEquals(CharSequence cs);
//忽略大小写的比较
public boolean equalsIgnoreCase(String anotherString) ;
//两字符串的部分比较(是否忽略大小写版本)
public boolean regionMatches(int toffset, String other, int ooffset,
int len);
public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len);
//字符串的开头或结尾的比较(应该算是部分比较的特例)
public boolean startsWith(String prefix, int toffset);
public boolean startsWith(String prefix) ;
public boolean endsWith(String suffix) ;
经过分类,这些函数显得清楚多了。在我们深入分析它们之前,有必要复习一下字符序列家族。
可见,String
、StringBuffer
(线程安全的)、StringBuilder
这3个使用最频繁的类都直接或间接继承自CharSqquence
。而后两者又继承自AbstractStringBuilder
。
1. contentEquals()
contentEquals可比较所有的字符序列类。
public boolean contentEquals(CharSequence cs) {
// 如果是StringBuilder或StringBuffer
if (cs instanceof AbstractStringBuilder) {
if (cs instanceof StringBuffer) {
synchronized(cs) {
/*
nonSyncContentEquals()是一个私有方法,实现即是逐一比较字符,这里就不提了
*/
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}
// 如果是String
if (cs instanceof String) {
return equals(cs);
}
// 如果是普通字符序列类,即实现了CharSequence接口的类
char v1[] = value;
int n = v1.length;
if (n != cs.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != cs.charAt(i)) {
return false;
}
}
return true;
}
方法思路很明确,就是逐一比较字符串的char数组的每个字符。
2. regionMatches()
regionMatches()比较对象是字符串,其可比较两字符串的某一部分是否相等。equalsIgnoreCase方法实现时也调用了此方法。
看含有ignoreCase
参数版本的即可:
public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// 验证参数合法性
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
char c1 = ta[to++];
char c2 = pa[po++];
if (c1 == c2) { //如果相等,继续匹配
continue;
}
if (ignoreCase) { //如果忽略了大小写
// 都转为大写后再比较
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
//这里都转为小写又比较了一次,文档解释说是
//格鲁吉亚字母(Georgianalphabet)可能对此无效,需做最后验证。
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}
startsWith()实现与上述类似,就不多说了。
实例演示:
@Test
public void test4(){
String str = "abcd";
StringBuffer sb = new StringBuffer("abcd");
System.out.println(str.contentEquals(sb)); //true
SubString subString = new SubString("abcd");
System.out.println(str.contentEquals(subString)); //true
String str2 = "111111abcd1111";
System.out.println(str.regionMatches(true,0,str2,6,str.length())); //true
}
class SubString implements CharSequence{
public SubString(String str){
this.value=str.toCharArray();
}
private char[]value;
@Override
public int length() {
return value.length;
}
@Override
public char charAt(int index) {
return value[index];
}
@Override
public CharSequence subSequence(int start, int end) {
return null;
}
}
11. 其他方法
//连接另一个字符串组成新字符串
public String concat(String str);
//是否包含某一字符序列
public boolean contains(CharSequence s);
//java8新方法,用指定连接符将字符串数组或字符结合组合成字符串
public static String join(CharSequence delimiter, CharSequence... elements) ;
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements);
//字符串去除前后空格
public String trim();
//字符串大小写转换
public String toLowerCase() ;
public String toLowerCase(Locale locale);
public String toUpperCase(Locale locale);
public String toUpperCase();
//字符串是否匹配某一正则表达式
public boolean matches(String regex);
这些方法大都见名知义,也不用都举例了。这里只说下join(),它是java8新加的方法。
public void test(){
/*
拼接字符串
*/
String []arr = {"abc","def","xyz"};
String str = String.join("-",arr);
System.out.println(str); //abc-def-xyz
/*
拼接字符集合
*/
List<String> list = new ArrayList<>();
list.add("haha");
list.add("yaya");
list.add("hello");
System.out.println(String.join(";",list)); // haha;yaya;hello
/*
* 拼接多个字符串
*/
System.out.println(String.join(" ","nice","to","meet","you")); //nice to meet you
}
全文完