您的位置 首页 java

Java基础之String漫谈(一)

Java-String

1. 导读

String类也是日常开发中经常用到的类, 今天主要分享下我在看String源码时想到的4个问题:

1.1 String为什么是不可变的; 为什么要设计成不可变的;

1.2 hash Code; 为什么是31;

2. String为什么是不可变的;

public final class String

implements java.io.Serializable, Comparable<String>, CharSequence {

/** The value is used for char acter storage. */

private final char value[];

这是String源码的前三行代码, 这三行代码给出四个关键信息:

.1 String类是被final修饰的, 不可被继承;

.2 String 字符串 的底层实现是基于char数组的;

.3 char数组也是被final修饰的, 他的引用是不可被修改的;

.4 value[]被private修饰, 寻遍整个String类, 没有提供value[]公共的getter 和 setter方

法, 那么他的值是不可被外部修改的;

基于以上四点, 我们可以找到String是不可变的原因: String类不可被继承, 那么就不存在引用到

StringChild这种子类, 换言之任何一个声明的String类型引用, 他引用到的对象一定是String对象, 而

不会是其他对象;

当一个String对象被创建时, 其底层的value[]被赋值, 被final修饰, 其引用始终指向同一个内存地

址, 并且String没有提供修改value的方法, 可以认为value[]数组的值是不可被更改的(如果你非要说通过

反射可以修改, 那我只能说反射这种方式不在正常的考虑范围内);

划重点:

.1 基于String整个类, 被final修饰不可继承, 那么String类就是不可变类;

.2 基于String对象, 底层的value[]被final修饰, 不对外提供修改value[]的方法, value[]引用

不变其值不变, 那么整个String对象也就是不可变的;

3. String为什么设计成不可变

上面说明了为什么String是不可变的, 但是为什么要把String设计成不可变的呢? 说不准有人需要个性化定制呢?

我想了下, String类的作者可能有已下几方面的考虑:

.1 线程安全 : 我们都知道因为共享变量可能会被多个线程修改, 会引起线程安全问题, 把String设计成不可变以后, 就不需要考虑多线程的安全性了, 因为每个线程只有读的权限;

.2 关注下源码中的hash:

/** Cache the hash code for the string */

private int hash; // Default to 0

hash的作用是将当前String对象的hash值 缓存 起来, 因为String是不可变的, 因而缓存的hash也是不变的; 那么把hash起来有啥用呢? 举个 HashMap 的例子, 当String作为HashMap的key时, 多次调用String::hashCode时, 只会在第一次计算hash, 后面所有的调用都会取这个Key缓存的hash, 大大提高了效率;

.3 在JVM设计时, 对String有个优化处理:设计了一个String常量池, “”声明的String对象会先去常量池寻找, 不存在则放入常量池, 反之则取常量池中的String对象;假设10K个”str”的引用, 不需要在堆中new 10K个String对象, 而只需要将引用指向同一个String对象即可;

String::intern方法做的也是同样的事情, 关于JVM对这种设计的变迁以及优缺点放到分享intern方法时再说;

.4 在使用int时, 我们是去考虑int是否可变的, 因为在设计时, int类型就已经具有了不可变性了,

所以在设计Integer这个封装类时, Integer也被设计成了不可变类(后面会有专门分享, 这里不展开);

而String在java世界中的受欢迎程度和基本数据类型没什么差别, 甚至一些java的底层设计都是基于String的, 比如反射的类路径等等; 而且想像一下前一秒还在对String判空, 下一秒调用时抛出了NPE, 这种时候你会不会问候下String的设计者;

从语言设计层面考虑, String在设计之初就是希望把他当做基本数据类型来使用, 那么不可变性是必须的;

.5 在java的其他设计中也使用了String, 还是拿HashMap举例, 有个节点A的key是”1″, value是

2; 假设String是可变的, A的key变成了”2″, 这时插入(“2”, 1)的数据, 会将原本的数据覆盖, 但根据插入之初的值, 应该新增一个节点的;甚至HashMap中有两个节点A(“1”, 2), B(“3”, 1), 假设String可变, A节点变成了(“3”, 2); 使用”1″去获取时发现数据不存在, 使用”3″获取时得到的是错误数据;

上述两种情况明显违背了HashMap设计的初衷, HashMap希望将数据缓存起来, 再次获取时可以取到正确的值, 这是基于key的不可变实现的; 所以了世界的和平, String的设计者就打断熊孩子的念想, 把String设计成了不可变;

划重点:

.1 String在JAVA语言设计时就约定了不可变;

.2 String的不可变性保证了线程安全;

.3 String的不可变性提供了不变的hash, 实现了hash的 懒加载 , 提升了效率;

.4 JVM的String常量池是基于String的不可变性实现的;

.5 String的不可变性维护了世界的和平, 防止JAVA底层的一些设计出问题(也有可能String的作者觉得自己的设计非常棒, 防止艺术被污染, 把String设计成了不可变);

4. hashCode以及为什么是31

public int hashCode() {

int h = hash;

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

char val[] = value;

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

h = 31 * h + val[i];

}

hash = h;

}

return h;

}

通过hashCode的源码, 我们可以看到:

.1 这是对Object::hashCode的重写;

.2 实现了懒加载, 只有在第一次调用时才会计算String对象的hash值, 往后所有的调用都会返回缓存的hash值;

.3 String::hashCode的 算法 是:

value[0]*31^(n-1) + value[1]*31^(n-2) + … + value[n-1](n是String字符串的长

度);

.4 31? String::hashCode的算法中31十分显眼, 在看源码的时候会想为什么用31?

4.1 31*h可以被JVM优化成 (h << 5) – h; 众所周知, JAVA中的位运算的速度比乘法运算要

快; 这是一个效率优化的考虑; 那么这又引出了另一个问题: 63 或者 15都可以被JVM优化, 还是没有解答为什么选用31;

4.2 String的hashCode其实用到了一个字符串散列化的算法—-djb2; 这是由Daniel J.

Bernstein提出字符串散列算法; 感兴趣的可以去wiki搜索下;

这个算法用大白话讲就是字符串不断得乘33, 所以又被叫做”Time33″算法;看到这里, 是不是发现String::hashCode只是把33替换成了31, 本质还是”Time33″;

划重点:

.1 String::hashCode同一个String对象只会计算一次hash;

.2 使用了”Time33″算法来重写Object::hashCode;

好了本次分享就到这里, 上面表述有什么问题, 欢迎指正; 若还有String的问题也欢迎一起探讨, 感谢阅读;

文章来源:智云一二三科技

文章标题:Java基础之String漫谈(一)

文章地址:https://www.zhihuclub.com/169490.shtml

关于作者: 智云科技

热门文章

网站地图