从JDK源码看String(下)

撸了今年阿里、腾讯和美团的面试,我有一个重要发现…….

作者:超人汪小建(seaboat)

出处:https://blog.csdn.net/wangyangzhizhou/column/info/16032


lastIndexOf方法

该方法用于返回指定字符在此字符串中最后一次出现处的索引,有多种方法参数。可传入 int 类型,也可传入 String 类型,另外还能传入开始位置。根据编码的不同分别用 Latin1 和 UTF16 两种方式处理。

    public int lastIndexOf(int ch) {
            return lastIndexOf(ch, length() - 1);
        }

    public int lastIndexOf(int ch, int fromIndex) {
            return isLatin1() ? StringLatin1.lastIndexOf(value, ch, fromIndex)
                              : StringUTF16.lastIndexOf(value, ch, fromIndex);
        }

    public int lastIndexOf(String str) {
            return lastIndexOf(str, length());
        }

    public int lastIndexOf(String str, int fromIndex) {
            return lastIndexOf(value, coder(), length(), str, fromIndex);
        }

    static int lastIndexOf(byte[] src, byte srcCoder, int srcCount,
                               String tgtStr, int fromIndex) {
            byte[] tgt = tgtStr.value;
            byte tgtCoder = tgtStr.coder();
            int tgtCount = tgtStr.length();
            int rightIndex = srcCount - tgtCount;
            if (fromIndex > rightIndex) {
                fromIndex = rightIndex;
            }
            if (fromIndex < 0) {
                return -1;
            }
            if (tgtCount == 0) {
                return fromIndex;
            }
            if (srcCoder == tgtCoder) {
                return srcCoder == LATIN1
                    ? StringLatin1.lastIndexOf(src, srcCount, tgt, tgtCount, fromIndex)
                    : StringUTF16.lastIndexOf(src, srcCount, tgt, tgtCount, fromIndex);
            }
            if (srcCoder == LATIN1) {   
                return -1;
            }
            return StringUTF16.lastIndexOfLatin1(src, srcCount, tgt, tgtCount, fromIndex);
        }

Latin1 编码的逻辑为,
* 判断 int 值是否能转成 byte,方法是看右移8位是否为0,为0即说明除了低8位其他都为0。
* 通过Math.min(fromIndex, value.length - 1)取偏移值。
* 从偏移处开始往前遍历查找,找到即返回索引值。
* 找不到返回-1。

    public static int lastIndexOf(final byte[] value, int ch, int fromIndex) {
            if (!canEncode(ch)) {
                return -1;
            }
            int off  = Math.min(fromIndex, value.length - 1);
            for (; off >= 0; off--) {
                if (value[off] == (byte)ch) {
                    return off;
                }
            }
            return -1;
        }

类似地,对于 UTF16 编码也做类似处理,但因为 unicode 包含了基本多语言平面(Basic Multilingual Plane,BMP)外,还存在补充平面。而传入的值为 int 类型(4字节),所以如果超出 BMP 平面,此时需要4个字节,分别用来保存 High-surrogate 和 Low-surrogate,此时就需要对比4个字节。

另外,如果查找子字符串则是从子字符串第一个字符开始匹配直到子字符串完全被匹配成功。

substring方法

该方法用于获取字符串的指定子字符串。有两个方法,一个是只传入开始索引,第二个是出传入开始索引和结束索引。逻辑通过源码已经很清晰了,先计算截取的子字符串长度,然后分别根据 Latin1 和 UTF16 两种方式生成新的 String 对象。

    public String substring(int beginIndex) {
            if (beginIndex < 0) {
                throw new StringIndexOutOfBoundsException(beginIndex);
            }
            int subLen = length() - beginIndex;
            if (subLen < 0) {
                throw new StringIndexOutOfBoundsException(subLen);
            }
            if (beginIndex == 0) {
                return this;
            }
            return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
                              : StringUTF16.newString(value, beginIndex, subLen);
        }

    public String substring(int beginIndex, int endIndex) {
            int length = length();
            checkBoundsBeginEnd(beginIndex, endIndex, length);
            int subLen = endIndex - beginIndex;
            if (beginIndex == 0 && endIndex == length) {
                return this;
            }
            return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
                              : StringUTF16.newString(value, beginIndex, subLen);
        }

    public static String newString(byte[] val, int index, int len) {
            return new String(Arrays.copyOfRange(val, index, index + len),
                              LATIN1);
        }

    public static String newString(byte[] val, int index, int len) {
            if (String.COMPACT_STRINGS) {
                byte[] buf = compress(val, index, len);
                if (buf != null) {
                    return new String(buf, LATIN1);
                }
            }
            int last = index + len;
            return new String(Arrays.copyOfRange(val, index << 1, last << 1), UTF16);
        }

