String源码详解一

String源码详解

前言

String是我们平时写代码最常用的类型,所以了解String的源码也是我们必要的功课。
下面是String源码解读,JDK版本是1.9。


//String实现了序列化接口,比较接口和字符序列接口
//说明String类可以序列化和反序列化,实现了自己的比较逻辑,和定义不可变字符
//CharSequence 是可读可写字符序列接口
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    //用于存储字符串即String的值,final修饰,说明值不会改变
    @Stable  //该注解表示字符数组value第一个非null值永远不会改变
    private final byte[] value; 

    //字符串所用的编码器,LATIN1或UTF16
    //Latin1是ISO-8859-1的别名,向下兼容ASCII码
    private final byte coder;

    //String的hash值,默认是0 
    private int hash;

    //序列号:ID
    private static final long serialVersionUID = -6849794470754667710L;

    //字符串编码是否紧凑,为true则为紧凑以LATIN1编码优先,为false则总是使用UTF16编码
    static final boolean COMPACT_STRINGS;
    //字符串编码是否紧凑默认为true
    static {
        COMPACT_STRINGS = true;
    }

    //用于声明String类的序列化字段
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

    //初始化新创建的String对象,使其表示空字符串。 但是,由于字符串是不可变的,因此不建议使用该无参构造函数,因为没有意义。
    public String() {
        this.value = "".value;
        this.coder = "".coder;
    }

    //构造函数,传入一个字符串
    // HotSpotIntrinsicCandidate标注的方法,在HotSpot中都有一套高效的实现,该高效实现基于CPU指令,运行时,HotSpot维护的高效实现会替代JDK的源码实现,从而获得更高的效率。
    @HotSpotIntrinsicCandidate  
    public String(String original) {
        this.value = original.value;
        this.coder = original.coder;
        this.hash = original.hash;
    }

	//构造函数,将chars数组转换为String
    public String(char value[]) {
    	//具体实现看这个方法
        this(value, 0, value.length, null);
    }

	//指定char数组以及开头和长度,即截取char数组的某一长度为String
    public String(char value[], int offset, int count) {
        this(value, offset, count, rangeCheck(value, offset, count));
    }

    private static Void rangeCheck(char[] value, int offset, int count) {
    	//如果开始值小于0,或者截取的长度小于0,或者开始值加长度大于char数组的总长度,那么就会直接跑出异常,如果正常则直接返回null,下面是方法实现
        checkBoundsOffCount(offset, count, value.length);
        /*
            static void checkBoundsOffCount(int offset, int count, int length) {
        if (offset < 0 || count < 0 || offset > length - count) {
            throw new StringIndexOutOfBoundsException(
                "offset " + offset + ", count " + count + ", length " + length);
        }
    } 
        */
        return null;
    }

	//构造函数。
   String(char[] value, int off, int len, Void sig) {
    if (len == 0) {  //如果需要截取char长度为0,那么构造一个空字符串
        this.value = "".value;
        this.coder = "".coder;
        return;
    }
    if (COMPACT_STRINGS) { //判断字符串编码是否紧凑,默认为true
    	//这里判断要取的字符数组里面的字符的长度是否小于255,如果都小于255,
    	//那么编码就可以用LATIN1,如果有一个字符不小于255,那么返回val字节数组为null,编码就要用UTF16了。
        byte[] val = StringUTF16.compress(value, off, len);
        if (val != null) {
            this.value = val;
            this.coder = LATIN1;
            return;
        }
    }
    this.coder = UTF16; //这里就是编码用UTF16
    this.value = StringUTF16.toBytes(value, off, len);
}

   // StringUTF16.compress方法
    public static byte[] compress(char[] val, int off, int len) {
        byte[] ret = new byte[len];
        if (compress(val, off, ret, 0, len) == len) {
            return ret;
        }
        return null;
    }
    @HotSpotIntrinsicCandidate
    public static int compress(char[] src, int srcOff, byte[] dst, int dstOff, int len) {
        for (int i = 0; i < len; i++) {
            char c = src[srcOff];
            if (c > 0xFF) {
                len = 0;
                break;
            }
            dst[dstOff] = (byte)c;
            srcOff++;
            dstOff++;
        }
        return len;
    }


    }
}

