Java源码分析之String

String,这个集神秘与强势于一体的常用封装类,今天要一探究竟。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

首先,String类被定义为final类型,说明其不可被继承。实现了Serializable, Comparable<String>, CharSequence三个接口。

java.io.Serializable 这个接口实现了序列化,对于序列化,就是将对象转换为字节序列的过程(就是把内存里面的这些对象给变成一连串的字节描述的过程。常见的就是变成文件),从而实现将对象实例持久化,进行保存数据、使用套接字在网络上传输数据时使用等。

Comparable

这个接口只有一个compareTo(T 0)接口,用于对两个实例化对象比较大小。

CharSequence

这个接口是一个只读的字符序列。包括length(), charAt(int index), subSequence(int start, int end)这几个API接口,值得一提的是,StringBuffer和StringBuild也是实现了该接口。

private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

定义了三个成员变量,value[]用来存放String字符串,hash哈希缓存值,serialVersionUID序列化UID。

然后String类提供的构造方法竟然有16个那么多,都是各种花式赋值初始化,这里便不一一列举。

然后就是一些基本常用方法:length()获取长度,isEmpty()判空,charAt索引下标为index的值,还有以下不常用的方法:

// 返回下标为index的值,由于返回类型是int,所以返回的是ascall值
public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }
// 返回下标为index-1的值
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);
    }
// 返回下标从beginIndex到endIndex的字符个数
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);
    }
// 返回下标为index的值加上codePointOffset的下标
public int offsetByCodePoints(int index, int codePointOffset) {
        if (index < 0 || index > value.length) {
            throw new IndexOutOfBoundsException();
        }
        return Character.offsetByCodePointsImpl(value, 0, value.length,
                index, codePointOffset);
    }

 

这是一个将自身字符串的值复制到dst数组(从下标dstBegin开始)里的方法,但奇怪的是,按照上面的逻辑和风格,这个方法竟然没有处理边界值,也就是说,当调用这个方法并且数组越界时不会抛出异常,真是百思不得其解!

void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

 

再看这个重载方法就改回老本行了(可能不是一个人写的),从secBegin到secEnd的字符复制给dst。。。

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

 

以下都是获取字符串的二进制,以byte数组存放

public void getBytes(int srcBegin, int srcEnd, byte 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);
        }
        Objects.requireNonNull(dst);

        int j = dstBegin;
        int n = srcEnd;
        int i = srcBegin;
        char[] val = value;   /* avoid getfield opcode */

        while (i < n) {
            dst[j++] = (byte)val[i++];
        }
    }

public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);
    }

public byte[] getBytes(Charset charset) {
        if (charset == null) throw new NullPointerException();
        return StringCoding.encode(charset, value, 0, value.length);
    }

public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

 

equals方法,其实现逻辑是先判断两个对象引用是否相同,再判断比较对象是否是它及其子类的实例对象,是就一个一个字符比较。满足一个条件就认定相等,也就是说String str="abc";Object dst="abc";用equals比较这两个对象返回是true。

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

还有其他contentEquals、nonSyncContentEquals比较内容的,equalsIgnoreCase不管大小写比较内容的,compareTo按字典序比较大小的,比较该字符串和其他一个字符串从分别指定地点开始的n个字符是否相等的regionMatches等等。

然后看看hashCode方法,算法就不说了,可以看出来每个对象实例hash值只计算一次,然后永远不变。

为什么系数选择31?31的乘法可以由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化,使用31的原因可能是为了更好的分配hash地址,并且31只占用5bits!在java乘法中如果数字相乘过大会导致溢出的问题,从而导致数据的丢失. 而31则是素数(质数)而且不是很长的数字,最终它被选择为相乘的系数。

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

然后是各种索引字符位置(下标)的indexOf方法,各种字符串的截取substring方法,字符串的拼接concat方法,各种字符的替换replace方法。。。

 

接下来是重头戏,正则表达式的split方法

public String[] split(String regex, int limit) {
        /* fastpath if the regex is a
         (1)one-char String and this character is not one of the
            RegEx's meta characters ".$|()[{^?*+\\", or
         (2)two-char String and the first char is the backslash and
            the second is not the ascii digit or ascii letter.
         */
        char ch = 0;
        if (((regex.value.length == 1 &&
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substring(off, next));
                    off = next + 1;
                } else {    // last one
                    //assert (list.size() == limit - 1);
                    list.add(substring(off, value.length));
                    off = value.length;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0)
                return new String[]{this};

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, value.length));

            // Construct result
            int resultSize = list.size();
            if (limit == 0) {
                while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                    resultSize--;
                }
            }
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
        return Pattern.compile(regex).split(this, limit);
    }

先看逻辑判断条件:

1.((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) //regex长度为1并且regex不为已列出字符。

2.(regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)) //regex长度为2,第1位是转义字符,第2位是不是数字字母,ch小于a大于z,ch小于A大于Z。

3.(ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE)) //不在utf编码范围之内的。

而三者之间又以(1 || 2)&& 3的关系依赖,这些特殊情况的处理,然后就是找分割点while循环,然后分割字符串。

最后,做了这么多操作,对于一般的正则,还是调用更底层的方法来实现,也就是说,我们所用到的大部分正则匹配的都是return Pattern.compile(regex).split(this, limit);这行代码来完成的(是使用Pattern的正则方式去解析并拆分成字符串数组)。

然后是加字符串的join方法,字符串转换大小写的toUpperCase、toLowerCase方法,toString方法,转字符数组的toCharArray方法,格式化format方法,各种取值valueOf方法等。

最后一个trim()方法,删除字符串前后ascall码小于空格的字符(空格、制表符等),算法是找出首尾第一个不为空格的位置,取原字符串的子串。

public String trim() {
        int len = value.length;
        int st = 0;
        char[] val = value;    /* avoid getfield opcode */

        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值