Java-String源码解析

String定义

String类代表字符串。 Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例。
字符串不变; 它们的值在创建后不能被更改。 字符串缓冲区支持可变字符串。 因为String对象是不可变的,它们可以被共享。 例如:

String str = "abc";

相当于:

     char data[] = {'a', 'b', 'c'};
     String str = new String(data);

以下是一些如何使用字符串的示例:

     System.out.println("abc");
     String cde = "cde";
     System.out.println("abc" + cde);
     String c = "abc".substring(2,3);
     String d = cde.substring(1, 2);

String类包括用于检查序列的各个字符的方法,用于比较字符串,搜索字符串,提取子字符串以及创建将所有字符翻译为大写或小写的字符串的副本。 案例映射基于Character类指定的Unicode标准版本。

Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。 字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。 字符串转换是通过方法来实现toString ,由下式定义Object和继承由在Java中的所有类。 有关字符串连接和转换的其他信息,请参阅Gosling,Joy和Steele, Java语言规范 。

除非另有说明,否则传递null参数到此类中的构造函数或方法将导致抛出NullPointerException 。

A String表示UTF-16格式的字符串,其中补充字符由代理对表示 (有关详细信息,请参阅Character课程中的Character部分)。 索引值是指char代码单元,所以补充字符在String中使用两个String 。

String类提供处理Unicode代码点(即字符)的方法,以及用于处理Unicode代码单元(即char值)的方法。

String 类实现接口

public final class String implements Serializable, Comparable<String>, CharSequence

从该类的声明中我们可以看出String是final类型的,表示该类不能被继承,同时该类实现了三个接口。

  • Serializable 接口是为了实现类对象的序列化,主要是能把堆内存中的对象的生命周期延长,做持久化操作。当下次再需要这个对象的时候,我们不用 new了,直接从硬盘中读取就可以了。
  • Comparable 接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序 ,类的 compareTo 方法被称为它的自然比较方法 。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射表中的键或有序集合中的元素,无需指定比较器。后续会讲解 String 类中的 compareTo 方法。
  • CharSequence 是一个接口,它只包括 length(), charAt(int index), subSequence(int start, int end)这几个 API 接口。除了 String 实现了 CharSequence 之外,StringBuffer 和 StringBuilder 也实现了 CharSequence 接口。CharSequence 就是字符序列,String, StringBuilder 和 StringBuffer 本质上都是通过字符数组实现的!

属性

1、私有属性

private final char[] value;
private int hash;

由于底层 char 数组是 final 的,所以 String 对象是不可变的,且不可被继承。 value:是一个 private
final 修饰的 char 数组,String 类是通过该数组来存在字符串的。 hash:是一个 private 修饰的 int
变量,用来存放 String 对象的 hashCode。

private staticfinal long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

因为String实现了Serializable接口,所以支持序列化和反序列化支持。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

底层存储的优化

上面说的情况是JDK8及以前版本,到了JDK9,String中字符串的存储不再用char数组了,改用byte数组。

	private final byte[] value;
    private final byte coder;

不仅将char数组改为byte数组,而且新增了一个coder的成员变量。

在程序中,绝大多数字符串只包含英文字母数字等字符,使用Latin-1编码,一个字符占用一个byte。如果使用char,一个char要占用两个byte,会占用双倍的内存空间。

但是,如果字符串中使用了中文等超出Latin-1表示范围的字符,使用Latin-1就没办法表示了。这时JDK会使用UTF-16编码,那么占用的空间和旧版(使用char[])是一样的。

coder变量代表编码的格式,目前String支持两种编码格式Latin-1和UTF-16。Latin-1需要用一个字节来存储,而UTF-16需要使用2个字节或者4个字节来存储。

据说这一改进方案是JDK的开发人员用大数据和人工能智能,调研了成千上万的应用程序的heapdump信息后,得出:大部分的String都是以Latin-1字符编码来表示的,只需要一个字节存储就够了,两个字节完全是浪费。

COMPACT_STRINGS属性则是用来控制是否开启String的compact功能。默认情况下是开启的。可以使用-XX:-CompactStrings参数来对此功能进行关闭。

改进的好处
改进的好处是非常明显的,首先如果项目中使用Latin-1字符集居多,内存的占用大幅度减少,同样的硬件配置可以支撑更多的业务。

当内存减少之后,进一步导致减少GC次数,进而减少Stop-The-World的频次,同样会提升系统的性能。

2、私有类 CaseInsensitiveComparator

private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable

