Java源码通俗理解之String类(一)--实现接口,成员变量和构造方法

String类简介

String类应该是最频繁使用的类型之一,同时也是超重要的一个类,因为是final修饰的类,所以无法被继承,但他并不能阻止我们通过阅读代码吸收营养

实现接口

java.io.Serializable

这个接口只是一个表示启用序列化功能的许可证,如果要实现序列化功能实现一下就好,不用重写方法.

Comparable

实现这个接口需要重写compareTo()方法,Comparable进行比较时参考的规则就是这个方法中的规则
作用是为了方便我们用的时候进行自然排序的

CharSequence

这个接口包含 length() , charAt(int index) , subSequence(int start, int end) , toString() 四个方法
不只是String类,StringBuffer类,StringBuilder类都实现了这个接口
这个接口通俗的讲就是一个有序字符集合,而String类本质上就是通过一个字符数组实现的,所以实现了这个接口

成员变量

private final byte[] value

这个变量是用来储存字符串的,可以看到这个变量是无法修改的,也就是说我们的每一次字符串修改操作其实本质上应该都是创建了一个新的String对象来装新的字符串

在java9之前官方是用char数组来储存的,后来为了节省空间改成byte数组加编码标记.StringBuffer和StringBuilder,AbstractStringBuilder中也会进行相同的优化.

private final byte coder

这个变量就是上面说的那个编码标记,可能是LATIN1,也可能是UTF16.

private int hash

这个变量是用来存放字符串的hash值得,就是hashCode方法生成的那个东西

private static final long serialVersionUID

这个变量有个默认值:-6849794470754667710L
java序列化机制通过这个东西来验证版本一致性的,详情请看序列化相关内容

static final boolean COMPACT_STRINGS;

static final boolean COMPACT_STRINGS;
 
static {
        COMPACT_STRINGS = true;
    }

COMPACT_STRINGS的作用是表示是否开启字符串压缩,默认是开启的,想改的话需要改JDK配置,JVM会通过获取到的值决定是否打开压缩功能.

如果打开的话当字符串的所有字符都在ASCII编码范围内时coder的值就是LATIN1,从而节省储存空间,反之有字符不能用ASCII编码表示时coder的值就是UTF16.

这是个新东西,以前的老版本都没有这些东西的,原来的String基本全是用UTF16编码,作为最频繁使用的类型之一,String类每省一点空间对于程序的性能来说提升都是巨大的.这是个大好事.

private static final ObjectStreamField[] serialPersistentFields

这个变量也设了默认值:new ObjectStreamField[0]

作用也是和序列化相关的,正常情况下序列化时所有非transient非static修饰的字段都会被序列化,但如果有这个字段的话,只有这个数组里添加的字段才会被序列化,哪怕这个字段被transient修饰,只要在这里面一样会序列化,也就是说这个优先级比transient还高.

String类里的这个数组是个空数组,所以String的字段都不会被序列化.

序列化具体内容请看序列化相关内容,这里就不多说了.

@Native static final byte LATIN1 = 0;

这个和下面那个常量是两个标志,分别代表LATIN1编码和UTF16编码,@Native注解的意思是这俩可能来源于native代码,就是实现JVM的c/c++.

@Native static final byte UTF16 = 1;

同上.

构造方法

缺省权限方法

缺省权限和protected权限不一样,不要搞混了.

String(char[] value, int off, int len, Void sig)

String(char[] value, int off, int len, Void sig) {
        if (len == 0) {
            this.value = "".value;
            this.coder = "".coder;
            return;
        }
        if (COMPACT_STRINGS) {
            byte[] val = StringUTF16.compress(value, off, len);
            if (val != null) {
                this.value = val;
                this.coder = LATIN1;
                return;
            }
        }
        this.coder = UTF16;
        this.value = StringUTF16.toBytes(value, off, len);
    }

sig是用来区分其他构造方法的,value是字符数组,off是从value的哪里开始,len是value的长度.

StringUTF16.compress方法会根据是否开启压缩执行相关操作,决定每个char的最终空间占用是一个byte还是两个byte.

String(AbstractStringBuilder asb, Void sig)

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

AbstractStringBuilder类是StringBuilder和StringBuffer的父类,sig是用来区分其他方法的

asb.isLatin1()方法表面上是返回是否开启压缩,实际上连带着编码格式其实一块返回了,如果开启压缩会返回true,编码格式自然是LATIN1格式,这时候直接赋值就好.如果没开压缩的话再看看自己开压缩了吗,如果开了的话继续用StringUTF16.compress尝试压缩,压缩成功直接结束,压缩失败就老老实实通过Arrays.copyOfRange方法进行值的复制.

String(byte[] value, byte coder)

 String(byte[] value, byte coder) {
        this.value = value;
        this.coder = coder;
    }

这个方法是为了追求速度和节约空间而出现的

这个方法好处不小:
因为直接用"="进行的赋值,所以这个字符串跟传进来的byte数组其实用的是同一个数组,而传统方法是将字符一个一个考进来,所以在性能上完爆其他方法
其次因为是共享数组,所以他俩用的是同一块内存,在内存的节约上也有极大的优势

但与之而来的也有坏处:
字符串是不可变得,而传进去的数组是可以修改的,因为他俩用的是同一个数组,所以该外面那个数组就等于改了字符串的内容,而这是绝对不允许的 ,当然这个问题也很好解决,比如把权限改掉,不让我们用就好了.

public权限方法

