美文网首页Java
String类面试题

String类面试题

作者: h2coder | 来源:发表于2021-09-05 22:40 被阅读0次

    String类的面试题,往往是面试的开端,如果String类的面试题都没有答好,那么给面试官的第一印象并不太好了

    String 是如何实现的?它有哪些重要的方法?

    String类的内部组成

    以JDK1.8为例,发现String类实质是一个字符数组

    public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
        //字符串的值
        private final char value[];
        //字符串的哈希码
        private int hash;
        //...
    }
    
    • 多构造方法

    String类有4个构造方法,其中容易忽略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());
    }
    

    equals() 比较两个字符串是否相等

    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 数组对比
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                // 循环比对两个字符串的每一个字符
                while (n-- != 0) {
                    // 如果其中有一个字符不相等就 true false,否则继续对比
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    

    String类重写了Object类的equals()方法,该方法传入一个Object类型的对象,在比较时,先通过instanceof判断是否为String类型,如果不是则直接返回false,如果是,则会循环比较2个字符串的每一个字符,当所有字符都相等时,返回true

    还有一个和equals()方法类型的equalsIgnoreCase()方法,它用于忽略字符大小写的比较

    compareTo() 比较两个字符串

    compareTo()用于比较2个字符串,返回值为int

    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        //获取到两个字符串长度最短的那个 int 值
        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()方法中,会循环比较2个字符串的所有字符,当有一个字符不相等时,则返回2个字符的相减值c1 - c2,如2个字符串分别存储的是1和2,那么返回值为-1,如果存储的值是1和1,那么返回值为0,如果存储的是2和1,那么返回值为1

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

    综上所述,可以看出,compareTo()equals()都用于比较字符串,但它们有2点不同

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

    它们都可以用于比较2个字符串的值,equals()方法返回true,或者compareTo()方法返回0时,2个字符串完全相同

    String类的其他重要方法

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

    为什么 String 类型要用 final 修饰?,== 和 equals 的区别是什么?

    == 和 equals 的区别

    == 对于基本数据类型来说,比较的是是否相等,而对于引用类型来讲,比较的是对象的内存地址,而Object的equals()方法,使用的就是==进行比较

    public boolean equals(Object obj) {
        return (this == obj);
    }
    

    所以默认对象的equals()方法比较的就是对象的地址值,而子类可以复写该方法,例如String类就复写了equals()方法,比较2个字符串的值是否相等

    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 数组对比
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                //循环比对两个字符串的每一个字符
                while (n-- != 0) {
                    //如果其中有一个字符不相等就 true false,否则继续对比
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    

    final 修饰的好处

    String类的源码可以看出,String类是final修饰的不可变类

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

    主要原因有2点,一个是安全,另外一个是高效,理由如下:

    • 安全
      • 在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题
    • 高效
      • 它能够缓存结果,当你在传参时不需要考虑谁会修改它的值,如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失

    以JVM常量池来讲

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

    只有字符串时不可变时,我们才能够实现字符串常量池,字符串常量池可以缓存字符串,提高程序的运行效率

    如果String类是可变的,当我们修改s1的值后,s2也跟着变了,这样和我们预期就不相符了,因此也没办法实现字符串常量池了

    String 和 StringBuilder、StringBuffer 的区别

    因为String类是不可变类,所以在字符串拼接时,性能会很低,这时我们需要使用StringBuffer,它提供了appendinsert,用于字符串拼接,它使用synchronized关键字来保证线程安全

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

    因为它使用synchronized关键字来保证线程安全,所以它的性能不是很高,于是在JDK 1.5中,提供了StringBuilder,它同样提供了appendinsert,但它没有使用synchronized关键字来修饰,因此性能优于StringBuffer,所以在非并发环境下可以使用StringBuilder

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

    String类的intern()方法是一个native方法

    public native String intern();
    

    它会先从字符串常量池中查找,如果存在,则直接返回该引用,如果不存在,则创建该字符串,再放到字符串常量池中,再返回

    String 和 JVM

    String的常见创建方式有2种,new String()和字面量赋值,字面量赋值会先去常量池中查找是否有该值的字符串,如果有则直接把引用地址指向该值,如果没有则先会在常量池中创建,再把引用指向该值。而new String()是直接在堆上创建一个字符串对象,然后再去常量池中查找是否存在该值,不存在则在常量池中创建,再把引用的值指向该字符串

    //堆上直接创建一个String类对象
    String s1 = new String("Java");
    //从常量池中查询,找得到则使用,找不到则创建,再把字符串拷贝到常量池,它和s1不是同一个对象
    String s2 = s1.intern();
    //字面量声明,从常量池中查找,找到s2,所以s2和s3是同一个对象
    String s3 = "Java";
    System.out.println(s1 == s2); //false
    System.out.println(s2 == s3); //true
    

    除此以外,JVM还会对字符串做优化,在使用+号拼接字符串时,会直接把字面量组合成新的值

    String s1 = "Ja" + "va";
    String s2 = "Java";
    System.out.println(s1 == s2);//true
    

    结果是true,因为编译器在编译时做了优化,直接把"ja" + "va"编译成了"java",所以s1实际是"java",然后存放在了字符串常量池中,而s2再去常量池中找时,则找到s1,所以他们是同一个对象

    相关文章

      网友评论

        本文标题:String类面试题

        本文链接:https://www.haomeiwen.com/subject/temswltx.html