Java源码之String

本文出自:http://blog.csdn.net/dt235201314/article/details/78330377

一丶概述

还记得那会的“Hello World”,第一个程序,输出的String,下面介绍String源码,颇有计算机二级考试习题的感觉。

二丶源码及案例

1.String是final类型的

在Java中,被 final 类型修饰的类不允许被其他类继承,被final修饰的变量赋值后不允许被修改。

什么是不可变类?

所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。
需要注意的是,对于如下代码

[java] view plain copy

  1. String s="abc";
  2. s="def";

你可能会感到疑惑,不是说String是不可变类吗,这怎么可以改变呢,平常我也是这样用的啊。请注意,s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,这点请注意区分。

2.String类实现了Serializable, Comparable, CharSequence接口。

Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法,后面详解

3.成员变量

[java] view plain copy

  1. //用于存储字符串
  2. private final char value[];
  3. //缓存String的hash值
  4. private int hash; // Default to 0

String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

4.构造函数

[java] view plain copy

  1. //不含参数的构造函数,一般没什么用,因为value是不可变量
  2. public String() {
  3. this.value = new char[0];
  4. }
  5. //参数为String类型
  6. public String(String original) {
  7. this.value = original.value;
  8. this.hash = original.hash;
  9. }
  10. //参数为char数组,使用java.utils包中的Arrays类复制
  11. public String(char value[]) {
  12. this.value = Arrays.copyOf(value, value.length);
  13. }
  14. //从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
  15. public String(byte bytes[], int offset, int length, String charsetName)
  16. throws UnsupportedEncodingException {
  17. if (charsetName == null)
  18. throw new NullPointerException("charsetName");
  19. checkBounds(bytes, offset, length);
  20. this.value = StringCoding.decode(charsetName, bytes, offset, length);
  21. }
  22. //调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
  23. public String(byte bytes[], String charsetName)
  24. throws UnsupportedEncodingException {
  25. this(bytes, 0, bytes.length, charsetName);
  26. }
  27. //StringBuffer 和 StringBuider 也可以被当做构造 String 的参数。
  28. //这两个构造方法是很少用到的,因为当我们有了 StringBuffer 或者 StringBuilfer 对象之后可以直接使用他们的 toString 方法来得到 String。
  29. public String(StringBuffer buffer) {
  30. synchronized(buffer) {
  31. this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
  32. }
  33. }
  34. public String(StringBuilder builder) {
  35. this.value = Arrays.copyOf(builder.getValue(), builder.length());
  36. }

相关问题:

String两种不同的赋值方式

String str = new String("abc");
String str = "abc";

为什么String可以不用new就可以创建对象?这两种赋值方式有什么不同?

例:

[java] view plain copy

  1. public class Test {
  2. public static void main(String[] args) {
  3. String   a   =   "ok";   // 新建了一个String对象
  4. String   b   =   "ok";   // 从缓冲池找
  5. String   c   =   new   String("ok");   // 新建一个String对象
  6. String   d   =   new   String("ok");   // 不从缓冲池找,新建一个
  7. System.out.println(a==b);//将输出"true";因为两个变量指向同一个对象。
  8. System.out.println(c==d);//将输出"flase";因为两个变量不指向同一个对象。虽然值相同,只有用c.equals(d)才能返回true.
  9. String e = "a"+"b"+1;
  10. String f = "ab1";
  11. System.out.println(e == f);//将输出"true";因为编译器识别 “e = "a"+"b"+1”等同于“e = "ab1"”
  12. String g = new String("ab1");
  13. String h = "ab1";
  14. System.out.println(g == h);////将输出"flase";因为两个变量不指向同一个对象
  15. }
  16. }

5.常用方法

boolean equals(Object anObject)