public String()

 public String() {
        this.value = "".value;
        this.coder = "".coder;
    }

创建一个新的字符串对象,这个字符串对象是空的
因为字符串创建后是不可变的,所以连官方都不推荐用这个方法

public String(String original)

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

这个方法会将传进去的字符串复制一份
如果不是需要显式的副本官方也不建议用这个方法

public String(char value[])

 public String(char value[]) {
        this(value, 0, value.length, null);
    }

传入的字符数组将会复制一份生成成一个字符串
调用的是String(char[] value, int off, int len, Void sig)方法

public String(char value[], int offset, int count)

public String(char value[], int offset, int count) {
       this(value, offset, count, rangeCheck(value, offset, count));
    }

这个方法表示将传入数组的第offset个字符开始后面的count个字符打包成一个新的字符串,包括第offset个字符
调用的也是String(char[] value, int off, int len, Void sig)方法
rangeCheck方法是用来判断传进去的参数好不好使得,下一章会讲.

public String(int[] codePoints, int offset, int count)

public String(int[] codePoints, int offset, int count) {
        checkBoundsOffCount(offset, count, codePoints.length);
        if (count == 0) {
            this.value = "".value;
            this.coder = "".coder;
            return;
        }
        if (COMPACT_STRINGS) {
            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);
    }

这个方法通俗地讲就是将传进去的int数组中的每一个数转换成对应的char类型,然后和上一个方法一样拼成一个字符串,乱填也会抛异常,里面每个部分干什么的都有注释,简单介绍下用到的方法:
StringLatin1.toBytes方法会将传进去的数字(代码点)全部转换成char类型的字符.
StringUTF16.toBytes作用一样.他俩的区别显而易见就是编码方式不同.

public String(byte bytes[], int offset, int length, String charsetName)

public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBoundsOffCount(offset, length, bytes.length);
        StringCoding.Result ret =
            StringCoding.decode(charsetName, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }

按照你给的字符集来编码指定的byte数组,从而生成一个新的字符串
StringCoding.decode方法就是用来编码的,utf-8,GBK就是字符集
StringCoding类是个内部类,要想在自己的程序里用的话把这个类考一份改改构造方法权限就好
checkBoundsOffCount方法是用来判断传进去的参数好不好使得,rangeCheck就是调用这个方法判断参数的.

public String(byte bytes[], String charsetName) throws UnsupportedEncodingException

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

这个本质上就是调用上面那个方法,只不过帮忙填了两个参数,使得整个传入的byte数组全部被转换成对应字符串

public String(byte bytes[], int offset, int length, Charset charset)

public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBoundsOffCount(offset, length, bytes.length);
        StringCoding.Result ret =
            StringCoding.decode(charset, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }

和上上个方法一样的作用,连实现都是一模一样的

public String(byte bytes[], Charset charset)

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

这个也没啥说的,将整个数组全部转换为字符串

public String(byte bytes[], int offset, int length)

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

这个功能也同上,只不过用的是默认编码规则

public String(byte bytes[])

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

这个是上面那个方法的简化版

public String(StringBuffer buffer) {
        this(buffer.toString());
    }

这个方法会将StringBuffer当前包含的字符序列打包成一个新的字符串
StringBuffer是字符串缓冲区对象

之所以这个方法和下面那个方法的下一层不是同一个方法,是因为StringBuffer有一个toStringCache属性,这个东西是toString方法的缓存,每次调用StringBuffer的toString方法,就会更新一次他的值,所以只能这么创建,而下面那个就可以很干脆的调用String(AbstractStringBuilder asb, Void sig)方法.

this调用的是 String(String original)方法

 public String(StringBuilder builder) {
        this(builder, null);
    }

这个方法会将StringBuilder当前包含的字符序列打包成一个新的字符串
StringBuilder是字符串生成器对象
this调用的是String(AbstractStringBuilder asb, Void sig)方法

@Deprecated方法

出现@Description的方法就不要使了,天知道什么时候负责那一块代码得人就把它删了,如果要用@SuppressWarnings注解强行使用也行,最好自己心里有数在干.

这个注解的意思是这个方法已经废弃了的方法.说不定什么时候就删了.贴出来参考一下就好.

public String(byte ascii[], int hibyte, int offset, int count)
@Deprecated(since="1.1")
    public String(byte ascii[], int hibyte, int offset, int count) {
        checkBoundsOffCount(offset, count, ascii.length);
        if (count == 0) {
            this.value = "".value;
            this.coder = "".coder;
            return;
        }
        if (COMPACT_STRINGS && (byte)hibyte == 0) {
            this.value = Arrays.copyOfRange(ascii, offset, offset + count);
            this.coder = LATIN1;
        } else {
            hibyte <<= 8;
            byte[] val = StringUTF16.newBytesFor(count);
            for (int i = 0; i < count; i++) {
                StringUTF16.putChar(val, i, hibyte | (ascii[offset++] & 0xff));
            }
            this.value = val;
            this.coder = UTF16;
        }
    }
public String(byte ascii[], int hibyte)
@Deprecated(since="1.1")
    public String(byte ascii[], int hibyte) {
        this(ascii, hibyte, 0, ascii.length);
    }

总结

String不愧是最频繁使用的类型之一,官方考虑的非常全面,光构造方法就这么多排序,序列化,修改文字编码等等各种情况sun公司全部体贴的考虑到了,虽然只是看了构造方法,但从中也可以想到,未来看String中的普通方法时那些方法又该有多么精妙.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值