String(int[],int,int) 这个构造函数

    public String(int[] codePoints, int offset, int count) {
    	/* 
    	    static void checkBoundsOffCount(int offset, int count, int length) {
        if (offset < 0 || count < 0 || offset > length - count) {
            throw new StringIndexOutOfBoundsException(
                "offset " + offset + ", count " + count + ", length " + length);
        	}
    	}
		*/
		//这里还是校验一下开始值和长度如果小于0,或者开始值加长度要大于int数组长度就抛出异常。
        checkBoundsOffCount(offset, count, codePoints.length);
        if (count == 0) {
            this.value = "".value;
            this.coder = "".coder;
            return;
        }
        //上面说过,判断字符是否紧凑编码
        if (COMPACT_STRINGS) {
        	//这里依然是判断int数组中如果有值大于255那么就val字节数组就是null,
        	//编码格式用UTF16,否则就用LATIN1编码
            byte[] val = StringLatin1.toBytes(codePoints, offset, count);
            if (val != null) {
                this.coder = LATIN1;
                this.value = val;
                return;
            }
        }
        this.coder = UTF16;
        this.value = StringUTF16.toBytes(codePoints, offset, count);
    }
    public static byte[] toBytes(int[] val, int off, int len) {
        byte[] ret = new byte[len];
        for (int i = 0; i < len; i++) {
            int cp = val[off++];
            if (!canEncode(cp)) { //这里来判断是否数组中是否有值大于255,如果有就返回null
                return null;
            }
            ret[i] = (byte)cp;
        }
        return ret;
    }
    //位运算,int类型32位,低八位代表256,右移8位为0是否为0就能判断该int值是否大于255
    public static boolean canEncode(int cp) {
        return cp >>> 8 == 0;
    }

下面是以字节数组作为参数的构造函数,方法都大同小异,就不挨个的说了
两个int依然是字节数组的开始位置和长度,String和Charset是代表编码的字符集
在这里插入图片描述

    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
         //和之前的一样检查参数有没有越界
        checkBoundsOffCount(offset, length, bytes.length);
        //按照指定编码格式来对字节数组进行编码,如果为空,则默认使用ISO-8859-1来编码
        StringCoding.Result ret =
            StringCoding.decode(charsetName, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }

构造函数的参数还可以是StringBuffer和StringBuilder但是基本没见人用过,因为这两个类都有自己的toString方法,没必要这么写,吃力不讨好。
在这里插入图片描述

    public String(StringBuffer buffer) {
        this(buffer.toString());
    }
    //StringBuffer的比较简单,直接构造就行
    public String(String original) {
        this.value = original.value;
        this.coder = original.coder;
        this.hash = original.hash;
    }

    public String(StringBuilder builder) {
        this(builder, null);
    }
	//stringBuilder的比较复杂
    String(AbstractStringBuilder asb, Void sig) {
        byte[] val = asb.getValue();
        int length = asb.length();
        if (asb.isLatin1()) {
            this.coder = LATIN1;
            this.value = Arrays.copyOfRange(val, 0, length);
        } else {
            if (COMPACT_STRINGS) {
                byte[] buf = StringUTF16.compress(val, 0, length);
                if (buf != null) {
                    this.coder = LATIN1;
                    this.value = buf;
                    return;
                }
            }
            this.coder = UTF16;
            this.value = Arrays.copyOfRange(val, 0, length << 1);
        }
    }

length()

获取String的长度

    public int length() {
    	//返回String字符串的长度
    	//判断是LATIN1说明是单字节编码,直接返回字节的长度的就行了
    	//判断是UTF16说明是双字节编码,要右移一位,即字节长度除以2才是String字符的长度
        return value.length >> coder();
    }
    
    byte coder() {
        return COMPACT_STRINGS ? coder : UTF16;
    }
    static final byte LATIN1 = 0;
    static final byte UTF16  = 1;