subSequence 方法

等同substring方法。

    public CharSequence subSequence(int beginIndex, int endIndex) {
            return this.substring(beginIndex, endIndex);
        }

concat方法

该方法用于将指定的字符串参数连接到字符串上。逻辑为,
* 获取待连接字符串长度,如果长度为0则直接返回本身。
* 两者编码如果相同,则直接通过System.arraycopy进行拷贝并返回新的 String 对象。
* 如果编码不同,则使用 UTF16 编码分别将二者的值拷贝到字节数组上并返回新的 String 对象。

    public String concat(String str) {
            int olen = str.length();
            if (olen == 0) {
                return this;
            }
            if (coder() == str.coder()) {
                byte[] val = this.value;
                byte[] oval = str.value;
                int len = val.length + oval.length;
                byte[] buf = Arrays.copyOf(val, len);
                System.arraycopy(oval, 0, buf, val.length, oval.length);
                return new String(buf, coder);
            }
            int len = length();
            byte[] buf = StringUTF16.newBytesFor(len + olen);
            getBytes(buf, 0, UTF16);
            str.getBytes(buf, len, UTF16);
            return new String(buf, UTF16);
        }

replace方法

该方法用于替换字符串中指定的字符,分两种编码处理。

    public String replace(char oldChar, char newChar) {
            if (oldChar != newChar) {
                String ret = isLatin1() ? StringLatin1.replace(value, oldChar, newChar)
                                        : StringUTF16.replace(value, oldChar, newChar);
                if (ret != null) {
                    return ret;
                }
            }
            return this;
        }
    public static String replace(byte[] value, char oldChar, char newChar) {
            if (canEncode(oldChar)) {
                int len = value.length;
                int i = -1;
                while (++i < len) {
                    if (value[i] == (byte)oldChar) {
                        break;
                    }
                }
                if (i < len) {
                    if (canEncode(newChar)) {
                        byte buf[] = new byte[len];
                        for (int j = 0; j < i; j++) {   
                            buf[j] = value[j];
                        }
                        while (i < len) {
                            byte c = value[i];
                            buf[i] = (c == (byte)oldChar) ? (byte)newChar : c;
                            i++;
                        }
                        return new String(buf, LATIN1);
                    } else {
                        byte[] buf = StringUTF16.newBytesFor(len);
                        inflate(value, 0, buf, 0, i);
                        while (i < len) {
                            char c = (char)(value[i] & 0xff);
                            StringUTF16.putChar(buf, i, (c == oldChar) ? newChar : c);
                            i++;
                        }
                        return new String(buf, UTF16);
                    }
                }
            }
            return null;
        }
    public String replace(CharSequence target, CharSequence replacement) {
            String tgtStr = target.toString();
            String replStr = replacement.toString();
            int j = indexOf(tgtStr);
            if (j < 0) {
                return this;
            }
            int tgtLen = tgtStr.length();
            int tgtLen1 = Math.max(tgtLen, 1);
            int thisLen = length();

            int newLenHint = thisLen - tgtLen + replStr.length();
            if (newLenHint < 0) {
                throw new OutOfMemoryError();
            }
            StringBuilder sb = new StringBuilder(newLenHint);
            int i = 0;
            do {
                sb.append(this, i, j).append(replStr);
                i = j + tgtLen;
            } while (j < thisLen && (j = indexOf(tgtStr, j + tgtLen1)) > 0);
            return sb.append(this, i, thisLen).toString();
        }

replaceFirst和replaceAll

都是用正则去实现。

    public String replaceFirst(String regex, String replacement) {
            return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
        }

    public String replaceAll(String regex, String replacement) {
            return Pattern.compile(regex).matcher(this).replaceAll(replacement);
        }

split方法

