JDK源码(二):String

String 类实现了三个接口Serializable/Comparable/CharSequence,一个个看:

Serializable:序列化接口,表示这个类是可序列化的,所谓的Serializable,就是java提供的通用数据保存和读取的接口,下次好好理解了这个类再详细说

Comparable:字面意思,可比较,实现compareTo方法,从源码可以看出就是挨个比较字符串中字符的大小(unicode编码大小)

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

CharSequence:String、StringBuilder和StringBuffer都实现了这个接口,从字面意思理解就是这个类的对象是字符序列,从实现这个接口的三个类也可以看出

构造方法


最基本的构造方法

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

看到这边,想到面试中常考的一个问题:

String a = "aa";
String b = new String("aa");
System.out.println(a==b);

答案大家肯定都知道,是false,原因是a是从常量池取的,而b是new出来的一个对象

常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

所以只要new了就不相等

String b = new String("aa");
String c = new String("aa");
System.out.println(c==b);

这个同样是false

扯远了,回到上面的构造方法,用的比较少,并且没特殊需求,我觉得还是直接给值比较好,从常量池中取值。

字符数组构造字符串

public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
String b = new String(new char[]{'a', 'a'});

其实就是回归本质,字符串从数据结构角度看就是 ,而串就是字符的集合。

字符数组构造字符串(进阶版)

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    // Note: offset or count might be near -1>>>1.
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

所谓进阶版其实就是比上面多了两个参数,一个是字符数组的起始位置,还有个是从起始位置开始的字符个数

char[] chars = {'a', 'b', 'c', 'd'};
String b = new String(chars, 1, 2);
System.out.println(b); // bc

字符数组构造字符串(进阶ascii版)

public String(int[] codePoints, int offset, int count)
char[] chars = {97, 98, 99, 100};
String b = new String(chars, 1, 2);
System.out.println(b); // bc

看个例子就懂了,char ch='a'给ch赋值的时候,'a'在内存中的存储值就是97,所以这个方法就是直接转换用整数赋值的char

byte数组构造字符串

public String(byte bytes[], int offset, int length, String charsetName)
byte[] bytes = {97, 98, 99, 100};
String b = new String(bytes, 1, 2, StandardCharsets.UTF_8);
System.out.println(b); // bc

还是一个道理,还有有几个和上面类似的就不细说了,都一样。

StringBuffer 和 StringBuilder 去构造字符串

public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

StringBuffer 和 StringBuilder 在这里也显而易见了,StringBuffer是线程安全的。

私有构造方法

String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

从注释可以看出,现在只支持 share 为 true,并且这样构造的字符串直接和字符数组共享,性能提高了。这个方法之所以没有设为protected是因为一旦公开就破坏了字符串的不可变性,可以直接修改字符数组去修改字符串了。

常用api


public int length()

返回字符串的长度

public boolean isEmpty()

判断字符串长度是否为0

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

获取字符串中下标为index的字符

public int codePointAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return Character.codePointAtImpl(value, index, value.length);
}
String a = "abcd";
int i = a.codePointAt(1);
System.out.println(i); // 98  也就是b

返回字符串中下标为index的字符的unicode 编码

public int codePointBefore(int index) {
    int i = index - 1;
    if ((i < 0) || (i >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return Character.codePointBeforeImpl(value, index, 0);
}
String a = "abcd";
int i = a.codePointBefore(1);
System.out.println(i);

看源码发现主要多了这一步 int i = index - 1; 再看字面意思很明显,就是返回前一个字符的unicode 编码

public int codePointCount(int beginIndex, int endIndex) {
    if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
        throw new IndexOutOfBoundsException();
    }
    return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}

除了增补字符,即代码点为 U+10000~U+10FFFF 的字符,这个方法返回的就是字符串的长度,当然是beginIndex到endIndex之间字符的长度

关于增补字符,可以看这篇 https://www.oschina.net/question/12_12216

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
char[] chars = {'a', 'b', 'c'};
String a = "123456789";
a.getChars(1,2, chars, 1);
System.out.println(chars); // a2c

把字符串中下标为srcBegin到srcEnd复制到目标字符数组中,dstBegin为字符数组的偏移量

getBytes()/getBytes(Charset charset)/getBytes(String charsetName)

都是获取字符串的unicode 编码

String a = "abcd";
byte[] bytes = a.getBytes();
System.out.println(Arrays.toString(bytes)); // [97, 98, 99, 100]

今日总结

总感觉自己看源码的方式不太对,这样一个个看效率太低,得想想怎么提高效率了,今天就看了String类的一半吧,不过在String类中看到了大量的重载,有时候是为了功能性,有时候是为了提供默认项,就跟springboot给我们提供默认配置一样,学习学习,等会再多琢磨琢磨。还有就是参数的校验以及异常处理都值得好好学习。

你可能感兴趣的:(JDK源码(二):String)