天天看点

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

该文章基于jdk1.8

String 不可变性

String类是被final修饰的,意味着String是不可变的,那么为什么这么设计呢?其实源码里的注释给出了答案:

their values cannot be changed after they are created. 
String buffers support mutable strings.
Because String objects are immutable they can be shared. 
           

因为String 可以被共享,所以不可变。

如果字符串可变的话,当两个引用指向指向同一个字符串时,对其中一个做修改就会影响另外一个。

不可变对象可以自由地在多个线程之间共享。不需要任何同步处理。

所以String设计成不可变是安全而且高效的。

String 实现的接口

String 实现了三个接口,分别是 java.io.Serializable ,Comparable,CharSequence

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

java.io.Serializable

序列化接口

Comparable

比较对象大小的接口 该接口有一个compareTo(T o)方法。实现 该方法比较对象大小,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。以下是String对compareTo的实现:

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

从第一个字符开始比较,当遇到较小的字符时,判断该字符串小,如果循环中两个字符串比较的字符都相等,那么判断字符串长度长的字符串大。

CharSequence

CharSequence 对许多不同实现类的 char 序列提供统一的只读访问。

CharSequence 有四个抽象方法,分别是:

int length();

返回字符序列的长度,也就是该字符序列字符的数量。以下是String 对int length()的实现

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

直接返回了字符数组的长度

char charAt(int index);

返回该字符序列指定索引的字符,以下是String 对char charAt(int index)的实现

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

判端了传入索引是否在字符数组索引范围内,不在范围内,抛出索引越界异常。反之则返回字符。

subSequence(int start, int end);

返回指定索引范围的子字符序列,如果start==end ,返回空字符串。

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

直接调用了substring方法。

String toString();

返回字符序列组成的字符串 直接返回自己:

public String toString() {
        return this;
    }
           

CharSequence还有两个默认方法:

chars

public default IntStream chars() {
        class CharIterator implements PrimitiveIterator.OfInt {
            int cur = ;

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                if (hasNext()) {
                    return charAt(cur++);
                } else {
                    throw new NoSuchElementException();
                }
            }

            @Override
            public void forEachRemaining(IntConsumer block) {
                for (; cur < length(); cur++) {
                    block.accept(charAt(cur));
                }
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliterator(
                        new CharIterator(),
                        length(),
                        Spliterator.ORDERED),
                Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
                false);
    }
           

返回了字符序列的IntStream

codePoints

public default IntStream codePoints() {
        class CodePointIterator implements PrimitiveIterator.OfInt {
            int cur = ;

            @Override
            public void forEachRemaining(IntConsumer block) {
                final int length = length();
                int i = cur;
                try {
                    while (i < length) {
                        char c1 = charAt(i++);
                        if (!Character.isHighSurrogate(c1) || i >= length) {
                            block.accept(c1);
                        } else {
                            char c2 = charAt(i);
                            if (Character.isLowSurrogate(c2)) {
                                i++;
                                block.accept(Character.toCodePoint(c1, c2));
                            } else {
                                block.accept(c1);
                            }
                        }
                    }
                } finally {
                    cur = i;
                }
            }

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                final int length = length();

                if (cur >= length) {
                    throw new NoSuchElementException();
                }
                char c1 = charAt(cur++);
                if (Character.isHighSurrogate(c1) && cur < length) {
                    char c2 = charAt(cur);
                    if (Character.isLowSurrogate(c2)) {
                        cur++;
                        return Character.toCodePoint(c1, c2);
                    }
                }
                return c1;
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliteratorUnknownSize(
                        new CodePointIterator(),
                        Spliterator.ORDERED),
                Spliterator.ORDERED,
                false);
    }
           

return an IntStream of Unicode code points from this sequence

返回了字符序列的码点序列的IntStream

String中的变量

Java学习笔记 (十二) String源码分析String 不可变性String 实现的接口String中的变量String的构造器字符串的常用方法参考资料

value存储的是字符数组,可以看出字符串本质上是以字符数组的形式存储在value的。

hash 是String实例化的hashcode的一个缓存。因为String经常被用于比较,比如在HashMap中。如果每次进行比较都重新计算hashcode的值的话,那无疑是比较麻烦的,而保存一个hashcode的缓存能优化这样的操作。

String的构造器

无参构造

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

无参构造就是一个空字符串,并没有什么意义。

根据字符串创建新的字符串

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

可以看到就是将value数组赋值给数组

根据字符数组创建字符串

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

就是将数组复制出来一份

根据字符数组的一部分创建字符串

public String(char value[], int offset, int count) {
        if (offset < ) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= ) {
            if (count < ) {
                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);
    }
           

字符串的常用方法

判断字符串是否为空

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

可以看出判断的是字符数组的长度

获取字节数组

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

判断字符串是否相等

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 = ;
                while (n-- != ) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
           

通过遍历字符数组判断字符是否相等,来判断字符串是否相等。

判断字符串是否以某某字符串为开头

public boolean startsWith(String prefix, int toffset) {
        char ta[] = value;
        int to = toffset;
        char pa[] = prefix.value;
        int po = ;
        int pc = prefix.value.length;
        // Note: toffset might be near -1>>>1.
        if ((toffset < ) || (toffset > value.length - pc)) {
            return false;
        }
        while (--pc >= ) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }
           

从以上的几个方法可以看出,String的方法大多是基于对字符数组的操作。

参考资料

  • 《成神之路-基础篇》Java基础知识——String相关
  • https://www.cnblogs.com/listenfwind/p/8450241.html