推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > 肯定会 > 解析java中的String源码

解析java中的String源码

肯定会 2018-04-25



正文共: 6363字 14图 预计阅读时间: 16分钟


话说之前对代码的编写大多都是直接使用jdk里面的对象,有时候也对一些对象直接封装之后就使用了,很少去了解源码中具体细节是怎么实现的,这样显然不符合我这么帅的人的做事风格,所以我现在就来对源码进行学习学习,可能篇幅略长,不过会慢慢记录下我学习的过程和总结一下,希望对自己有帮助的同时,也能够帮助到和我一样,希望更进一步去理解java的小伙伴们!!


String类的大概面貌

废话不多说了,先打开我的 InteliJ IDEA , 创建一个学习源码的项目,就叫做「learnJavaSourceCode」吧。 打开String.java之后可以发现了String实现了java.io.Serializable, Comparable , CharSequence :

解析java中的String源码

有4个成员变量:

  1. private final char value[];


  2. private int hash; // Default to 0


  3. private static final long serialVersionUID = -6849794470754667710L;


  4. private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];


  5. public static final Comparator CASE INSENSITIVEORDER = new CaseInsensitiveComparator();


有一个内部类CaseInsensitiveComparator,还有其它就是构造函数和方法了!


String的概要描述总结

也就是在一开始的一大段注释:

解析java中的String源码

我对它总结一下就是以下几点:

  1. 在java中所有的字符字面值(例如:"abc")都是String的一个实例;


  2. Strings是常量,它们的值在被创建之后就不可以改变了,字符缓冲支持改变,因为String的对象是不可变的,所以它们可以被共享;


  3. String包含了一些方法,例如字符的大小写转换等(好像有点废话 - -);


  4. java语言提供了支持字符间的连接操作和将对象转化为字符串的操作,字符间的连接是通过 "+" 来操作的,它们之所以可以连接是因为通过 StringBuffrer 或者 StringBuilder 的 append 方法实现的,而将对象转化成字符串是通过Object方法中的toString方法。


  5. 携带 null 这个参数给String的构造函数或者方法,String会抛出NullPointerException,这不是见惯不惯了吗:)


  6. String 表示一个 UTF-16 格式的字符串。其中的 增补字符 由 代理项对 表示,索引值是指 char 代码单元,因此增补字符在 String 中占用两个位置。


对String的主要描述进行演示

  1. 在java中所有的字符字面值(例如:"abc")都是String的一个实例!

    很好理解,我们经常就是这样做的,java这么规定,我们也就这么写了:


 
   
   
 
  1. String s = "abc"; //这里的 "abc" 就是一个对象

  2. System.out.println("abc");




2. 

Strings是常量,它们的值在被创建之后就不可以改变了,字符缓冲  支持改变,因为String的对象是不可变的,所以它们可以被共享!

那是不是这样:


 
   
   
 
  1. public static void main (String[] args){

  2.    String s = "abc"; //s是常量,abc被创建了,那么s对应的值就不可以改变了

  3.    s = "def"; // 我就把它改成def看看

  4.    System.out.println(s); // 输出 def

  5. }

奇怪,不是说不能被改变了吗?为毛可以是def?其实不然,我们一开始是创建的 "abc" , 我们从1中 「在java中所有的字符字面值(例如:"abc")都是String的一个实例」 可以知道,其实"abc"就是一个String的实例,我们的String s 只不过是指向这个"abc"的String对象了,而我们的 s = "def" 则是将s指向"def"这个对象!

心情好,画个图吧:

首先我们写了这样一句 String s = "abc"; 那么是这样的:

解析java中的String源码

s 这个引用会去常量池里面找有没有"abc",发现卧槽,没有,那么就创建一个:

解析java中的String源码

这时候 s 就可以指向 "abc" 了!

接着我们把 s = "def" , 同样的道理,它会去常量池找有没有 "def", 有就指向它,没有就在常量池创建一个:

解析java中的String源码

所以我们现在应该知道 「String的对象是不可变的,所以它们可以被共享」 这句话是什么意思了吧 - -


3.

Java语言提供了支持字符间的连接操作和将对象转化为字符串的操作,字符间的连接是通过 "+" 来操作的,它们之所以可以连接是因为通过 StringBuffrer 或者 StringBuilder 的 append 方法实现的,而将对象转化成字符串是通过Object方法中的toString方法。

写段现代吗:


 
   
   
 
  1. String s1 = "I ";

  2. String s2 = "Love ";

  3. String s3 = "You ";

  4. System.out.println(s1 + s2 + s3 );