[java] view plain copy

  1. boolean equals(Object anObject)
  2. public boolean equals(Object anObject) {
  3. //如果引用的是同一个对象,返回真
  4. if (this == anObject) {
  5. return true;
  6. }
  7. //如果不是String类型的数据,返回假
  8. if (anObject instanceof String) {
  9. String anotherString = (String) anObject;
  10. int n = value.length;
  11. //如果char数组长度不相等,返回假
  12. if (n == anotherString.value.length) {
  13. char v1[] = value;
  14. char v2[] = anotherString.value;
  15. int i = 0;
  16. //从后往前单个字符判断,如果有不相等,返回假
  17. while (n-- != 0) {
  18. if (v1[i] != v2[i])
  19. return false;
  20. i++;
  21. }
  22. //每个字符都相等,返回真
  23. return true;
  24. }
  25. }
  26. return false;
  27. }

equals方法经常用得到,它用来判断两个对象从实际意义上是否相等,String对象判断规则:
1. 内存地址相同,则为真。
2. 如果对象类型不是String类型,则为假。否则继续判断。
3. 如果对象长度不相等,则为假。否则继续判断。
4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

int compareTo(String anotherString)

[java] view plain copy

  1. public int compareTo(String anotherString) {
  2. //自身对象字符串长度len1
  3. int len1 = value.length;
  4. //被比较对象字符串长度len2
  5. int len2 = anotherString.value.length;
  6. //取两个字符串长度的最小值lim
  7. int lim = Math.min(len1, len2);
  8. char v1[] = value;
  9. char v2[] = anotherString.value;
  10. int k = 0;
  11. //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
  12. while (k < lim) {
  13. char c1 = v1[k];
  14. char c2 = v2[k];
  15. if (c1 != c2) {
  16. return c1 - c2;
  17. }
  18. k++;
  19. }
  20. //如果前面都相等,则返回(自身长度-被比较对象长度)
  21. return len1 - len2;
  22. }

理解:

java中的compareto方法,返回参与比较的前后两个字符串的asc码的差值,看下面一组代码
String a="a",b="b";
System.out.println(a.compareto.b);
则输出-1;
若a="a",b="a"则输出0;
若a="b",b="a"则输出1;
 
单个字符这样比较,若字符串比较长呢??
若a="ab",b="b",则输出-1;
若a="abcdef",b="b"则输出-1;
也就是说,如果两个字符串首字母不同,则该方法返回首字母的asc码的差值;
 
如果首字母相同呢??
若a="ab",b="a",输出1;
若a="abcdef",b="a"输出5;
若a="abcdef",b="abc"输出3;
若a="abcdef",b="ace"输出-1;
即参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的asc码差值,如果两个字符串不一样长,可以参与比较的字符又完全一样,则返回两个字符串的长度差值

int hashCode()

[java] view plain copy

  1. int hashCode()
  2. public int hashCode() {
  3. int h = hash;
  4. //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
  5. if (h == 0 && value.length > 0) {
  6. char val[] = value;
  7. //计算过程
  8. //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
  9. for (int i = 0; i < value.length; i++) {
  10. h = 31 * h + val[i];
  11. }
  12. //hash赋值
  13. hash = h;
  14. }
  15. return h;
  16. }

String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。

boolean startsWith(String prefix,int toffset)

[java] view plain copy

  1. boolean startsWith(String prefix,int toffset)
  2. public boolean startsWith(String prefix, int toffset) {
  3. char ta[] = value;
  4. int to = toffset;
  5. char pa[] = prefix.value;
  6. int po = 0;
  7. int pc = prefix.value.length;
  8. // Note: toffset might be near -1>>>1.
  9. //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
  10. if ((toffset < 0) || (toffset > value.length - pc)) {
  11. return false;
  12. }
  13. //从所比较对象的末尾开始比较
  14. while (--pc >= 0) {
  15. if (ta[to++] != pa[po++]) {
  16. return false;
  17. }
  18. }
  19. return true;
  20. }
  21. public boolean startsWith(String prefix) {
  22. return startsWith(prefix, 0);
  23. }
  24. public boolean endsWith(String suffix) {
  25. return startsWith(suffix, value.length - suffix.value.length);
  26. }

起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较

String concat(String str)

[java] view plain copy

  1. public String concat(String str) {
  2. int otherLen = str.length();
  3. //如果被添加的字符串为空,返回对象本身
  4. if (otherLen == 0) {
  5. return this;
  6. }
  7. int len = value.length;
  8. char buf[] = Arrays.copyOf(value, len + otherLen);
  9. str.getChars(buf, len);
  10. return new String(buf, true);
  11. }

concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。


String replace(char oldChar,char newChar)

[java] view plain copy

  1. public String replace(char oldChar, char newChar) {
  2. //新旧值先对比
  3. if (oldChar != newChar) {
  4. int len = value.length;
  5. int i = -1;
  6. char[] val = value; /* avoid getfield opcode */
  7. //找到旧值最开始出现的位置
  8. while (++i < len) {
  9. if (val[i] == oldChar) {
  10. break;
  11. }
  12. }
  13. //从那个位置开始,直到末尾,用新值代替出现的旧值
  14. if (i < len) {
  15. char buf[] = new char[len];
  16. for (int j = 0; j < i; j++) {
  17. buf[j] = val[j];
  18. }
  19. while (i < len) {
  20. char c = val[i];
  21. buf[i] = (c == oldChar) ? newChar : c;
  22. i++;
  23. }
  24. return new String(buf, true);
  25. }
  26. }
  27. return this;
  28. }

这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。

String trim()

[java] view plain copy

  1. public String trim() {
  2. int len = value.length;
  3. int st = 0;
  4. char[] val = value;    /* avoid getfield opcode */
  5. //找到字符串前段没有空格的位置
  6. while ((st < len) && (val[st] <= ‘ ‘)) {
  7. st++;
  8. }
  9. //找到字符串末尾没有空格的位置
  10. while ((st < len) && (val[len - 1] <= ‘ ‘)) {
  11. len--;
  12. }
  13. //如果前后都没有出现空格,返回字符串本身,左右有空格则去掉空格
  14. return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
  15. }

ASCII值比较,“”为32,字母都大于64,可参考ASCII值表

String substring()

[java] view plain copy

  1. // 截取字符串
  2. public String substring(int beginIndex) {
  3. if (beginIndex < 0) { // 如果起始下标<0
  4. throw new StringIndexOutOfBoundsException(beginIndex);
  5. }
  6. int subLen = value.length - beginIndex; // 获取截取长度
  7. if (subLen < 0) { // 如果截取长度<0
  8. throw new StringIndexOutOfBoundsException(subLen);
  9. }
  10. return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
  11. }

[java] view plain copy

  1. // 截取字符串
  2. public String substring(int beginIndex, int endIndex) {
  3. if (beginIndex < 0) { // 如果起始下标<0
  4. throw new StringIndexOutOfBoundsException(beginIndex);
  5. }
  6. if (endIndex > value.length) { // 如果末尾下标>字符数组长度
  7. throw new StringIndexOutOfBoundsException(endIndex);
  8. }
  9. int subLen = endIndex - beginIndex; // 获取截取长度
  10. if (subLen < 0) { // 如果截取长度<0
  11. throw new StringIndexOutOfBoundsException(subLen);
  12. }
  13. return ((beginIndex == 0) && (endIndex == value.length)) ? this
  14. : new String(value, beginIndex, subLen);
  15. }

如:

"hamburger".substring(4) returns "urger"
"hamburger".substring(4, 8) returns "urge"
 "smiles".substring(1, 5) returns "mile"

indexOf():