empty()

判断String是否为空

    public boolean isEmpty() {
    	//简答明了,判断字节数组是否为空
        return value.length == 0;
    }

charAt(int index)

返回String指定下标的字符,依然根据不同的编码格式返回不同的字符

    public char charAt(int index) {
    //依然判断编码,
        if (isLatin1()) {
            return StringLatin1.charAt(value, index);
        } else {
            return StringUTF16.charAt(value, index);
        }
    }

codePointAt

返回对应下标字符的unicode编码值
比如"abcd".codePointAt(1)值为98,就是返回这个字符串下标为1,即b字符的unicode编码值。

    public int codePointAt(int index) {
        if (isLatin1()) {
        	//如果是Latin1编码,直接返回字节数组的下标值
            checkIndex(index, value.length);
            return value[index] & 0xff;
        }
        //如果是utf16,那么就返回utf16编码的字符的unicode值
        int length = value.length >> 1;
        checkIndex(index, length);
        return StringUTF16.codePointAt(value, index, length);
    }

codePointBefore

返回对应下标字符的前一个字符的unicode编码值
比如"abcd".codePointBefore(2)值为98,就是返回这个字符串下标为2的前一个字符,即b字符的unicode编码值。

    public int codePointBefore(int index) {
    	//这里就是把下标减1,其他的和上面的就一样了
        int i = index - 1;
        if (i < 0 || i >= length()) {
            throw new StringIndexOutOfBoundsException(index);
        }
        if (isLatin1()) {
            return (value[i] & 0xff);
        }
        return StringUTF16.codePointBefore(value, index);
    }

codePointCount

//返回开始开始下标和结束下标之间的代码点数,从代码可以得知,如果是Latin1编码,那么直接返回他们之间的差值,如果是utf16编码,那么就返回utf16编码的代码点数。代码点数也就是他们的下标之间的差值,和length()方法一样,区别就在于length()方法返回的是两个char字符编码的结果,而这个方法返回的是unicode编码的结果,unicode编码的范围要大于两个char编码的范围,所以一般情况下两者是相等的,但是unicode编码的范围超过的时候就不一样了。比如四个字符编码的内容。

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

offsetByCodePoints

第一个参数是开始索引,第二个参数是偏移数,返回的结果就是从开始索引处的偏移数的索引值,即比如参数为(1,3)意思就是从索引处为1,偏移三个代码点的索引,即返回的是4,这个方法和上面的一样,也会出现unicode编码大于两个char编码的情况。具体的可以查阅相关资料,这里不做详细讨论。

    public int offsetByCodePoints(int index, int codePointOffset) {
        if (index < 0 || index > length()) {
            throw new IndexOutOfBoundsException();
        }
        return Character.offsetByCodePoints(this, index, codePointOffset);
    }

getChars

srcBegin:此字符串的开始下标
srcEnd:此字符串的结束下标
dst:字符数组
dstBegin:字符数组的开始长度
将该字符串的开始索引到结束索引之间的值复制到dst数组中并以dstBegin索引开始复制
比如有个字符串str:“hello” 有个字符数组dst:‘a’‘b’‘c’‘d’
str.getChars(0,1,dst,1) 结果就是将hello的从0开始截取到下标1,就是h,然后替换数组的从1开始,结果dst数组就变成了‘a’‘h’‘c’‘d’

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        checkBoundsBeginEnd(srcBegin, srcEnd, length());
        checkBoundsOffCount(dstBegin, srcEnd - srcBegin, dst.length);
        if (isLatin1()) {
            StringLatin1.getChars(value, srcBegin, srcEnd, dst, dstBegin);
        } else {
            StringUTF16.getChars(value, srcBegin, srcEnd, dst, dstBegin);
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_44130081/article/details/89704624