该类同样实现了 Comparator 和 Serializable 接口,用于 String 类对象的排序。

public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

在该类中存在一个 compare 方法,方法对两个对象的比较流程如下:循环的次数为长度最小的字符串的长度;从头开始比较每个字符,如果不相等则转换为大写再比较,再不相等转换为小写比较,最后字符之间相减。减法操作会将获取字符的 hashCode,然后相减(汉字也是如此)。如果循环过程中字符比较都相等,最后返回两个字符串对象长度的差值。

String 构造方法

1、无参构造

    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
        this.value = "".value;
    }

2、参数类型为String

    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

Java 的语法是允许在一个类中访问该类的实例对象的私有属性的。

3、参数类型为字符数组

	public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }   

同样都是用字符数组创建 String,前者是复制完整的字符数组到 String 中的 value 值,后者是从截取字符数组的一部分内容复制到 String 中。使用 Arrays.copyOf 方法或 Arrays.copyOfRange 方法进行复制,创建一个新的字符串对象,随后修改的字符数组不影响新创建的字符串。

4、参数为 int 数组

    public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= codePoints.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > codePoints.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }

        final int end = offset + count;

        // Pass 1: Compute precise size of char[]
        int n = count;
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                continue;
            else if (Character.isValidCodePoint(c))
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
        }

        // Pass 2: Allocate and fill in char[]
        final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                v[j] = (char)c;
            else
                Character.toSurrogates(c, v, j++);
        }

        this.value = v;
    }

需要注意的是:作为参数的 int 数组中值,至少需要满足“大写字母(A-Z):65 (A)~ 90(Z);小写字母(a-z):97(a) ~ 122(z);字符数字(‘0’ ~ ‘9’):48(‘0’) ~ 57(‘9’)”的条件。当数组中值为其他数字时,得到的字符串结果可能为空或特殊符号。

5、参数为字节数组

byte 是网络传输或存储的序列化形式,所以在很多传输和存储的过程中需要将 byte[] 数组和 String 进行相互转化。所以 String 提供了一系列重载的构造方法来将一个字符数组转化成 String,提到 byte[] 和 String 之间的相互转换就不得不关注编码问题。

public String(byte bytes[], Charset charset)

该构造方法是指通过 charset 来解码指定的 byte 数组,将其解码成 unicode 的 char[] 数组,构造成新的 String。
这里的 bytes 字节流是使用 charset 进行编码的,想要将他转换成 unicode 的 char[] 数组,而又保证不出现乱码,那就要指定其解码方式。
通过字节数组构造 String 有很多形式,会使用 StringCoding.decode 方法进行解码,按照是否指定解码方式分的话可以分为两种:
a、

public String(byte[] var1, int var2, int var3) {
   checkBounds(var1, var2, var3);
     this.value = StringCoding.decode(var1, var2, var3);
 }
 public String(byte[] var1) {
     this((byte[])var1, 0, var1.length);
 }

这两种构造方法没有指定编码格式,默认使用 ISO-8859-1 编码格式进行编码操作。
static char[] decode(byte[] var0, int var1, int var2) {
String var3 = Charset.defaultCharset().name();
try {
return decode(var3, var0, var1, var2);
} catch (UnsupportedEncodingException var6) {
warnUnsupportedCharset(var3);
try {
return decode(“ISO-8859-1”, var0, var1, var2);
} catch (UnsupportedEncodingException var5) {
MessageUtils.err("ISO-8859-1 charset not available: " + var5.toString());
System.exit(1);
return null;
}
}
}
b、

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

当构造方法参数中带有 charsetName 或者 charset 的时候,使用的解码的字符集就是我们指定的 charsetName 或者 charset。

6、参数为 StringBuilder 或 StringBuffer

public String(StringBuffer var1) {
        synchronized(var1) {
            this.value = Arrays.copyOf(var1.getValue(), var1.length());
        }
    }
    public String(StringBuilder var1) {
        this.value = Arrays.copyOf(var1.getValue(), var1.length());
    }

这两个构造方法是很少用到的,平时多使用 StringBuffer.toString 方法或者 StringBuilder.toString 方法。其中 StringBuffer.toString 是调用 String(char[] var1, boolean var2);而 StringBuffer.toString 则是调用 String(char[] var1, int var2, int var3)。关于效率问题,Java 的官方文档有提到说使用 StringBuilder 的 toString 方法会更快一些,原因是 StringBuffer 的 toString 方法是 synchronized 的,在牺牲了效率的情况下保证了线程安全。