运行后理所当然是 I Love You :) 接着使用jad反编译下上面这段代码会发现:

 
   
   
 
  1. String s1 = "I ";

  2. String s2 = "Love ";

  3. String s3 = "You ";

  4. System.out.println((new StringBuilder()).append(s1).append(s2).append(s3).toString());

可以看到它真的用StringBuilder对象用append方法把我们的s1 + s2 + s3 拼接起来了,然后用toString方法得到 I Love You ... 害羞 - -


对String常用构造方法解析

String(String original);

首先来个问题思考:

 
   
   
 
  1. String msg1 = "I Love You !" ;

  2. String msg2 = new String ("I Love You !");

上面的 msg1 和 msg2 是一样的吗? (我们不一样~~)

我们已经知道msg1是从常量池去取的,而我们new String() 这时候应该在堆内存产生一个String对象,而通过源码可以看到这个String对象方法是将我们传入的这个 "I Love You !" 对象的value和hash进行复制:

 
   
   
 
  1. public String(String original) {

  2.        this.value = original.value;

  3.        this.hash = original.hash;

  4.    }

画个图那就是这个样子的:

解析java中的String源码


String(char[] value)

我们创建个char[],然后传给String构造函数:

 
   
   
 
  1.            char[] c = new char[3];

  2.            c[0] = 'I';

  3.            c[1] = 'L';

  4.            c[2] = 'U';

  5.            String msg = new String(c);

  6.            System.out.println(msg);

通过debug后可以发现:

解析java中的String源码

原来它通过 Arrays.copyOf 把我们的char直接复制给value了! 其实我们应该也猜到了,String里面的value就是char数组!


String(byte[] bytes)

String提供了好几个含有byte[]参数的构造函数,那么我们对byte[]和String间的转化就容易许多了。 可能你之前应该遇到过乱码问题,你应该是这么解决的:

 
   
   
 
  1. try {

  2.      String s = new String("乱码".getBytes(),"utf-8");

  3.    } catch (UnsupportedEncodingException e) {

  4.      e.printStackTrace();

  5.    }

其实String里面是含有一个char[]来存放这些字符,而这些字符是以Unicode码来存储的,字节是通过网络传输信息的单位,所以我们在传入byte[]转化为String的时候,是需要对其进行编码的:

 
   
   
 
  1. static char[] decode(String charsetName, byte[] ba, int off, int len)

  2.        throws UnsupportedEncodingException

  3.    {

  4.        StringDecoder sd = deref(decoder);

  5.        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;//我们指定了utf-8,默认是ISO-8859-1

  6.        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())

  7.                              || csn.equals(sd.charsetName()))) {

  8.            sd = null;

  9.            try {

  10.                Charset cs = lookupCharset(csn);

  11.                if (cs != null)

  12.                    sd = new StringDecoder(cs, csn);

  13.            } catch (IllegalCharsetNameException x) {}

  14.            if (sd == null)

  15.                throw new UnsupportedEncodingException(csn);

  16.            set(decoder, sd);

  17.        }

  18.        return sd.decode(ba, off, len);

  19.    }


对String常用方法解析

  1. charAt(int index)

 
   
   
 
  1. String msg = "I love you !";

  2. char c = msg.charAt(3);

  3. System.out.println(c); // 输出的是 o;

可以看到这个方法是根据索引返回Char值!

进去源码看看:

 
   
   
 
  1. public char charAt(int index) {

  2.        if ((index < 0) || (index >= value.length)) {

  3.            throw new StringIndexOutOfBoundsException(index);

  4.        }

  5.        return value[index];

  6.    }

可以看到当我们传进去的索引值是小于0 或者 大于 这个字符串的长度,就会抛出异常。否则就返回value数组中对应的值!通过debug可以看到其实我们刚刚定义的 String msg = "I love you !" 中的值被放到了value这个数组中了!

解析java中的String源码

而我们 char c = msg.charAt(3) 传入的这个3 就是这个数组对应的下标3,所以呢,输出就是o啦!解析java中的String源码

   

 2.equals(Object anObject)

equals我们通常用于比较是否相同,那么你知道下面这段代码分别输出的是什么吗?

 
   
   
 
  1.        String s = "I Love You !";

  2.        String s1 = "I Love You !";

  3.        String s2 = new String("I Love You !");

  4.        System.out.println(s == s1);

  5.        System.out.println(s.equals(s1));

  6.        System.out.println(s == s2);

  7.        System.out.println(s.equals(s2));