该方式用于切分字符串,实现中首先会判断能不能不用正则引擎,如果可以则直接切分,否则采用正则引擎切分。

    public String[] split(String regex) {
            return split(regex, 0);
        }

    public String[] split(String regex, int limit) {
            char ch = 0;
            if (((regex.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 {    
                        int last = length();
                        list.add(substring(off, last));
                        off = last;
                        break;
                    }
                }
                if (off == 0)
                    return new String[]{this};

                if (!limited || list.size() < limit)
                    list.add(substring(off, length()));

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

join方法

用某个分隔符将字符串数组连接起来。主要是通过 StringJoiner 类来实现。

    public static String join(CharSequence delimiter, CharSequence... elements) {
            Objects.requireNonNull(delimiter);
            Objects.requireNonNull(elements);
            StringJoiner joiner = new StringJoiner(delimiter);
            for (CharSequence cs: elements) {
                joiner.add(cs);
            }
            return joiner.toString();
        }

    public static String join(CharSequence delimiter,
                Iterable<? extends CharSequence> elements) {
            Objects.requireNonNull(delimiter);
            Objects.requireNonNull(elements);
            StringJoiner joiner = new StringJoiner(delimiter);
            for (CharSequence cs: elements) {
                joiner.add(cs);
            }
            return joiner.toString();
        }

StringJoiner 的 add 方法和 toString 方法如下。add 方法主要逻辑就是将每个字符串赋值到字符串数组,并且要将分隔符的长度累加起来。toString 方法主要是将字符串数组和分隔符连接起来并返回最终的字符串。

    public StringJoiner add(CharSequence newElement) {
            final String elt = String.valueOf(newElement);
            if (elts == null) {
                elts = new String[8];
            } else {
                if (size == elts.length)
                    elts = Arrays.copyOf(elts, 2 * size);
                len += delimiter.length();
            }
            len += elt.length();
            elts[size++] = elt;
            return this;
        }
    public String toString() {
            final String[] elts = this.elts;
            if (elts == null && emptyValue != null) {
                return emptyValue;
            }
            final int size = this.size;
            final int addLen = prefix.length() + suffix.length();
            if (addLen == 0) {
                compactElts();
                return size == 0 ? "" : elts[0];
            }
            final String delimiter = this.delimiter;
            final char[] chars = new char[len + addLen];
            int k = getChars(prefix, chars, 0);
            if (size > 0) {
                k += getChars(elts[0], chars, k);
                for (int i = 1; i < size; i++) {
                    k += getChars(delimiter, chars, k);
                    k += getChars(elts[i], chars, k);
                }
            }
            k += getChars(suffix, chars, k);
            return jla.newStringUnsafe(chars);
        }

    private void compactElts() {
            if (size > 1) {
                final char[] chars = new char[len];
                int i = 1, k = getChars(elts[0], chars, 0);
                do {
                    k += getChars(delimiter, chars, k);
                    k += getChars(elts[i], chars, k);
                    elts[i] = null;
                } while (++i < size);
                size = 1;
                elts[0] = jla.newStringUnsafe(chars);
            }
        }

toLowerCase方法

用于转成小写,需要分两种编码处理,下面看 Latin1 编码的处理逻辑,

    public String toLowerCase(Locale locale) {
            return isLatin1() ? StringLatin1.toLowerCase(this, value, locale)
                              : StringUTF16.toLowerCase(this, value, locale);
        }

    public String toLowerCase() {
            return toLowerCase(Locale.getDefault());
        }
  • 遍历字节数组,分别转成 int 类型值,然后通过Character.toLowerCase检查是否全部为小写,如果是则直接返回字符串本身。
  • 如果为(lang == "tr" || lang == "az" || lang == "lt")三者语言,则额外处理,因为不在 Latin1 编码中。
  • 正常情况下先用System.arraycopy赋值一个新数组,然后通过遍历源数组,将一个个字符转成小写再赋给新数组。
  • 根据新数组创建新 String 对象并返回。
    public static String toLowerCase(String str, byte[] value, Locale locale) {
            if (locale == null) {
                throw new NullPointerException();
            }
            int first;
            final int len = value.length;
            for (first = 0 ; first < len; first++) {
                int cp = value[first] & 0xff;
                if (cp != Character.toLowerCase(cp)) {
                    break;
                }
            }
            if (first == len)
                return str;
            String lang = locale.getLanguage();
            if (lang == "tr" || lang == "az" || lang == "lt") {
                return toLowerCaseEx(str, value, first, locale, true);
            }
            byte[] result = new byte[len];
            System.arraycopy(value, 0, result, 0, first);  
            for (int i = first; i < len; i++) {
                int cp = value[i] & 0xff;
                cp = Character.toLowerCase(cp);
                if (!canEncode(cp)) {                      
                    return toLowerCaseEx(str, value, first, locale, false);
                }
                result[i] = (byte)cp;
            }
            return new String(result, LATIN1);
        }

toUpperCase方法

用于将字符串成转成大写,实现逻辑与上面的转小写一样。

    public String toUpperCase(Locale locale) {
            return isLatin1() ? StringLatin1.toUpperCase(this, value, locale)
                              : StringUTF16.toUpperCase(this, value, locale);
        }

    public String toUpperCase() {
            return toUpperCase(Locale.getDefault());
        }

trim方法

用于删除字符串的头尾空白符,分两种编码处理,以 Latin1 为例,

    public String trim() {
            String ret = isLatin1() ? StringLatin1.trim(value)
                                    : StringUTF16.trim(value);
            return ret == null ? this : ret;
        }
  • 获取字节数组长度。
  • 遍历看需要在字符串开头跳过多少个字符,小于等于空格 ASCII 值的都算在内。
  • 遍历看需要在字符串尾部跳过多少个字符。
  • 根据开头结尾跳过的空格数来创建新的 String 对象。
    public static String trim(byte[] value) {
            int len = value.length;
            int st = 0;
            while ((st < len) && ((value[st] & 0xff) <= ' ')) {
                st++;
            }
            while ((st < len) && ((value[len - 1] & 0xff) <= ' ')) {
                len--;
            }
            return ((st > 0) || (len < value.length)) ?
                newString(value, st, len - st) : null;
        }

toString方法

直接返回 this。

    public String toString() {
            return this;
        }

toCharArray方法

将字符串转成 char 数组,分两种编码处理,以 Latin1 为例,核心就在(char)(src[srcOff++] & 0xff)

    public char[] toCharArray() {
            return isLatin1() ? StringLatin1.toChars(value)
                              : StringUTF16.toChars(value);
        }
    public static char[] toChars(byte[] value) {
            char[] dst = new char[value.length];
            inflate(value, 0, dst, 0, value.length);
            return dst;
        }

    public static void inflate(byte[] src, int srcOff, char[] dst, int dstOff, int len) {
            for (int i = 0; i < len; i++) {
                dst[dstOff++] = (char)(src[srcOff++] & 0xff);
            }
        }

format方法

格式化字符串,通过 Formatter 来实现。

    public static String format(String format, Object... args) {
            return new Formatter().format(format, args).toString();
        }

    public static String format(Locale l, String format, Object... args) {
            return new Formatter(l).format(format, args).toString();
        }

valueOf方法

用于将传入的对象转成 String 对象,可传入多种类型参数。
* Objet 时,为空则返回”null”字符串,否则obj.toString()
* char 数组时,直接new 一个 String 对象。
* boolean 时,返回”true” 或 “false”字符串。
* char 时,优先尝试转成 Latin1 编码的 String 读,否则用 UTF16。
* int 时,Integer.toString(i)
* long 时,Long.toString(l)
* float 时,Float.toString(f)
* double 时,Double.toString(d)

    public static String valueOf(Object obj) {
            return (obj == null) ? "null" : obj.toString();
        }

    public static String valueOf(char data[]) {
            return new String(data);
        }

    public static String valueOf(char data[], int offset, int count) {
            return new String(data, offset, count);
        }

    public static String valueOf(boolean b) {
            return b ? "true" : "false";
        }

    public static String valueOf(char c) {
            if (COMPACT_STRINGS && StringLatin1.canEncode(c)) {
                return new String(StringLatin1.toBytes(c), LATIN1);
            }
            return new String(StringUTF16.toBytes(c), UTF16);
        }

    public static String valueOf(int i) {
            return Integer.toString(i);
        }

    public static String valueOf(long l) {
            return Long.toString(l);
        }

    public static String valueOf(float f) {
            return Float.toString(f);
        }

    public static String valueOf(double d) {
            return Double.toString(d);
        }

intern方法

一个 native 方法,具体实现可看前面的文章《[深入谈谈String.intern()在JVM的实现][String.intern_JVM]》

    public native String intern();

getBytes方法

用于复制指定的字节数组,主要是通过System.arraycopy来实现。但如果目标数组为 UTF16 编码,则需将高位和低位都赋值到字节数组。

    void getBytes(byte dst[], int dstBegin, byte coder) {
            if (coder() == coder) {
                System.arraycopy(value, 0, dst, dstBegin << coder, value.length);
            } else {
                StringLatin1.inflate(value, 0, dst, dstBegin, value.length);
            }
        }

    public static void inflate(byte[] src, int srcOff, byte[] dst, int dstOff, int len) {
            checkBoundsOffCount(dstOff, len, dst);
            for (int i = 0; i < len; i++) {
                putChar(dst, dstOff++, src[srcOff++] & 0xff);
            }
        }

    static void putChar(byte[] val, int index, int c) {
            index <<= 1;
            val[index++] = (byte)(c >> HI_BYTE_SHIFT);
            val[index]   = (byte)(c >> LO_BYTE_SHIFT);
        }

coder方法

获取字符串的编码,如果使用非紧凑布局则一定为 UTF16,否则可能为 Latin1 或 UTF16。

    byte coder() {
            return COMPACT_STRINGS ? coder : UTF16;
        }

isLatin1方法

判断是否为 Latin1 编码。必须是紧凑布局且为 LATIN1 才属于 Latin1 编码。

    private boolean isLatin1() {
            return COMPACT_STRINGS && coder == LATIN1;
        }
赞(1) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » 从JDK源码看String(下)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