7、特殊的 protected 构造方法

String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }

从代码中我们可以看出,该方法和 String(char[] value)有两点区别:
• 第一个区别:该方法多了一个参数:boolean var2,这个参数上述我们也提到过,在 StringBuffer.toString 方法中有使用过,当时传值为 true。但是实际使用过程中可以通过该构造函数直接进行字符串对象的生成,加入这个 var2 的可以有效区分于 String(char[] value) 方法,不加这个参数就没办法定义这个函数,只有参数是不同才能进行重载。
• 第二个区别:具体的方法实现不同。我们前面提到过 String(char[] value) 方法在创建 String 的时候会用到 Arrays 的 copyOf 方法将 value 中的内容逐一复制到 String 当中,而这个 String(char[] var1, boolean var2) 方法则是直接将 var1的引用赋值给 String 的 value。那么也就是说,这个方法构造出来的 String 和参数传过来的 char[] var1共享同一个数组。
在网上看到说是有两点区别,关于第二点区别,关键在于调用方调用该构造方法前是怎么处理的,在 StringBuffer.toString 方法中可以详细的看出 new String(char[] var1, boolean var2) 的使用。

public synchronized String toString() {
        if (this.toStringCache == null) {
            this.toStringCache = Arrays.copyOfRange(this.value, 0, this.count);
        }
        return new String(this.toStringCache, true);
    }

其中 toStringCache 表示缓存,用来保存上一次调用 toString 的结果,如果 value 的字符串序列发生改变,就会将它清空。首先判断 toStringCache 是否为 null,如果是先将 value 通过 Arrays.copyOfRange 方法复制到缓存里,然后使用 toStringCache new一个 String。
综上,我认为该构造方法虽然无法拿来直接使用,可是在别的地方可以使用,比如说 StringBuffer.toString 方法中。至于为何要再构建这么一个特殊的构造方法,而不是直接使用 String(char[] value) 方法,原因在于 StringBuffer 中的 toStringCache 属性存在,它的意义不支持在 toString 方法中直接使用 String(char[] value) 方法。(该部分仅为个人观点,如有差错,请指正!)
为什么 Java 会提供这样一个方法呢?
• 性能好:一个是直接给数组赋值(如果 StringBuffer 的 value 字符串序列不发生改变,仅需要复制一次即可),一个是逐一拷贝,当然是直接赋值快了。
• 节约内存:该方法之所以设置为 protected,是因为一旦该方法设置为公有,在外面可以访问的话,如果构造方法没有对 arr 进行拷贝,那么其他人就可以在字符串外部修改该数组,由于它们引用的是同一个数组,因此对 arr 的修改就相当于修改了字符串,那就破坏了字符串的不可变性。
• 安全的:对于调用他的方法来说,由于无论是原字符串还是新字符串,其 value 数组本身都是 String 对象的私有属性,从外部是无法访问的,因此对两个字符串来说都很安全。
讲到这里,就需要讲下 String 类中还有没有其他的方法像这个构造函数那样“性能好的、节约内存的、安全”。其实在 Java7 之前 String 类中也有很多这样的方法,比如 substring,replace,concat,valueOf 等方法,实际上它们使用的是 String(int var1, int var2, char[] var3)方法来实现。
但是在 Java 7 之后,substring 已经不再使用这种“优秀”的方法了,以下是 Java8 中的源码:

public String substring(int var1) {
        if (var1 < 0) {
            throw new StringIndexOutOfBoundsException(var1);
        } else {
            int var2 = this.value.length - var1;
            if (var2 < 0) {
                throw new StringIndexOutOfBoundsException(var2);
            } else {
                return var1 == 0 ? this : new String(this.value, var1, var2);
            }
        }
    }

Java8 中 substring 方法涉及到的 new String 源码如下:

public String(char[] var1, int var2, int var3) {
        if (var2 < 0) {
            throw new StringIndexOutOfBoundsException(var2);
        } else {
            if (var3 <= 0) {
                if (var3 < 0) {
                    throw new StringIndexOutOfBoundsException(var3);
                }
                if (var2 <= var1.length) {
                    this.value = "".value;
                    return;
                }
            }
            if (var2 > var1.length - var3) {
                throw new StringIndexOutOfBoundsException(var2 + var3);
            } else {
                this.value = Arrays.copyOfRange(var1, var2, var2 + var3);
            }
        }
    }

会对传进来的 value 值通过 Arrays.copyOfRange 方法进行拷贝。
反观 Java6 中 substring 方法涉及到的 new String 源码如下:

