JDK源码学习笔记——String
1、学习jdk源码,从以下几个方面入手:
类定义(继承,实现接口等)
全局变量
方法
内部类
2、hashcode
private int hash;
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; }
为什么是31?
(1)计算hashcode值一般选质数
(2)太小的数计算的hashcode值冲突率高,太大的数乘法计算会溢出int范围
(3)有以上两点和实验得 出:31, 33, 37, 39 ,41 作为乘子比较合适
(4)这几个数字中31的乘法运算可以被优化:31 * i == (i << 5) - i
3、构造两种:
(1)直接将otherstr引用给this
(2)数组copy
4、string对“+”的支持
public static void main(string[] args) { string s1="a" + "b";// 编译之后 string s1 = "ab"; string s = "a"; string s2= s+ "b";// 编译之后 string s = (new stringbuilder(string.valueof(s))).append("b").tostring(); }
5、jdk1.7修改substring()
// jdk1.6 string(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; } // substring方法部分 return ((beginindex == 0) && (endindex == count)) ? this : new string(offset + beginindex, endindex - beginindex, value); // jdk1.7 public string(char value[], int offset, int count) { . . . this.value = arrays.copyofrange(value, offset, offset+count); } // substring方法部分 return ((beginindex == 0) && (endindex == value.length)) ? this : new string(value, beginindex, sublen);
jdk1.6的substring:
(1)直接将引用赋值,性能好,共享内部数组节约内存
(2)由于原string的value是private final,可以保证安全性
(3)可能导致内存泄漏
string alongstring = "...a very long string..."; // 很长 string apart = data.substring(2, 4); return apart;
假设从一个很长的字符串中提取一小段内容:
当alongstring不再使用,apart继续使用时,
alongstring被回收,alongstring的value还被apart的value引用,不能被回收
导致内存泄漏
6、编程技巧学习
/** * 先比较是否同一个对象 * 先比较长度 * 虽然代码写的内容比较多,但是可以很大程度上提高比较的效率 */ public boolean equals(object anobject) { if (this == anobject) {// 先比较是否同一个对象 return true; } if (anobject instanceof string) { string anotherstring = (string)anobject; int n = value.length; if (n == anotherstring.value.length) {// 先比较长度 char v1[] = value; char v2[] = anotherstring.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
static int indexof(char[] source, int sourceoffset, int sourcecount, char[] target, int targetoffset, int targetcount, int fromindex) { if (fromindex >= sourcecount) { return (targetcount == 0 ? sourcecount : -1); } if (fromindex < 0) { fromindex = 0; } if (targetcount == 0) { return fromindex; } char first = target[targetoffset]; int max = sourceoffset + (sourcecount - targetcount); for (int i = sourceoffset + fromindex; i <= max; i++) { /* look for first character. 先比较第一个字符*/ if (source[i] != first) { while (++i <= max && source[i] != first); } /* found first character, now look at the rest of v2 */ if (i <= max) { int j = i + 1; int end = j + targetcount - 1; for (int k = targetoffset + 1; j < end && source[j] == target[k]; j++, k++); if (j == end) { /* found whole string. */ return i - sourceoffset; } } } return -1; }
/** * 三目运算符代替多个if */ public boolean equalsignorecase(string anotherstring) { return (this == anotherstring) ? true : (anotherstring != null) && (anotherstring.value.length == value.length) && regionmatches(true, 0, anotherstring, 0, value.length); }
7、intern()
(1)string s = new string("abc");创建个几个对象
类加载时创建"abc"放入常量池 第一个
执行代码时new string() 第二个
(2)string存入常量池方式:
一,直接使用双引号声明出来的string
对象,在类加载时会直接存储在常量池中。
二,如果不是用双引号声明的string
对象,可以使用string
提供的intern
方法。
intern 方法:
如果常量池中存在当前字符串, 就会直接返回当前字符串。 如果常量池中没有此字符串, 会将此字符串放入常量池中后,再返回。
(3)jdk6 和 jdk7 下 intern 的区别
在 jdk1.2 ~ jdk6 的实现中,hotspot 使用永久代实现方法区
jdk7+ 移除永久代 字符串常量和类引用被移动到 java heap中
jdk6 intern:如果常量池中存在当前字符串, 就会直接返回当前字符串。 如果常量池中没有此字符串, 会将此字符串复制一份到方法区,放入方法区中常量池,再返回。
jdk7 intern:如果常量池中存在当前字符串, 就会直接返回当前字符串。 如果常量池中没有此字符串, 会将此字符串的引用存储一份,放入堆中常量池,再返回。
举例:
public static void main(string[] args) { string s = new string("1"); s.intern(); string s2 = "1"; system.out.println(s == s2);// jdk1.6-false jdk1.7-false string s3 = new string("1") + new string("1"); s3.intern(); string s4 = "11"; system.out.println(s3 == s4);// jdk1.6-false jdk1.7-true } public static void main(string[] args) { string s = new string("1"); string s2 = "1"; s.intern(); system.out.println(s == s2);// jdk1.6-false jdk1.7-false string s3 = new string("1") + new string("1"); string s4 = "11"; s3.intern(); system.out.println(s3 == s4);// jdk1.6-false jdk1.7-false }
不再过多解释: (注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向)
(4)应用举例:
static final int max = 1000 * 10000; static final string[] arr = new string[max]; public static void main(string[] args) throws exception { integer[] db_data = new integer[10]; random random = new random(10 * 10000); for (int i = 0; i < db_data.length; i++) { db_data[i] = random.nextint(); } long t = system.currenttimemillis(); for (int i = 0; i < max; i++) { arr[i] = new string(string.valueof(db_data[i % db_data.length])).intern(); } system.out.println((system.currenttimemillis() - t) + "ms"); system.gc(); }
8、private final char[] value;final--->string的长度是不能改变的
(1)常量池高效,常量池里string对象改变,引用受影响
(2)安全,不可变,只能读不能写,保证线程安全
参考资料:
1、《成神之路-基础篇》java基础知识——string相关