他们分别输出的:

  • true

  • true

  • false

  • true

可能有些人会奇怪会什么 s == s2 是false? 他们不是都是是 ""I Love You !" 吗?这时候我们就要来看看 == 和 equals 的区别了!

可能此刻有人会疑惑那么为什么s.equals(s2)返回的是true了。这时候我们应该可以猜到equals应该判断的不是两个对象间的hashcode吧,我们看下源码:

 
   
   
 
  1. public boolean equals(Object anObject) {

  2.        if (this == anObject) {  //如果对象的hashcode一样就直接返回true

  3.            return true;

  4.        }

  5.        if (anObject instanceof String) {  // 判断传进来的对象是不是String类型

  6.            String anotherString = (String)anObject; // 将对象强转为String

  7.            int n = value.length; //获取我们本身拿来对比的String对象的长度

  8.            if (n == anotherString.value.length) { // 判断它们的长度是否一样

  9.                char v1[] = value;

  10.                char v2[] = anotherString.value;

  11.                int i = 0;

  12.                while (n-- != 0) {

  13.                    if (v1[i] != v2[i])  //逐一判断字符使用相等

  14.                        return false;

  15.                    i++;

  16.                }

  17.                return true;

  18.            }

  19.        }

  20.        return false;

  21.    }

很明显我们可以发现,equals判断的不是hashcode,而是判断它们的值是否相同,所以s.equals(s2)返回的是true!

   

 3.endsWith(String suffix)

有时候我们可能会判断字符串是否以指定的后缀结束。例如我们有获取的图片路径,判断他是不是以.jpg结尾的:

 
   
   
 
  1.   String s = "canglaoshi.jpg";

  2.   System.out.println(s.endsWith(".jpg")); //true

debug一下就明白了它是怎么判断的:解析java中的String源码

它会去调用 startsWith 方法。

  • 用 ta[] 这个数组来存放 "canglaoshi.jpg";

  • 用 int to 来接收 "canglaoshi.jpg"的长度(14) 减去 ".jpg" 的长度(4) = 10;

  • 用 pa[] 来存放 ".jpg";

  • 用int pc 来接收 ".jpg"的长度;

最后就是以pc为次数进行遍历ta[]从下标为to开始,pa[] 从下标为0开始逐一判断,如果相同就返回true!

   

 4.replace(char oldChar, char newChar) 

返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 那么问题来了,下面这一段输出的是什么呢? 


Strings="I Love You !";

s.replace("Love","Fxxk");

System.out.println(s);

如果你说是 I Fxxk You !, 那就真的是欠fxxk了~ 别忘了,String 是不可变的。所以呢,s 还是 I Love You, 而String s1 = s.replace("Love", "Fxxk"); 这样的s1 才是"I Fxxk You !"

  

  5. hashCode() 

  返回此字符串的哈希码。 哈希码是怎么算出来的?

 
   
   
 
  1. System.out.println("I Love You !".hashCode()); //-1710377367

我们知道我们的 "I Love You !" 被放到了char数组中。 hashcode的算法是这样的:

 
   
   
 
  1. 31 * 0 + val[0]

  2. 31 * (31 * 0 + val[0]) +  val[1]

  3. 31 * (31 * (31 * 0 + val[0]) +  val[1]) + val[2]

  4. ...

它的代码实现是:

 
   
   
 
  1. public int hashCode() {

  2.        int h = hash;

  3.        if (h == 0 && value.length > 0) {

  4.            char val[] = value;

  5.            for (int i = 0; i < value.length; i++) {

  6.                h = 31 * h + val[i];

  7.            }

  8.            hash = h;

  9.        }

  10.        return h;

  11.    }

可以看到它的算法,其中char值就是字符对应的ACII编码:

解析java中的String源码

    

6.substring(int beginIndex) 


返回一个新的字符串,它是此字符串的一个子字符串。 可以理解为截取字符串,它的实现就是用数组的copyOfRange将指定数组的指定范围复制到一个新数组:


 
   
   
 
  1. this.value = Arrays.copyOfRange(value, offset, offset+count);









原创干货分享

生活感悟思想

听我吹吹牛逼


       




自愿赞赏

有你的支持后我会

动力满满

分享干货

创建活动

礼物回馈

正向循环

....










版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《解析java中的String源码》的版权归原作者「肯定会」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注肯定会微信公众号

肯定会微信公众号:gh_2789de210a00

肯定会

手机扫描上方二维码即可关注肯定会微信公众号

肯定会最新文章

精品公众号随机推荐