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

一丶概述

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

二丶源码及案例

1.String是final类型的

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

什么是不可变类?

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

  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.成员变量

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

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

4.构造函数

  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就可以创建对象?这两种赋值方式有什么不同?

例:

  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)

  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)

  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()

  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)

  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)

  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)

  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()

  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()

  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. }
  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():

  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

  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()

  1. public native String intern();

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

例:

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

a == b就为true

 

int hash32()

  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型数值。

其他方法:

  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

Java源码之String的更多相关文章

  1. java源码阅读String

    1类签名与注释 public final class String implements java.io.Serializable, Comparable<String>, CharSeq ...

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

    String源码与常用方法 1.栗子 代码: public class JavaStringClass { public static void main(String[] args) { Strin ...

  3. [Java源码解析] -- String类的compareTo(String otherString)方法的源码解析

    String类下的compareTo(String otherString)方法的源码解析 一. 前言 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识,  ...

  4. Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库

    http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...

  5. Java 源码刨析 - String

    [String 是如何实现的?它有哪些重要的方法?] String 内部实际存储结构为 char 数组,源码如下: public final class String implements java. ...

  6. Java源码赏析(五)再识 String 类

    在 Java源码赏析(三)初识 String 类   中,我们已经大概理解了String的接口,接下来我们描述一下String的常用工具方法. /** * 为了精简的String结构,之前提到的方法省 ...

  7. Java源码赏析(三)初识 String 类

    由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...

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

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  9. 如何阅读Java源码

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...

随机推荐

  1. ros下基于百度语音的,语音识别和语音合成

    代码地址如下:http://www.demodashi.com/demo/13153.html 概述: 本demo是ros下基于百度语音的,语音识别和语音合成,能够实现文字转语音,语音转文字的功能. ...

  2. JQuery选择器大全 前端面试送命题:面试题篇 对IOC和DI的通俗理解 c#中关于协变性和逆变性(又叫抗变)帮助理解

    JQuery选择器大全   jQuery 的选择器可谓之强大无比,这里简单地总结一下常用的元素查找方法 $("#myELement")    选择id值等于myElement的元素 ...

  3. vue 手动挂载$mount() 获取 $el

    手动挂载$mount() 如果没有挂载的话,没有关联的 DOM 元素.是获取不到$el的. https://vuejs.org/v2/api/#vm-mount var MyComponent = V ...

  4. SpringCloud系列十三:Feign对继承、压缩、日志的支持以及构造多参数请求

    1. 回顾 上文讲解了手动创建Feign,比默认的使用更加灵活. 本文将讲解Feign对继承.压缩的支持以及日志和多参数请求的构造等. 2. Feign对继承的支持 Feign支持继承.使用继承,可将 ...

  5. Atitit.js跨域解决方案attilax大总结 后台java php c#.net的CORS支持

    Atitit.js跨域解决方案attilax大总结 后台java php c#.net的CORS支持 1. 设置 document.domain为一致  推荐1 2. Apache 反向代理 推荐1 ...

  6. Atitit.json类库的设计与实现 ati json lib

    Atitit.json类库的设计与实现 ati json lib 1. 目前jsonlib库可能有问题,可能版本冲突,抛出ex1 2. 解决之道:1 2.1. 自定义json解析库,使用多个复合的js ...

  7. 从DTS到驱动加载的过程分析

    http://blog.csdn.net/iefswang/article/details/40543733 http://www.linuxidc.com/Linux/2013-07/86839.h ...

  8. PHP预定义常量DIRECTORY_SEPARATOR

    PHP预定义常量DIRECTORY_SEPARATOR BY 天涯 · // DIRECTORY_SEPARATOR是一个显示系统分隔符的命令,DIRECTORY_SEPARATOR是PHP的内部常量 ...

  9. 没有局域网环境,全是公网IP可以做LVS吗,该如何做了!请大家赐教!

    没有局域网环境,全是公网IP可以做LVS吗,该如何做了!请大家赐教! 由 wjjava 在 周四, -- : 提交 LVS集群 现在有3台服务器,各有一个公网IP地址.IP地址形式如下: IP1:12 ...

  10. apache gzip

    1.首先启动模块mod_deflate.so和mod_headers.so 2.httpd.conf #文件缓存时间配置<FilesMatch ".(flv|gif|jpg|jpeg| ...