java基础之String漫谈(一)

1. 导读

String类也是日常开发中经常用到的类, 今天主要分享下我在看String源码时想到的4个问题:
1.1 String为什么是不可变的; 为什么要设计成不可变的;
1.2 hashCode; 为什么是31;

2. 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修饰的, 不可被继承;
.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) + ... + valuen-1;
.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的问题也欢迎一起探讨, 感谢阅读;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 本系列出于AWeiLoveAndroid的分享,在此感谢,再结合自身经验查漏补缺,完善答案。以成系统。 Java基...
    济公大将阅读 1,528评论 1 6
  • 后来,竟然哼起了歌,下午的阳光刚好打在喉咙上 “要好好地生活,一个人就够了。”我脱下鞋子磕土 突然爱上了自己小小的...
    静书的橡皮擦阅读 2,753评论 0 0
  • “ 爱?爱?我没有爱!我无法爱你!因为!因为!我是!我是文心!”“喂!文心!不要在讲鬼故事啦!我们都害怕了!”...
    大牙猪阅读 212评论 0 0
  • 2016-04-24 11:24:0116 读过董老师的《路还很长,目的地也还很远》之后,我的心久久不能平静...
    暖阳西子阅读 212评论 0 0
  • 前些天因为心里有情绪,对于商定的活动没有心思好好准备。今天早上,突然醒悟过来,如果不好好准备,明天自己肯定会在活动...
    Maggie_WangLi阅读 163评论 0 0