浅谈String的特点和具体的源码实现

x33g5p2x  于2022-05-05 转载在 其他  
字(4.6k)|赞(0)|评价(0)|浏览(181)

浅谈String的特点和具体的源码实现

1、String源码本质

String的基本使用是Java入门的一个必修课,在面试中有时候也往往会是第一道面试题,一些互联网大厂也喜欢从最基础的知识点入手,然后追问技术实现细节。所以本博客通过源码和对比方式对一些实现细节简单分析

以jdk8版本的源码来说,String是以final修饰的类,实际存储的数据结构为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 */
    // 缓存字符串的hashcode
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    
    ...
}

2、构造方法

挑出String里的主要几个构造方法,String不仅可以传入String参数、char数组,而且可以传入StringBufferStringBuilder

// String 为参数的构造方法
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
// char[] 为参数构造方法
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
// StringBuffer 为参数的构造方法
public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
// StringBuilder 为参数的构造方法
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

3、equals(Object)方法

equals(Object)方法比较两个字符串是否相等。String类重写了Object类的equals(Object)方法,比较的是字符串每个字符。

/**
 * Compares this string to the specified object.  The result is {@code
 * true} if and only if the argument is not {@code null} and is a {@code
 * String} object that represents the same sequence of characters as this
 * object.
 *
 * @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) {
    // 对象引用相同直接返回TRUE
    if (this == anObject) {
        return true;
    }
    // 判断要对比的值是否String类型,否直接返回FALSE
    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) {
                // 其中有一个字符不相同返回FALSE
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

equals(Object)方法传入一个Object对象,会用instanceof判断是否为String类型,不是String类型直接返回FALSE

Object oStr = "123";
Object oInt = 123;
// 返回 true
System.out.println(oStr instanceof String); 
// 返回 false
System.out.println(oInt instanceof String);

equals(Object)方法注释里,作者也提到了两个类似方法,@see #compareTo(String) @see #equalsIgnoreCase(String)equalsIgnoreCase(String)方法用于忽略字符串的大小写之后的字符串比较,compareTo(String)也是用于字符串比较的,具体和equals(Object)有什么不同?

4、compareTo(String)方法

compareTo(String):用于比较两个字符串,返回的结果为 int 类型的值

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    // 获取两个字符串长度值最小的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;
}

从源码也可以看出compareTo(String)方法也是遍历字符串的每一个字符,当两个字符串中有任意字符不相同时,返回c1 - c2。比如,两个字符串分别存储的是 1 和 2,返回值-1;如果两个字符串存储的是1和1,返回值0,;如果两个字符串存储的是2和1,返回值是1

equals(String)方法一样,compareTo(String)方法也有一个忽略大小写的比较方法compareToIgnoreCase(String)compareToIgnoreCase(String)用于用于忽略大小写后比较两个字符串。

5、compareTo(String) 和equals(Object) 对比

compareTo(String) 方法和 equals(Object) 方法,这两个方法都可以用于比较字符串

  • compareTo(String) 方法和 equals(Object) 方法的区别:
  • equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数
  • equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。

6、其他方法

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

拓展知识

上面对String的常用方法做了一个比较简单的介绍,下面给出面试中一个很常见的面试题,进行介绍,主要是学习理解,并非为了面试而面试

  • 为什么 String 类型要用 final 修饰?
    final 修饰的类都是一个不可继承类。那这样设计有什么好处呢?
    Java 语言之父 James Gosling的回答是,他会更倾向于使用final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
    另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。

总而言之就是用两个优点,一个是安全,另外一个是性能问题

  1. == 和 equals 的区别是什么?

  2. ==:对比的是栈中的值,基本数据类型对比的是变量值,引用数据类型对比的是堆中内存对象的地址

  3. equals:Object中默认也是常用==进行比较,而String的equals进行重写,比较的是两个字符串的内容

  4. String 和 StringBuilder、StringBuffer 有什么区别?

  5. String是不可变的,如果尝试修改String字符串,会重新生成一个对象;而StringBuffer、StringBuilder是可变的

  6. StringBuffer是线程安全的,StringBuilder是线程不安全的,所以StringBuilder的执行效率要快于StringBuffer

  7. String 的 intern() 方法有什么含义?

public static void main(String[] args) {  
    String s1 = new String("abc");
	String s2 = "abc"; 
    // s1 == s2?
    String s3 = s1.intern();
    // s2 == s3?
}

答案:

  1. s1 == s2 false
  2. s2 == s2 true

Stringintern()方法,会检查字符串常量池中是否有“abc”字符串,如果有,返回改字符串引用,否,将“abc”添加到常量池中。

详情可以参考博客深入解析String#intern 美团技术团队

  1. String和new String两张创建方式有什么区别?

  2. String str1 = “abc”; 最多创建一个String对象,最少是不创建对象。如果常量池中有“abc”,那么str1直接引用就可以,否则创建“abc”的内存空间,如何再引用。引号创建的字符串是直接量,在编译器就会存储到常量池中

  3. String str2 = new String(“abc”);最多创建两个对象,最少创建1个对象。new关键字,会在堆创建内存区域,在运行期才创建。

相关文章