String实现的接口


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

final

String 在类上修饰了final关键字,这也就是说明,不论你的String 变量如何做修改,原地址上的String对象不会做任何操作,所有的String对象修改,都可以理解为重新生成了一个新的对象。

java.io.Serializable(序列化)

Serializable 接口是序列化。序列化是将对象变为可传输内容的过程, 反序列化则是将可传输内容转化为对象的过程。

Java原生序列化是通过IO包中的ObjectInputStream和ObjectOutputStream实现的。ObjectOutputStream类负责实现序列化, ObjectInputStream类负责实现反序列化。

仅需知道序列化是为了在内存将对象状态保存即可。

该这个序列化接口没有任何方法和域。应该仅仅是标识语义(猜测)

Comparable<String>(可比较)

定义了int compareTo(T o)方法。

CharSequence(字符序列)

定义如下方法

int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
String toString();
default IntStream chars();
default IntStream codePoints()

注意: IntStream为jdk1.8补充的功能

主要变量


private final char value[];

很明显value是用来存放String 值的一个字符数组,比如 String str = "123" ,实质上是将 str 存放在一个char[] 数组中

private int hash; // Default to 0

hash为hashcode()一个缓存,否则每次都需要计算一下hashcode

private static final long serialVersionUID = -6849794470754667710L;

serialVersionUID序列化唯一性,避免对象不一致。

private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

serialPersistentFields只是一个序列化代码。

构造函数


无参构造函数

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

可以看出,如果 String str = new String(); 将会得到一个value为空的字符串;

String类型初始化

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

由此处可以看出,String类型初始化对象的value和hash值都是直接拷贝,例子如下

String str = "a123";

String str2 = "a123"

String a = new String("a123");

String b = new String("a123");

String c = new String("a123");

String d = new String("a123");

结合JVM理解,由于使用了new 关键字,一定会在堆中开辟新的内存空间,但是由于Java中根本就不存在两个完全一样的字符串对象,所以这几个之间的关系应该是

  1. 栈a --> 堆a --> 拷贝自字符串常量池 a123
  2. 栈b --> 堆b --> 拷贝自字符串常量池 a123
  3. 栈c --> 堆c --> 拷贝自字符串常量池 a123
  4. 栈d --> 堆d --> 拷贝自字符串常量池 a123
  5. 栈str --> 字符串常量池 a123
  6. 栈str2 --> 字符串常量池 a123

但是由于abcd4个对象都是由 a123的valuehash进行拷贝的值,所以

1)java中的==是内存地址的比较,所以a,b,c,d 互不相等,但是str和str是相等的

2)equals是比较字符串中的valuehash,所以a,b,c,d,str,str2都相等

字符数组初始化

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

将字符数组char[]拷贝给同为字符数组的value

字符数组初始化2

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

该方法的参数意义
char value[]:一个字符数组
int offset:这个数组的指定下标
int count:向后数几位
也就是说,String({'a','b','c'}, 1, 2),则是以{'a','b','c'}下标为[1]的值(‘b’)开始,数2位数的值,即为{'b','c'}数组为value生成字符串。

通过Unicode码位数组来创造字符串

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

这个方法其实是对照Unicode表,来根据int数组在Unicode表的位置,来生成字符数组。

字节数组构造函数

public String(byte ascii[], int hibyte, int offset, int count)
 
 public String(byte ascii[], int hibyte)

这两个鉴于官方给予deprecated,这里就不做分析阅读。

需要指定编码
public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }
    
public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }

字段意义和字符数组构造类似,对bytes数组进行范围指定切割

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

这两个函数只是默认为bytes不做范围切割

不指定编码(选择默认编码)

按照decode()方法描述,如果没有指定编码,会以ISO-8859-1进行解码
byte是网络传输或存储的序列化形式,
所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化,
byte是字节,char是字符,字节流和字符流需要指定编码,不然可能会乱码,
bytes字节流是使用charset进行编码的,想要将他转换成unicode的char[]数组,
而又保证不出现乱码,那就要指定其解码方法

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

StringBuffer和StringBuilder

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

由上可以知道,由于synchronized关键字的存在,StringBuffer是线程安全而builder不是。

内部方法

length()和isEmpty()

