写在前面

String 算是 Java 源码中先要学习的,今天就从源码的角度来重新认识一下

1.存储结构

看主流的 JDK 版本 1.8 ,String 内部实际存储结构为 char 数组,源码如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    //其他内容......

2.常用方法

2.1.构造方法

其中 StringBuffer 和 StringBuilder 为参数的构造函数用的比较少,但也要知道

    /**
     * String 为参数的构造方法
     * @param  original
     *        A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    /**
     * char[] 为参数构造方法
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    /**
     * StringBuffer 为参数的构造方法
     * @param  buffer
     *         A {@code StringBuffer}
     */
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }

    /**
     * StringBuilder 为参数的构造方法
     * @param   builder
     *          A {@code StringBuilder}
     * @since  1.5
     */
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

2.2.equals()

String 中 equals() 是比较两个字符串的值是否相等,== 才是比较字符串的引用是否相等,equals() 重写了父类 Object 方法,传参也为 Object 类型,方法中会通过 instanceof 判断,是 String 类型才进行下一步。

这里提一下,Object 父类中 equals() 和 == 对于引用类型的作用是一样的。

    /**
    * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
        if (this == anObject) {// 对象引用相同直接返回 true
            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])//转化为字符数组,对比每个字符,有一个不相同就是 fasle
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

有一个和 equels 相似的方法 equalsIgnoreCase(String),忽略大小写对比,传参为 String 类型

2.3.compareTo()

compareTo() 和 equels() 处理方式类似,都是字符对比,不同的是,equels() 比较两字符串相同返回 true,不相同返回 false;compareTo() 比较两字符串相同返回 0,不相同返回 其他 int 类型数值。并且 compareTo() 只能接收 String 类型。

还有一个和 compareTo() 比较类似的方法 compareToIgnoreCase(),用于忽略大小写后比较两个字符串。

    /**
     * @param   anotherString   the {@code String} to be compared.
     * @return  the value {@code 0} if the argument string is equal to
     *          this string; a value less than {@code 0} if this string
     *          is lexicographically less than the string argument; and a
     *          value greater than {@code 0} if this string is
     *          lexicographically greater than the string argument.
     */
    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

2.4.其他重要方法

  • indexOf():查询字符串首次出现的下标位置
  • lastIndexOf():查询字符串最后出现的下标位置
  • contains():查询字符串中是否包含另一个字符串
  • toLowerCase():把字符串全部转换成小写
  • toUpperCase():把字符串全部转换成大写
  • length():查询字符串的长度
  • trim():去掉字符串首尾空格
  • replace():替换字符串中的某些字符
  • split():把字符串分割并返回字符串数组
  • join():把字符串数组转为字符串

3.常遇问题

3.1.String 和 StringBuilder、StringBuffer

String 是不可变的,在字符串拼接的时候使用 String 会很耗性能,因此有了 StringBuilder 和 StringBuffer,它们有 2 方法 append 和 insert 可以实现字符串拼接,唯一不同的是 StringBuffer 使用 synchronized 来保证线程安全

//StringBuffer 截取片段,具体可以看 StringBuffer 类源码

    @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    //其他......


    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized StringBuffer insert(int offset, Object obj) {
        toStringCache = null;
        super.insert(offset, String.valueOf(obj));
        return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized StringBuffer insert(int offset, String str) {
        toStringCache = null;
        super.insert(offset, str);
        return this;
    }
   //其他......
//StringBuilder截取片段,具体可以看 StringBuilder类源码

     @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    //其他......


    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public StringBuilder insert(int offset, Object obj) {
            super.insert(offset, obj);
            return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public StringBuilder insert(int offset, String str) {
        super.insert(offset, str);
        return this;
    }
    //其他......

StringBuffer 保证线程安全,所以性能不是很高,JDK 1.5 就有了 StringBuilder

3.2.String 为什么用 final 修饰

使用 final 修饰的第一个好处是安全;第二个好处是高效,例如

String s1 = "java";
String s2 = "java";

只有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率,如下图所示:
在这里插入图片描述

3.3.JVM 中存储

String 常用的 2 种创建方式,有 String a1 = "java" 和 String a2 = new Strring("java"),但他们在内存中的存放方式不同,JDK1.8 中创建啊变量 a1,会先从常量池中找字符串 “java”,如果有直接返回,如果没有则先在常量池中创建该字符串再返回,而变量 a2会直接在堆内存上创建,a2 调用方法 intern() 会把字符串保存到常量池,例如

String a1 = "java";
String a2 = new Strring("java");
String a3 = "a2.intern();

System.out.println(a1 == a2); // false
System.out.println(a1 == a3); // true

JVM 存储位置如图
在这里插入图片描述
PS:JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。

结束......

如果有哪些不对的地方烦请指认,先行感谢

charmsongo
33 声望0 粉丝

公号:charmsongo