String不得不知
参考文章
String
String并不是基本数据类型,而是一个不可变的对象
””、null、new String()
-
null 表示string还没有new ,也就是说对象的引用还没有创建,也没有分配内存空间
-
””、new String()则说明了已经new了,只不过内部为空,但是它创建了对象的引用,且分配内存空间的。
-
一个空玻璃杯,你不能说它里面什么都没有,因为里面有空气,当然也可以把它弄成真空,null与" "、new String()的区别就象真空与空气一样。
字符串池
-
创建一个字符串对象时,首先检查字符串池,如果有,则不再创建,直接返回引用
-
若没有则创建然后放入到字符串池,并返回新建字符串引用。
-
提高效率,减少了内存空间的占用 。
-
所以在使用字符串的过程中,推荐使用直接赋值(即String s=”aa”),而非 new String(”aa”)。
String和StringBuffer
-
String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。
-
String类表示内容不可改变的字符串。而StringBuffer类表示内容可以被修改的字符串。
-
当你知道字符数据要改变的时候你就可以使用StringBuffer。
StringBuffer与StringBuilder
-
StringBuffer和StringBuilder类都表示内容可以被修改的字符串,StringBuilder是线程不安全的,运行效率高
-
如果一个字符串变量是在方法里面定义,这种情况只可能有一个线程访问它,不存在不安全的因素了,则用StringBuilder。
-
如果要在类里面定义成员变量,并且这个类的实例对象会在多线程环境下使用,那么最好用StringBuffer。
你应该知道的不可变
Q: String s = "Hello";s = s + " world!";这两行代码执行后,原始的String对象中的内容到底变了没有?
A: 没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s原先指向一个String对象,内容是 "Hello",然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
Q: 是否可以继承String类?
A: String类是final类故不可以继承。
hashCode神奇的因子 “31”
先来看看hashcode()的源码
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
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;
}
公式推导
s[0]31^(n-1) + s[1]31^(n-2) + ... + s[n-1]
假设 数组长度 n=3
i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]
h = 31x31x31x0 + 31x31xval[0] + 31xval[1] + val[2]
h = 31^(n-1)xval[0] + 31^(n-2)xval[1] + val[2]
网上流传的原因
-
原文地址 为什么选择31作为乘子
-
第一,31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了31呢?请看第二个原因。
-
31可以被 JVM 优化,
31 * i = (i << 5) - i。
上面两个原因中,第一个需要解释一下,第二个比较简单,就不说了。下面我来解释第一个理由。一般在设计哈希算法时,会选择一个特殊的质数。至于为啥选择质数,我想应该是可以降低哈希算法的冲突率。至于原因,这个就要问数学家了,我几乎可以忽略的数学水平解释不了这个原因。上面说到,31是一个不大不小的质数,是优选乘子。那为啥同是质数的2和101(或者更大的质数)就不是优选乘子呢,分析如下。
这里先分析质数2。首先,假设 n = 6,然后把质数2和 n 带入上面的计算公式。并仅计算公式中次数最高的那一项,结果是2^5 = 32,是不是很小。所以这里可以断定,当字符串长度不是很长时,用质数2做为乘子算出的哈希值,数值不会很大。也就是说,哈希值会分布在一个较小的数值区间内,分布性不佳,最终可能会导致冲突率上升。
上面说了,质数2做为乘子会导致哈希值分布在一个较小区间内,那么如果用一个较大的大质数101会产生什么样的结果呢?根据上面的分析,我想大家应该可以猜出结果了。就是不用再担心哈希值会分布在一个小的区间内了,因为101^5 = 10,510,100,501。但是要注意的是,这个计算结果太大了。如果用 int 类型表示哈希值,结果会溢出,最终导致数值信息丢失。尽管数值信息丢失并不一定会导致冲突率上升,但是我们暂且先认为质数101(或者更大的质数)也不是很好的选择。最后,我们再来看看质数31的计算结果: 31^5 = 28629151,结果值相对于32和10,510,100,501来说。是不是很nice,不大不小。

浙公网安备 33010602011771号