String(int var1, int var2, char[] var3) {
        this.value = var3;
        this.offset = var1;
        this.count = var2;
    }

同 new String(char[] var1, boolean var2) 构造函数的第二个特点一样,构造出来的 String 和参数传过来的 char[] value 共享同一个数组 。这就可能会造成内存泄漏问题。
看一个例子,假设一个方法从某个地方(文件、数据库或网络)取得了一个很长的字符串,然后对其进行解析并提取其中的一小段内容,这种情况经常发生在网页抓取或进行日志分析的时候。
下面是示例代码:

String aLongString = "...averylongstring...";
String aPart = aLongString .substring(20, 40);    //aPart字符串共享aLongString部分数据
return aPart;

在这里 aLongString 只是临时的,真正有用的是 aPart,其长度只有 20 个字符,但是它的内部数组却是从 aLongString 那里共享的,因此虽然 aLongString 本身可以被回收,但它的内部数组却不能释放。这就导致了内存泄漏。如果一个程序中这种情况经常发生有可能会导致严重的后果,如内存溢出,或性能下降。
新的实现虽然损失了性能,而且浪费了一些存储空间,但却保证了字符串的内部数组可以和字符串对象一起被回收,从而防止发生内存泄漏,因此新的 substring 比原来的更健壮。

其他方法

	public int length() {//返回字符串长度
        return value.length;
    }
    public boolean isEmpty() {//返回字符串是否为空
        return value.length == 0;
    }
    public char charAt(int index) {//返回字符串中第(index+1)个字符(数组索引)
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }
    public int codePointAt(int index) {//返回字符串中指定索引处字符的Unicode值
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }
    public int codePointBefore(int index) {//返回指定索引之前字符的Unicode
        int i = index - 1;
        if ((i < 0) || (i >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointBeforeImpl(value, index, 0);
    }
    public int codePointCount(int beginIndex, int endIndex) {//返回此字符串指定文本范围内的Unicode
        if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
            throw new IndexOutOfBoundsException();
        }
        return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
    }

    public char[] toCharArray() {//转化成字符数组
        // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
    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;
    }
	public String toUpperCase() {//转化为大写
        return toUpperCase(Locale.getDefault());
    }
        public String toUpperCase(Locale locale) {
        if (locale == null) {
            throw new NullPointerException();
        }

        int firstLower;
        final int len = value.length;

        /* Now check if there are any characters that need to be changed. */
        scan: {
            for (firstLower = 0 ; firstLower < len; ) {
                int c = (int)value[firstLower];
                int srcCount;
                if ((c >= Character.MIN_HIGH_SURROGATE)
                        && (c <= Character.MAX_HIGH_SURROGATE)) {
                    c = codePointAt(firstLower);
                    srcCount = Character.charCount(c);
                } else {
                    srcCount = 1;
                }
                int upperCaseChar = Character.toUpperCaseEx(c);
                if ((upperCaseChar == Character.ERROR)
                        || (c != upperCaseChar)) {
                    break scan;
                }
                firstLower += srcCount;
            }
            return this;
        }

        /* result may grow, so i+resultOffset is the write location in result */
        int resultOffset = 0;
        char[] result = new char[len]; /* may grow */

        /* Just copy the first few upperCase characters. */
        System.arraycopy(value, 0, result, 0, firstLower);

        String lang = locale.getLanguage();
        boolean localeDependent =
                (lang == "tr" || lang == "az" || lang == "lt");
        char[] upperCharArray;
        int upperChar;
        int srcChar;
        int srcCount;
        for (int i = firstLower; i < len; i += srcCount) {
            srcChar = (int)value[i];
            if ((char)srcChar >= Character.MIN_HIGH_SURROGATE &&
                (char)srcChar <= Character.MAX_HIGH_SURROGATE) {
                srcChar = codePointAt(i);
                srcCount = Character.charCount(srcChar);
            } else {
                srcCount = 1;
            }
            if (localeDependent) {
                upperChar = ConditionalSpecialCasing.toUpperCaseEx(this, i, locale);
            } else {
                upperChar = Character.toUpperCaseEx(srcChar);
            }
            if ((upperChar == Character.ERROR)
                    || (upperChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) {
                if (upperChar == Character.ERROR) {
                    if (localeDependent) {
                        upperCharArray =
                                ConditionalSpecialCasing.toUpperCaseCharArray(this, i, locale);
                    } else {
                        upperCharArray = Character.toUpperCaseCharArray(srcChar);
                    }
                } else if (srcCount == 2) {
                    resultOffset += Character.toChars(upperChar, result, i + resultOffset) - srcCount;
                    continue;
                } else {
                    upperCharArray = Character.toChars(upperChar);
                }

                /* Grow result if needed */
                int mapLen = upperCharArray.length;
                if (mapLen > srcCount) {
                    char[] result2 = new char[result.length + mapLen - srcCount];
                    System.arraycopy(result, 0, result2, 0, i + resultOffset);
                    result = result2;
                }
                for (int x = 0; x < mapLen; ++x) {
                    result[i + resultOffset + x] = upperCharArray[x];
                }
                resultOffset += (mapLen - srcCount);
            } else {
                result[i + resultOffset] = (char)upperChar;
            }
        }
        return new String(result, 0, len + resultOffset);
    }
public String toLowerCase() {//转化为小写
        return toLowerCase(Locale.getDefault());
    }
public boolean matches(String regex) {//判断字符串是否匹配给定的regex正则表达式
        return Pattern.matches(regex, this);
    }
public boolean contains(CharSequence s) {//判断字符串是否包含字符序列 s
        return indexOf(s.toString()) > -1;
    }
public String[] split(String regex, int limit) {//按照字符 regex将字符串分成 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);
    }
public String[] split(String regex) {//按照字符 regex 将字符串分段
        return split(regex, 0);
    }

1、concat 连接函数

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

拼接 str会生成一个新的字符串对象,对原有字符串无影响。

2、getBytes

byte[] getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
byte[] getBytes(String var1)     使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
byte[] getBytes(Charset var1)        使用给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
void getBytes(int var1, int var2, byte[] var3, int var4)    已过时

值得注意的是,在使用这些方法的时候一定要注意编码问题。比如:String s = “你好,世界!”; byte[] bytes = s.getBytes();这段代码在不同的平台上运行得到结果是不一样的。由于没有指定编码方式,所以在该方法对字符串进行编码的时候就会使用系统的默认编码方式。
在中文操作系统中可能会使用 GBK 或者 GB2312 进行编码,在英文操作系统中有可能使用 iso-8859-1 进行编码。这样写出来的代码就和机器环境有很强的关联性了,为了避免不必要的麻烦,要指定编码方式。

3、equals 和 hashCode

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

在 Java 中基于各种数据类型分析 == 和 equals 的区别一节中讲到 String 类有重写自己的 equals 和 hashCode 方法,所以它俩需要一起讲述。
首先是 equals 方法,它比较的流程是:字符串对象相同(即自我比较);类型一致且长度相等时,比较字符内容是否相同。
然后是 hashCode 方法,如果 hash 值不等于 0,且 value.length 大于 0,则进行 hash 值计算。这里重点说下 h == 0 这一判定条件,h 是一个 int 类型的值,默认值为 0,因此 0 可以表示可能未执行过 hash 计算,但不能表示一定未执行过 hash 计算,原因是我们现在还不确定 hash 计算后是否会产生 0 值;
执行 hash 计算后,会不会产生值为 0 的 hash呢?根据 hash 的计算逻辑,当 val2[0] = 0 时,根据公式 h = 31 *h + val[i]; 进行计算, h 的值等于 0。但是经过查询 ASCII 表发现,null 的 ASCII 值为 0 。显然 val[0]中永远不可能存放 null,因此 hash 计算后不会产生 0 值, h== 0 可以作为是否进行过 hash 计算的判定条件。
最后得到计算公式为:

val[0]*31^(n-1) + val[1]*31^(n-2) + ... + val[n-1]  

为什么要使用这个公式,就是在存储数据计算 hash 地址的时候,我们希望尽量减少有同样的 hash 地址。如果使用相同 hash 地址的数据过多,那么这些数据所组成的 hash 链就更长,从而降低了查询效率。
所以在选择系数的时候要选择尽量长的系数并且让乘法尽量不要溢出的系数,因为如果计算出来的 hash 地址越大,所谓的“冲突”就越少,查找起来效率也会提高。

4、比较方法

boolean equals(Object anObject); 比较对象
boolean contentEquals(StringBuffer sb); 与StringBuffer对象比较内容
boolean contentEquals(CharSequence var1); 与字符比较内容
boolean equalsIgnoreCase(String anotherString);忽略大小写比较字符串对象
int compareTo(String anotherString); 比较字符串
int compareToIgnoreCase(String str); 忽略大小写比较字符串
boolean regionMatches(int toffset, String other, int ooffset, int len)局部匹配
boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) 可忽略大小写局部匹配

contentEquals 方法

public boolean contentEquals(CharSequence var1) {
        if (var1 instanceof AbstractStringBuilder) {
            if (var1 instanceof StringBuffer) {
                synchronized(var1) {
                    return this.nonSyncContentEquals((AbstractStringBuilder)var1);
                }
            } else {
                return this.nonSyncContentEquals((AbstractStringBuilder)var1);
            }
        } else if (var1 instanceof String) {
            return this.equals(var1);
        } else {
            char[] var2 = this.value;
            int var3 = var2.length;
            if (var3 != var1.length()) {
                return false;
            } else {
                for(int var4 = 0; var4 < var3; ++var4) {
                    if (var2[var4] != var1.charAt(var4)) {
                        return false;
                    }
                }
                return true;
            }
        }
    }

String 、StringBuilder、StringBuffer 都实现了 CharSequence 接口,所以上述方法可以接收这三种类型的参数。另外 StringBuilder、StringBuffer 继承了 AbstractStringBuilder 父类,所以它俩通过 nonSyncContentEquals 方法进行比较,注意 StringBuffer 需要考虑线程安全,加锁之后再调用。
compareTo 方法

public int compareTo(String var1) {
        int var2 = this.value.length;
        int var3 = var1.value.length;
        int var4 = Math.min(var2, var3);
        char[] var5 = this.value;
        char[] var6 = var1.value;
        for(int var7 = 0; var7 < var4; ++var7) {
            char var8 = var5[var7];
            char var9 = var6[var7];
            if (var8 != var9) {
                return var8 - var9;
            }
        }
        return var2 - var3;
    }

当两个对象内容完全一致时,返回结果为 0;在字符串最小长度下,如果有不同的字符,系统则自动转换为 int 类型做差值。

5、前后缀判定

boolean startsWith(String var1, int var2)     测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean startsWith(String var1)     测试此字符串是否以指定的前缀开始。
boolean endsWith(String var1)        测试此字符串是否以指定的后缀结束。

6、索引获取

int indexOf(int var1)        返回指定字符(intchar)在此字符串中第一次出现处的索引。从0索引开始
int indexOf(int var1, int var2)    返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。var2 小于字符串的长度
int indexOf(String var1)        返回指定子字符串在此字符串中第一次出现处的索引,从0索引开始。var1必须是此字符串的一个连续子集,否则返回-1
int indexOf(String var1, int var2)        返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
int lastIndexOf(int var1)    返回指定字符(intchar)在此字符串中最后一次出现处的索引。从0索引开始
int lastIndexOf(int var1, int var2)    返回在此字符串中最后一次出现指定字符处的索引,从指定的索引开始搜索。var2 小于字符串的长度
int lastIndexOf(String var1)    返回指定子字符串在此字符串中最后一次出现处的索引,从0索引开始。var1必须是此字符串的一个连续子集,否则返回-1
int lastIndexOf(String var1, int var2)    返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始。

7、字符串截取

String substring(int var1)    返回一个新的字符串,它是此字符串的一个子字符串。从var1开始截取,截取长度为此字符串的长度减去var1
String substring(int beginIndex, int endIndex)    返回一个新字符串,它是此字符串的一个子字符串。从beginIndex开始截取,截取长度为endIndex-beginIndex。
CharSequence subSequence(int var1, int var2)        返回一个新的字符序列,它是此序列的一个子序列。

8、字符串替换

String replace(char oldChar, char newChar)     返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replaceFirst(String regex, String replacement)         使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
String replaceAll(String regex, String replacement)     使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replace(CharSequence var1, CharSequence var2)    使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
复制代码

9、valueOf

static String valueOf(Object var0)
static String valueOf(char[] var0)
static String valueOf(char[] var0, int var1, int var2)
static String valueOf(boolean var0)
static String valueOf(int var0)
static String valueOf(long var0)
static String valueOf(float var0)
static String valueOf(double var0)

valueOf 都是静态函数,不需要实例化 String 对象,直接调用用于将其他基本数据类型转换为 String 类型。

10、intern()方法

public native String intern();

intern 方法是 Native 调用,它的作用是在全局字符串常量池里寻找等值的对象的引用,如果没有找到则在常量池中存放当前字符串对象的引用并返回该引用,否则直接返回常量池中已存在的 String 对象引用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值