[java] view plain copy

  1. //该字符跟数组中的每个字符从左往右比较
  2. //lastIndexOf一样只不过是从右往左比较
  3. public int indexOf(int ch, int fromIndex) {
  4. final int max = value.length;
  5. if (fromIndex < 0) {
  6. fromIndex = 0;
  7. } else if (fromIndex >= max) {
  8. // Note: fromIndex might be near -1>>>1.
  9. return -1;
  10. }
  11. if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
  12. // handle most cases here (ch is a BMP code point or a
  13. // negative value (invalid code point))
  14. final char[] value = this.value;
  15. for (int i = fromIndex; i < max; i++) {
  16. if (value[i] == ch) {
  17. return i;
  18. }
  19. }
  20. return -1;
  21. } else {
  22. return indexOfSupplementary(ch, fromIndex);
  23. }
  24. }
  25. //indexOf最终是调用下面的第二个static方法来进行求解的
  26. //求解步骤大概是:
  27. //首先搜索到第一个字符所在的位置,之后逐个比较;
  28. //这里并没有使用kmp算法因此是一个可以优化的地方
  29. public int indexOf(String str, int fromIndex) {
  30. return indexOf(value, 0, value.length,
  31. str.value, 0, str.value.length, fromIndex);
  32. }
  33. static int indexOf(char[] source, int sourceOffset, int sourceCount,
  34. char[] target, int targetOffset, int targetCount,
  35. int fromIndex) {
  36. if (fromIndex >= sourceCount) {
  37. return (targetCount == 0 ? sourceCount : -1);
  38. }
  39. if (fromIndex < 0) {
  40. fromIndex = 0;
  41. }
  42. if (targetCount == 0) {
  43. return fromIndex;
  44. }
  45. char first = target[targetOffset];
  46. int max = sourceOffset + (sourceCount - targetCount);
  47. for (int i = sourceOffset + fromIndex; i <= max; i++) {
  48. /* Look for first character. */
  49. if (source[i] != first) {
  50. while (++i <= max && source[i] != first);
  51. }
  52. /* Found first character, now look at the rest of v2 */
  53. if (i <= max) {
  54. int j = i + 1;
  55. int end = j + targetCount - 1;
  56. for (int k = targetOffset + 1; j < end && source[j]
  57. == target[k]; j++, k++);
  58. if (j == end) {
  59. /* Found whole string. */
  60. return i - sourceOffset;
  61. }
  62. }
  63. }
  64. return -1;
  65. }

split

[java] view plain copy

  1. public String[] split(String regex, int limit) {
  2. char ch = 0;
  3. //1. 如果regex只有一位,且不为列出的特殊字符;
  4. //2.如regex有两位,第一位为转义字符且第二位不是数字或字母,“|”表示或,即只要ch小于0或者大于9任一成立,小于a或者大于z任一成立,小于A或大于Z任一成立
  5. //3.第三个是和编码有关,就是不属于utf-16之间的字符
  6. if (((regex.value.length == 1 &&
  7. ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
  8. (regex.length() == 2 &&
  9. regex.charAt(0) == ‘\\‘ &&
  10. (((ch = regex.charAt(1))-‘0‘)|(‘9‘-ch)) < 0 &&
  11. ((ch-‘a‘)|(‘z‘-ch)) < 0 &&
  12. ((ch-‘A‘)|(‘Z‘-ch)) < 0)) &&
  13. (ch < Character.MIN_HIGH_SURROGATE ||
  14. ch > Character.MAX_LOW_SURROGATE))
  15. {
  16. int off = 0;
  17. int next = 0;
  18. boolean limited = limit > 0;
  19. ArrayList<String> list = new ArrayList<>();
  20. //如果这个字符串中包含了用于分割的ch,则进行下一步操作
  21. while ((next = indexOf(ch, off)) != -1) {
  22. if (!limited || list.size() < limit - 1) {
  23. list.add(substring(off, next));
  24. off = next + 1;
  25. } else {
  26. list.add(substring(off, value.length));
  27. off = value.length;
  28. break;
  29. }
  30. }
  31. // If no match was found, return this
  32. if (off == 0)
  33. return new String[]{this};
  34. // Add remaining segment
  35. if (!limited || list.size() < limit)
  36. list.add(substring(off, value.length));
  37. // Construct result
  38. int resultSize = list.size();
  39. if (limit == 0)
  40. while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
  41. resultSize--;
  42. String[] result = new String[resultSize];
  43. return list.subList(0, resultSize).toArray(result);
  44. }
  45. return Pattern.compile(regex).split(this, limit);
  46. }

详解见:

String.split()方法你可能不知道的一面

String intern()

[java] view plain copy

  1. public native String intern();

intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用

例:

[java] view plain copy

  1. String a = new String("ab1");
  2. String b = new String("ab1").intern();

a == b就为true

 

int hash32()

[java] view plain copy

  1. int hash32()
  2. private transient int hash32 = 0;
  3. int hash32() {
  4. int h = hash32;
  5. if (0 == h) {
  6. // harmless data race on hash32 here.
  7. h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);
  8. // ensure result is not zero to avoid recalcing
  9. h = (0 != h) ? h : 1;
  10. hash32 = h;
  11. }
  12. return h;
  13. }

