String源码学习

简介

String 是 由 final 声明的一个不可被继承的类,其值在初始化后便不可更改。

属性

    private final char value[];
    private int hash; // Default to 0

value属性就是String类的核心,一个String字符串本质就是一个char类型的数组,另外hash属性则是用于比较的优化,后面的注释是指一个空字符串默认的hash值是0.

public static void main(String[] args) {
        System.out.println("空字符串哈希值:"+"".hashCode());
    }

输出:
这里写图片描述
hashCode 函数分析:

  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;
    }

可以看到,当value长度大于0时,hash值将重新计算,否则其值未被初始化,默认为0。

构造函数简介

String 提供了许多构造函数用于构造String对象,这里简要介绍几种:

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

可以看到,这种构造方法直接将当前字符串的value指向源字符串的value,hash值赋值为源字符串的hash值,但这并不会导致一个String对象的修改影响到源字符串,因为String被设计为不可变的,当字符串发生改变,他会创建一个新的字符串,并指向新的字符串。

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

String类可以通过传入一个char数组构造一个String对象,这里调用了Arrays.copyOf 方法,复制了一个相同的数组赋值给value,并不是直接将value指向这个数组,这样可以防止原数组被修改导致String对象的值被修改。

public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

String类也可以通过传入字节数组构造String对象,这里它调用了另一个构造函数:

   public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

其中checkBounds是对传入参数的校验:

 private static void checkBounds(byte[] bytes, int offset, int length) {
        if (length < 0)
            throw new StringIndexOutOfBoundsException(length);
        if (offset < 0)
            throw new StringIndexOutOfBoundsException(offset);
        if (offset > bytes.length - length)
            throw new StringIndexOutOfBoundsException(offset + length);
    }

真正对value的复制则是调用了StringCoding.decode方法:

 static char[] decode(byte[] ba, int off, int len) {
        String csn = Charset.defaultCharset().name();
        try {
            // use charset name decode() variant which provides caching.
            return decode(csn, ba, off, len);
        } catch (UnsupportedEncodingException x) {
            warnUnsupportedCharset(csn);
        }
        try {
            return decode("ISO-8859-1", ba, off, len);
        } catch (UnsupportedEncodingException x) {
            // If this code is hit during VM initialization, MessageUtils is
            // the only way we will be able to get any kind of error message.
            MessageUtils.err("ISO-8859-1 charset not available: "
                             + x.toString());
            // If we can not find ISO-8859-1 (a required encoding) then things
            // are seriously wrong with the installation.
            System.exit(1);
            return null;
        }
    }

这个函数会获得当前系统的字符编码,通过它来解析字节数组,如果解析失败则使用”ISO-8859-1”来解码数组,再失败则程序退出,返回null。String类字节数组相关的构造函数也提供了指定编码方式的构造函数,过程类似,只是将默认编码方式改为指定编码方式。

常用方法简介

length()、isEmpty()、charAt():

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

这三个方法都是直接通过操作value属性实现,还有类似的getChars方法含有多个重载方法,通过复制value数组指定区间的值实现。
equals:

 public boolean equals(Object anObject) {
        //比较的对象与当前字符串是同一对象,返回true
        if (this == anObject) {
            return true;
        }
        //对象不是String类型返回false
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            //长度不同,返回false
            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函数并未直接开始比较字符串的每个字符,而是处理了几种特殊情况后开始比较,提高了比较的效率。
还有许多函数不一一列举。

String类’+’号的使用:
代码执行结果如下:

public static void main(String[] args) {
        String a = "Hello";
        String b = a;
        b += " World";
        System.out.println(a);//Hello
        System.out.println(b);//Hello World
    }

javap 反编译后的结果:
这里写图片描述
反编译后虽然不易理解,但几行注释依然可以看出一些端倪,
图中第一个红箭头可以看到它创建了一个StringBuilder对象,往下通过String.valueOf获取字符串值为StringBuilder对象做了初始化,第二个红箭头处调用了StringBuilder.append
方法,第三个红箭头则调用了toString方法,这里就是对”+”号的处理,即b是指向了由StringBuilder.toString生成的字符串,StringBuilder.toString源码如下:

 @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

该函数返回了一个新的String对象,因此字符串的改变总会生成一个新的对象,并不会影响原字符串。String类还有不少操作字符串的函数,其实都是复制value数组后生成一个新的字符串,同样不影响原字符串。

猜你喜欢

转载自blog.csdn.net/qq_38071004/article/details/81633123