public int length() {
        return value.length;
    }

public boolean isEmpty() {
        return value.length == 0;
    }

即为简单的判断char[] 数组的长读

charAt(int index)和codePointAt(int index)

public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }

charAt(int index)返回当前字符串在下标为index的字符,即为char[index]

codePointAt(int index)返回当前字符串在下标为index的unicode字符编码

另: 还有codePointBefore(int index),codePointCount(int beginIndex, int endIndex),offsetByCodePoints(int index, int codePointOffset)等unicode方法,这里就不讨论。

equals(Object anObject)

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

equals方法

  1. 先比较内存地址是否一致,如果内存地址一致,直接返回true。
  2. 如果内存地址不一致,判断anObject是否为String的实例,如果不是,直接返回false。
  3. 如果内存地址不一致且anObject为String的实例,则先比较 value数组长度,再遍历当前String和 anObject的字符数组

总结
只要两个String的value一致,equals()就会返回true,不用考虑地址和hash值。

equalsIgnoreCase()

public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }

public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        char ta[] = value;
        int to = toffset;
        char pa[] = other.value;
        int po = ooffset;
        // Note: toffset, ooffset, or len might be near -1>>>1.
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)value.length - len)
                || (ooffset > (long)other.value.length - len)) {
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {
                continue;
            }
            if (ignoreCase) {
                // If characters don't match but case may be ignored,
                // try converting both characters to uppercase.
                // If the results match, then the comparison scan should
                // continue.
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                // Unfortunately, conversion to uppercase does not work properly
                // for the Georgian alphabet, which has strange rules about case
                // conversion.  So we need to make one last check before
                // exiting.
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }

equalsIgnoreCase三目运算的最后,执行了regionMatches(),该方法将两个比较对象的char[] value都提出来,进行遍历比值,只要toLowerCase()toUpperCase()其中一个达成,即认为当前字符为相等。

hashCode()

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;
    }
  1. hash值是int类型。
  2. 如果String的length==0或者hash值为0,则直接返回0
  3. 如果上述条件不满足,则通过算法计算hash值

indexOf()

public int indexOf(int ch) {
        return indexOf(ch, 0);
    }

这一种indexOf()传入的参数认为是unicode码下标,通过查询码表获取字符再做循环

public int indexOf(String str) {
        return indexOf(str, 0);
    }
    
 public int indexOf(String str, int fromIndex) {
        return indexOf(value, 0, value.length,
                str.value, 0, str.value.length, fromIndex);
    }
 static int indexOf(char[] source, int sourceOffset, int sourceCount,
            String target, int fromIndex) {
        return indexOf(source, sourceOffset, sourceCount,
                       target.value, 0, target.value.length,
                       fromIndex);
    }

static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
        // 当字符串的开始判断位置大于当前字符串的大小时,如果目标字符串的长度为0,则返回0,否则返回-1;
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
		// 如果字符串的开始判断位小于0,则使其等于0(从开头进行判断)
        if (fromIndex < 0) {
            fromIndex = 0;
        }
		// 如果目标字符串长度为0,则返回0
        if (targetCount == 0) {
            return fromIndex;
        }

		// 获取目标字符的第一个值
        char first = target[targetOffset];
        // 这里限定一下遍历的上线,因为寻找第一个匹配字符,不需要查满整个数组
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
            //	找到第一个和first相等的位置,找不到就继续加,但是个人认为++i和i++这种不易区分的代码尽量不要写在这种地方
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            // 获得目标字符串的长度以及下标终点,如果遍历后内容一致,就返回,否则继续循环
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

public int indexOf(String str)是一个较为常用的方法,判断当前字符串内是否包含指定字符串,并返回第一次返回的位置。
前面的所有方法最终都将皮球踢到了static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)方法中。

substring

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

public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

substring是用来裁切字符串方法,通过指定下标,获取字符数组区间,再调用构造函数,返回新串。

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

concat用于链接字符串,还是用数组的copyOf实现,制作一个新的数组。

valueOf(int /long/so on)

valueOf是直接调用封装类的toString方法。

资料参考


https://yq.aliyun.com/articles/672181



关于JVM部分由于没有看过JVM源码,结论都是通过书籍以及别人博客学习,若有错误欢迎指正 ?