String源码分析

前言:String类在日常开发过程中使用频率非常高,平时大家可能看过String的源码,但是真的认真了解过它么,笔者在一次笔试过程中要求写出String的equals方法,瞬间有点懵逼,凭着大致的理解,算是写出来了,可是下来一翻String的源码顿悟,原来自己写得是多么的low,所以有必要把这些基础知识点记录下来,加深印象。

注:本文jdk源码版本为jdk1.8.0_172


1.String类的基本概念

首先String是不可变对象,其体现主要在String类是被final关键字修饰,因此该对象不能被继承,不能被修改。那你可能要问了为什么我们在日常操作过程中不是可以很方便的修改String的内容吗?其实我们修改其内容是new了一个对象,原来的内容并没有改变。

String内部是通过char数组来存储的内容,从以下源码中可以发现:

注:这里的char数组也是被final修饰。

2.String的构造函数

不知道你注意没,String的构造函数非常的多:

这里挑选几个笔者认为有特点的构造函数进行分析,其它构造函数请查看相应源码。

#1.默认构造函数:

1  public String() {
2         this.value = "".value;
3     }

注:默认构造函数的实现非常简单,就是返回空字符串的数组形式。

#2.入参为char数组:

1  public String(char value[]) {
2         this.value = Arrays.copyOf(value, value.length);
3     }

分析:如果传入char数组,是通过Arrays#copyOf方法进行拷贝的。

#3.入参为String对象:

1   public String(String original) {
2         this.value = original.value;
3         this.hash = original.hash;
4     }

分析:如果入参为String对象,则直接进行相应的赋值即可(value和hash值)。

#4.入参为StringBuffer:

1    public String(StringBuffer buffer) {
2         synchronized(buffer) {
3             this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
4         }
5     }

分析:这里对StringBuffer进行了加锁,然后再进行拷贝操作。为什么要对StringBuffer加锁呢?StringBuffer不是线程安全的吗?其实这里对其进行加锁正是为了保证在多线程环境下只能有一个线程去操作StringBuffer对象,这里需要注意一下。

#5.入参为StringBuilder:

1   public String(StringBuilder builder) {
2         this.value = Arrays.copyOf(builder.getValue(), builder.length());
3     }

分析:对比StringBuffer入参,如果用StringBuilder作为入参是不加锁操作的,因为StringBuilder本身线程不安全,但会提升性能,并且其源码上也做出了相应注释。

注意:以StringBuffer和StringBuilder为入参的构造函数这里要特别关注一下:StringBuffer线程安全,为了保证在String中也线程安全所以需要加锁,而StringBuilder非线程安全,因此不需要加锁操作,直接进行拷贝即可。

3.hashCode方法

 1     public int hashCode() {
 2         int h = hash;
 3         if (h == 0 && value.length > 0) {
 4             char val[] = value;
 5 
 6             for (int i = 0; i < value.length; i++) {
 7                 h = 31 * h + val[i];
 8             }
 9             hash = h;
10         }
11         return h;
12     }

分析:String的hashCode方法还是比较简单的,它是遍历char数组以31为基数做一个累加操作。注意这里是以31来作为的基数,为什么取31作为基数可参考:String hashCode 方法为什么选择数字31作为乘子

4.equals方法

 1 public boolean equals(Object anObject) {
 2         if (this == anObject) {
 3             return true;
 4         }
 5         if (anObject instanceof String) {
 6             String anotherString = (String)anObject;
 7             int n = value.length;
 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     }

分析:首先会判断是否是同一个对象,如果是,则直接返回true;其次判断传入对象是否为String对象,如果对象不匹配直接返回false,否则依次比较两个对象的char,只要发现一个不相等,则直接返回false,停止循环。这里的写法还是比较简洁的,在日常开发中可以利用起来。

5.关于不可变对象

关于不可变对象,这里看一段源码就清楚了

 1   public String substring(int beginIndex) {
 2         if (beginIndex < 0) {
 3             throw new StringIndexOutOfBoundsException(beginIndex);
 4         }
 5         int subLen = value.length - beginIndex;
 6         if (subLen < 0) {
 7             throw new StringIndexOutOfBoundsException(subLen);
 8         }
 9         return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
10     }

截取函数,直接看第9行代码处,如果beginIndex==0,返回的是当前对象,否则这里是new的一个新对象,其实String中的很多函数都是这样的操作,具体可翻看源码阅读一下。


总结

#1.String类在日常开发中经常使用,但可能对其源码并不是十分了解,通过分析其源码,了解更多看似简单的知识点。

#2.String类的构造函数非常多,需要注意一下,特别是StringBuffer和StringBuilder为入参的构造函数。

#3.String是不可变对象

#4.String#hashCode的计算方式,以31为计算基数。

by Shawn Chen,2019.08.20日,上午。

转载于:https://www.cnblogs.com/developer_chan/p/11379693.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于Java String类的replace方法,其源码如下: ``` public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; } ``` 这个方法用于将字符串中的一个字符替换成另一个字符,并返回新的字符串。我们来逐行分析一下它的源码: 1. 如果要替换的字符和替换后的字符相同,则直接返回原字符串。 ``` if (oldChar != newChar) { ``` 2. 获取字符串的长度和字符数组。 ``` int len = value.length; char[] val = value; /* avoid getfield opcode */ ``` 3. 在字符数组中查找要替换的字符。 ``` int i = -1; while (++i < len) { if (val[i] == oldChar) { break; } } ``` 4. 如果找到了要替换的字符,则将其替换为新字符,否则直接返回原字符串。 ``` if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } ``` 5. 如果要替换的字符不存在,则直接返回原字符串。 ``` return this; ``` 总体来说,这个方法的实现比较简单,就是遍历字符数组,找到要替换的字符,然后再将其替换成新字符,最后返回新的字符串。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值