String 不变性、Long 缓存源码解析和面试题

String 不变性、Long 缓存源码解析和面试题

String 和 Long 大家都很熟悉,现在我们结合实际的工作场景,来一起看下 String 和 Long 的底层源码实现,看看平时我们使用时,有无需要注意的点,总结一下这些 API 都适用于哪些场景。

一:String

1.1、不变性

我们常常看到这样的面试题:请简单的说说 String、StringBuilder 和StringBuffer 三者之间的区别。其中 String 就有一点与其他两者不一样的地方,那就是String值一旦被初始化,就不能再被改变了,如果被修改,将会是新的类。我们来看下面一段代码,虽然最后会输出 “ssssss”,看起来 String的值好像被修改了。

String str = "aaa";
str = "ssssss";
System.out.println(str);

其实我们通过 Debug 就能很直观的看出来,String 的值并没有被修改。
String不变性
String不变性
一开始str指向的内存地址是527,经过一轮赋值str指向的内存地址就变成了529。也就说 str = “ssssss” 这个看似简单的赋值,其实已经把 str 的引用指向了新的 String。

现在我们从 String 的源码上看一下原因

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

从 String 的源码中我们可以看出:

  1. String 被 final 修饰,说明 String 类绝不可能被继承了,也就是说任何对 String 的操作方法,都不会被继承覆写
  2. String 中保存数据的是一个 char 的数组 value。我们发现 value 也是被 final 修饰的,也就是说 value 一旦被赋值,内存地址是绝对无法修改的,而且 value 的权限是 private 的,外部绝对访问不到,String 也没有开放出可以对 value 进行赋值的方法,所以说 value 一旦产生,内存地址就根本无法被修改

以上两点就是 String 不变性的原因,充分利用了 final 关键字的特性,如果你自定义类时,希望也是不可变的,也可以模仿 String 的这两点操作。

1.2、相等判断

我们判断相等有两种办法,equals 和 equalsIgnoreCase。后者判断相等时,会忽略大小写,如果让你写判断两个 String 相等的逻辑,应该如何写,我们来一起看下 equals 的源码:

public boolean equals(Object anObject) {
    
    
    // 判断内存地址是否相同
    if (this == anObject) {
    
    
        return true;
    }
    // 待比较的对象是否是 String,如果不是 String,直接返回不相等
    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 的源码可以看出,逻辑非常清晰,完全是根据 String 底层的结构来编写出相等的代码。这也提供了一种思路给我们:如果有人问如何判断两者是否相等时,我们可以从两者的底层结构出发,这样可以迅速想到一种贴合实际的思路和方法,就像 String 底层的数据结构是 char 的数组一样,判断相等时,就挨个比较 char 数组中的字符是否相等即可。

二:Long

2.1、缓存

Long 最被我们关注的就是 Long 的缓存问题,Long 自己实现了一种缓存机制,缓存了从 -128 到 127 内的所有 Long 值,如果是这个范围内的 Long 值,就不会初始化,而是从缓存中拿,缓存初始化源码如下:

private static class LongCache {
    
    
    private LongCache(){
    
    }
    // 缓存,范围从 -128 到 127,+1 是因为有个 0
    static final Long cache[] = new Long[-(-128) + 127 + 1];

    // 容器初始化时,进行加载
    static {
    
    
        // 缓存 Long 值,注意这里是 i - 128 ,所以再拿的时候就需要 + 128
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}

三:面试题

3.1、为什么使用 Long 时,大家推荐多使用 valueOf 方法,少使用 parseLong 方法?

因为 Long 本身有缓存机制,缓存了 -128 到 127 范围内的 Long,valueOf 方法会从缓存中去拿值,如果命中缓存,会减少资源的开销,parseLong 方法就没有这个机制。

3.2、为什么大家都说 String 是不可变的?

要是因为 String 和保存数据的 char 数组,都被 final 关键字所修饰,所以是不可变的,具体细节描述可以参考上文。

三:总结

String 和 Long 在我们工作中使用频率很高,在面试的过程中,考官也喜欢问一些关于实际操作的问题,来考察我们的使用熟练度。

猜你喜欢

转载自blog.csdn.net/weixin_38478780/article/details/107762049