手记

String源码分析,以及如何偷源码思路

一、前言

本章内容主要分析String源码,了解String的核心思路,并且总结学习String源码之后的收获
①String、StringBuilder、StringBuffer的区别,如果光靠背答案则很容易就会忘记,甚至回答的不够透彻
②把其核心思想运用到我们的网盘系统,文章后面会详细的介绍‘

二、String类的源码讲解

打开String类,具体如下所示:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
    private int hash;
}

通过以上代码我们需要掌握以下几点内容
1)String是final修饰,它是不可以被继承,也就是说不可以有子类
2)它本身是char[]数组
3)char[]数组是final修饰,则数组是不可以被改变的

思考:既然char[]数组不可以被改变,那么它默认的数组长度是多少呢?

//构造函数一
public String(String original) {
  this.value = original.value;
  this.hash = original.hash;
}
//构造函数二
public String(char value[]) {
  this.value = Arrays.copyOf(value, value.length);
}
//构造函数三
public String(char value[], int offset, int count) {
  if (offset < 0) {
     throw new StringIndexOutOfBoundsException(offset);
  }
  if (count <= 0) {
    if (count < 0) {
       throw new StringIndexOutOfBoundsException(count);
    }
    if (offset <= value.length) {
      this.value = "".value;
      return;
    }
  }
  if (offset > value.length - count) {
    throw new StringIndexOutOfBoundsException(offset + count);
  }
  this.value = Arrays.copyOfRange(value, offset, offset+count);
}

String有好多的构造函数,以上只是复制3个出来,通过以上构造函数,其实我们发现给String赋值,就是给char[]数组赋值。数组的长度就是传递字符串的长度。

思考:既然char[]数组是不可以被改变的,那么字符串拼接的时候是怎么实现的呢?

public String concat(String str) {
    //1.获取被拼接字符串的长度
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    //2.旧字符串的长度
    int len = value.length;
    //3.扩容一个新的数组,并且把旧数组拷贝到新数组里面
    char buf[] = Arrays.copyOf(value, len + otherLen);
    //4.请看下面源码
    str.getChars(buf, len);
    //5.把最终的新数组,当做参数,创建一个新的String【核心】
    return new String(buf, true);
 }
 
 void getChars(char dst[], int dstBegin) {
     //把被拼接的字符串添加到新数组的后面
     System.arraycopy(value, 0, dst, dstBegin, value.length);
 }

String字符串底层调用的是concat的方法,该方法的核心思想是通过Arrays.copyOf操作数组,再底层就是System.arraycopy操作数组(这里不再重复讲解了,请参考上一篇文章),核心的思路如下:

①创建一个新长度的数组
②把旧数组的内容拷贝到新数组
③再把新拼接的内容拷贝到新数组的后面
④旧数组被gc掉
⑤把最终的新数组,当做参数来创建一个新的String

思考:字符串的hash值是如何计算的呢?

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        //1.获取数组      
        char val[] = value;
        //2.遍历数组
        for (int i = 0; i < value.length; i++) {
            //3.hash值计算
            h = 31 * h + val[i];
        }
        hash = h;
      }
    return h;
}

通过上面的代码,我们发现字符串的hash值其实就是各个char的累加
①char其实和int是可以互换的,每个char在ASCII码都能找到其对应的int类型的值
②字符串的hash等于31*旧的hash值+新的char值
③至于为什么加31,主要是为了降级hash冲突,关于Hash值,它的规律是,相同的内容则Hash值一致,但是Hash值一致不代表内容一致。

好了,关于String的源码就分析到这里,String的方法非常的多,大家可以自己去看源码,我主要传递给大家的是看源码的方法。大家可以私下去了解字符串的比较截取字符串等方法的源码。

三、面试题

面试题:String和StringBuilder的区别

通过这两篇文章源码的分析,我们需要掌握String和StringBuilder的区别,具体如下所示:
①String和StringBuilder底层都是char[]数组,但是String是final修饰的,则表示char[]不可被改变,数组长度固定
②字符串拼接的时候,都是通过System.arraycopy去操作char[]数组,但是StringBuilder则是创建一个新的数组,引用没有改变;而String则是另外new String(),引用改变了。

面试题:StringBuilder和StringBuffer的区别

①这个问题很简单,就是StringBuffer使用了synchronized来修饰,线程是安全的,但是性能很低

四、学习源码如何运用到实际项目

其实学习源码,不光是为了面试而看,我们吸收大牛的思想,并且想办法运用到自己的项目当中,这样才能有助于理解。
通过这两篇文章的源码阅读(String、StringBuilder),我们核心掌握的知识点就是数组的操作,主要是数组的扩容、拷贝等。

需求: 在我们的网盘系统里面有这样的一个业务场景,前端上传的文件,Java后台需要对文件进行切块处理,并且把各个切块上传到FastDFS、把其对应的切块记录保存到MySQL。

分析: 在计算机里任何数据的底层存储都是以二进制的补码形式进行存储的,也就是说任何数据(如:文件)都可以转换成对应的byte[],那么我们只需要对byte[]数组进行切块即可。【具体源码就不粘贴了,大家可以去看网盘的源码】

慕课网专栏(架构思想之微服务+仿百度网盘源码):
https://www.imooc.com/read/73
感谢您的阅读,希望您多多支持,这样我在原创的道路上才能更加的有信心。

1人推荐
随时随地看视频
慕课网APP