在JDK1.7中,Hash相关集合类在String类作key的情况下,不再使用hashCode方式离散数据,而是采用hash32方法。这个方法默认使用系统当前时间,String类地址,System类地址等作为因子计算得到hash种子,通过hash种子在经过hash得到32位的int型数值。

其他方法:

[java] view plain copy

  1. public int length() {
  2. return value.length;
  3. }
  4. public String toString() {
  5. return this;
  6. }
  7. public boolean isEmpty() {
  8. return value.length == 0;
  9. }
  10. public char charAt(int index) {
  11. if ((index < 0) || (index >= value.length)) {
  12. throw new StringIndexOutOfBoundsException(index);
  13. }
  14. return value[index];
  15. }

三丶参考文章
String 源码解析,深入认识String

时间: 2024-11-11 09:56:59

Java源码之String的相关文章

从Java源码看String的两种比较方式

String的两种字符串比较方式 == 和 equals方法 ==: ==比较的是字符串在内存中的地址 代码示例: 1 public class EqualsDemo { 2 3 /** 4 * @param args 5 */ 6 public static void main(String[] args) { 7 String s1 = "String"; 8 String s2 = "String"; 9 String s3 = new String(&quo

Java源码分析——String的设计

Tip:笔者马上毕业了,准备开始Java的进阶学习计划.于是打算先从String类的源码分析入手,作为后面学习的案例.这篇文章寄托着今后进阶系列产出的愿望,希望能坚持下去,不忘初心,让自己保持那份对技术的热爱. 因为学习分析源码,所以借鉴了HollisChuang成神之路的大部分内容,并在此基础上对源码进行了学习,在此感谢. 问题的引入 关于String字符串,对于Java开发者而言,这无疑是一个非常熟悉的类.也正是因为经常使用,其内部代码的设计才值得被深究.所谓知其然,更得知其所以然. 举个例

java源码阅读String

1类签名与注释 public final class String implements java.io.Serializable, Comparable<String>, CharSequence String类被定义为final类型的,所以String对象一旦创建了,就是不可变的. String类实现了Serializable接口,表示可以序列化. String类实现了Comparable<String>接口,表示String类型可以相互比较.(通过compareTo方法) S

Java源码解析|String源码与常用方法

String源码与常用方法 1.栗子 代码: public class JavaStringClass { public static void main(String[] args) { String s ="hello"; s = "world"; //内存地址已经修改 原来地址上的值还是不变的 String s2 = "hello"; //从常量值中找到并引用 String s4 = new String("hello"

[LeetCode][8]String to Integer (atoi)解析与模仿Java源码实现 -Java实现

Q: Implement atoi to convert a string to an integer. Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below and ask yourself what are the possible input cases. Notes: It is intended for this problem to be

如何阅读Java源码 阅读java的真实体会

刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的源码很漂亮,但我绝不建议你一开始就读它.我文中会专门谈到这个,暂时不展开. 强烈

[笔记&amp;轮子]java源码 生成本地javadoc api文档

在用Eclipse写java代码时候,有时候因为不知道一个java函数的作用,会通过把鼠移动到java函数上,如果它有javadoc的相关内容就会显示出来.但是并非所有java代码都有javadoc:即使安装了javadoc,在eclipse中如果不进行设定,也可能无法使用. 我在win7下安装的是javase的jdk,发现eclipse中默认的javadoc路径是http://download.oracle.com/javase/7/docs/api/,显然这是一个在线资源,问题是网络总是不稳

JDK源码学习--String篇(二) 关于String采用final修饰的思考

JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JDK源码的时候,有粗略的思考过,今天下班后又把<Thinking in Java>中关于final的内容重新看了一遍,对此写下一些关于自己的理解和想法. String类中final关键字的使用 final关键字,用来描述一块数据不能被改变,两种可能理由:设计.效率 final使用的三种情况:数据.方

Java源码阅读的真实体会

原文:http://zwchen.iteye.com/blog/1154193 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的