String源码分析:底层实现原理

String不可变性

String的不变性指的是类值一旦被初始化,就不能被改变。我们从源码出发,可以看到String类由final修饰,即类不能被继承,String中的方法不能被继承重写。String通过一个char数组value来保存数据,同样是final修饰的,即value数组一旦被赋值,内存地址无法修改。String的不变性,充分利用了final关键字的性质。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
}    

String被设计成不可变的主要目的是为了安全和高效。
第一,安全方面。String中有些方法比较底层,如果被继承并重写方法会有隐患;同时不可变对象一定是线程安全的,可以在线程之间共享,无需同步。
第二,设计者希望用户用到的String就是JDK中的String,不希望被继承和重写,确保String可控。
第三,性能方面。基于不变性,编译器和JVM可以对String进行优化,提高性能。
String是不可变的,其hashcode值也是固定不变的。String通过hash来缓存当前字符串的hashcode值,保证只需要计算一次。

private int hash; // Default to 0
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

方法中首先对hash属性值做了判断,如果计算过hashCode,结果会被缓存到hash属性,不需要重复计算。

replaceFirst()、replaceAll()、replace()三个方法的区别

public String replaceFirst(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

将字符串中目标文本字符序列(非正则表达式)替换为指定字符序列。
替换从字符串的开头到结尾,例如,在字符串“aaa”中将“aa”替换为“b”将导致“ba”而不是“ab”

小结:

  1. replacereplaceAll都是全部替换,replace替换的只能是字符或字符串形式,而replaceAll是基于正则表达式替换。
  2. replaceAllreplaceFirst()是基于正则表达式的替换,replaceFirst仅替换首次出现。
  3. replace的另一个方法参数类型是char,对字符串中所有匹配字符做替换。
  4. replace(String)方法还可以用于对字符串中字符的删除。
"Awecoder".replace("e","")  // 值为Awcodr

扩展:对于替换,特别注意转移字符’’。
反斜杠'\'即是Java语言的转移字符,也是正则表达式的转义字符。反斜杠\,在正则表达式表示反斜杠用\\,而Java代码需要用\\\\来表示。

// 输出a\b
System.out.println("a/b".replaceAll("/","\\\\"));
System.out.println("a/b".replace("/","\\"));
System.out.println("a/b".replace('/', '\\'));

如何解决String乱码问题

出现乱码主要是因为字符集不支持复杂汉字、二进制转化时字符集不匹配等原因。在出现String乱码时,我们通常会采取以下操作。

  1. 在可以指定字符集的地方强制指定字符集,例如new StringgetBytes
  2. 使用UTF-8这种能完整支持复杂汉字的字符集。

示例:

String str = "字符串";
byte[] bytes = str.getBytes("ISO-8859-1"); // 字符串转化成byte数组
String s2 = new String(bytes); // byte数组转化成字符串
System.out.println(s2);
运行结果:
???

首字母大小写转换

首字母大小写我们通常采用下面的代码来实现。

String str = "Awecoder";
str.substring(0, 1).toLowerCase() + str.substring(1) // awecoder

底层源码分析

public String substring(int beginIndex, int endIndex) {
    // 边界处理
    int subLen = endIndex - beginIndex;
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
public String(char value[], int offset, int count) {
     // 边界处理
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

substring()方法会创建一个新的字符串,底层采用了字符数组范围截取: Arrays.copyOfRange(value, offset, offset+count)

String的相等判断

String中通常使用equalsequalsIgnoreCase两个方法。其中,equalsIgnoreCase方法忽略大小写比较,参数类型是Stringequals方法是重写方法,其参数类型是Object。在进行相等判断设计时,equals方法更具代表性。

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

equals的源码设计逻辑是很清晰的,分为下面四个步骤。

  • 判断内存地址是否相同
  • 判断对象类型是否相同
  • 判断数据长度是否相同
  • 挨个比较每个数据元素是否相同

字符串拆分

字符串拆分采用的是split方法,底层split方法有两个参数,分别是分隔符和分隔的最大子字符串个数。

public String[] split(String regex, int limit)

实例:

String str = "awe|coder";
str.split("|"); // ["awe","coder"]
str.split("|", 1); // ["awecoder"]
str.split("|", 2); // ["awe","coder"]

如果分隔符之间存在空值,空值是拆分不掉的,作为空串成为结果数组一部分。借助外部工具例如Guava,可以快速并且可靠地去除空格、空值等。

String str ="|awe||coder";
str.split("|") 结果:["","awe","","coder"]
发布了72 篇原创文章 · 获赞 110 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/LIZHONGPING00/article/details/103231210