zoukankan      html  css  js  c++  java
  • JDK1.8源码(三)——java.lang.String类

    一、概述

    1、介绍

      String是一个final类,不可被继承,代表不可变的字符序列,是一个类类型的变量。Java程序中的所有字符串字面量(如"abc")都作为此类的实例实现,"abc"是一个对象。
    字符串是常量,创建之后不能更改,包括该类后续的所有方法都是不能修改该对象的,直至该对象被销毁(该类的一些方法看似改变了字符串,其实内部都是创建一个新的字符串)。
      String对象的字符内容是存储在一个字符数组 value[] 中的。

    二、类源码

    1、类声明

    源码示例:

     1  * @author  Lee Boynton
     2  * @author  Arthur van Hoff
     3  * @author  Martin Buchholz
     4  * @author  Ulf Zibis
     5  * @see     java.lang.Object#toString()
     6  * @see     java.lang.StringBuffer
     7  * @see     java.lang.StringBuilder
     8  * @see     java.nio.charset.Charset
     9  * @since   JDK1.0
    10  */
    11 public final class String
    12     implements java.io.Serializable, Comparable<String>, CharSequence {}

      实现了 Serializable 接口,标识该类可序列化。
      实现了 Comparable 接口,用于比较两个字符串的大小。
      实现了 CharSequence 接口,表示是一个有序字符的集合。

    2、类属性

      源码示例:读一下源码中的英文注释。

     1 // 被用于存储字符
     2 /** The value is used for character storage. */
     3 private final char value[];
     4 
     5 // 用于缓存字符串的哈希码.默认是 0
     6 /** Cache the hash code for the string */
     7 private int hash; // Default to 0
     8 
     9 // 实现序列化标识后的UID
    10 /** use serialVersionUID from JDK 1.0.2 for interoperability */
    11 private static final long serialVersionUID = -6849794470754667710L;

      可以看到,String 底层维护了一个 final 的 char[] 。

    3、类构造器

      String 类有多个重载的构造器。

    4、equals() 方法

      String 类重写了 equals 方法,比较的是组成字符串的每一个字符是否相同,如果都相同则返回true,否则返回false。
      源码示例:

     1 public boolean equals(Object anObject) {
     2     // 如果引用相同,则为true
     3     if (this == anObject) {
     4         return true;
     5     }
     6     if (anObject instanceof String) {
     7         String anotherString = (String)anObject;
     8         int n = value.length;
     9         // 判断入参与当前 String 长度是否一致
    10         if (n == anotherString.value.length) {
    11             char v1[] = value;
    12             char v2[] = anotherString.value;
    13             int i = 0;
    14             
    15             // 循环判断两个字符串的每一个字符是否相同
    16             while (n-- != 0) {
    17                 if (v1[i] != v2[i])
    18                     return false;
    19                 i++;
    20             }
    21             return true;
    22         }
    23     }
    24     return false;
    25 }

    5、hashCode() 方法

      源码示例:

     1 public int hashCode() {
     2     int h = hash;
     3     // 判断缓存起来的哈希值是否为 0 且字符长度大于0
     4     if (h == 0 && value.length > 0) {
     5         char val[] = value;
     6 
     7         // 字符串每一个字符都参与 哈希值 的计算
     8         for (int i = 0; i < value.length; i++) {
     9             h = 31 * h + val[i]; // 为什么是 31 ?
    10         }
    11         hash = h;
    12     }
    13     return h;
    14 }

      这个方法不难读懂,中间的 for 循环,计算公式如下:

      s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

      这里,为什么选择31作为乘积因子,而且没有用一个常量来声明?主要原因有两个:
      ①31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。
      ②31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运行更快更省性能。
      具体解释可以参考这篇文章。

    6、charAt() 方法

      源码示例:

    1 public char charAt(int index) {
    2     // 判断索引是否越界
    3     if ((index < 0) || (index >= value.length)) {
    4         throw new StringIndexOutOfBoundsException(index);
    5     }
    6     
    7     // 根据索引下标返回数组中字符
    8     return value[index];
    9 }

    7、compareTo() 和 compareToIgnoreCase() 方法

      源码示例:

     1 public int compareTo(String anotherString) {
     2     int len1 = value.length;
     3     int len2 = anotherString.value.length;
     4     
     5     // 取当前字符串与入参字符串的长度最小值
     6     int lim = Math.min(len1, len2);
     7     char v1[] = value;
     8     char v2[] = anotherString.value;
     9 
    10     int k = 0;
    11     // 循环比较两个字符串的 字符
    12     while (k < lim) {
    13         char c1 = v1[k];
    14         char c2 = v2[k];
    15         
    16         // 如果不相等了,返回他们的 ASCII 差值
    17         if (c1 != c2) {
    18             return c1 - c2;
    19         }
    20         k++;
    21     }
    22     
    23     // 若 lim 的长度值都相同,返回两个字符串长度之差。
    24     return len1 - len2;
    25 }
    26 
    27 
    28 public int compareToIgnoreCase(String str) {
    29     return CASE_INSENSITIVE_ORDER.compare(this, str);
    30 }

      compareToIgnoreCase() 方法在 compareTo 方法的基础上忽略大小写,我们知道大写字母是比小写字母的 ASCII 值小32的。

    8、concat() 方法

      该方法是将指定的字符串拼接到该字符串的末尾。
      源码示例:

     1 public String concat(String str) {
     2     int otherLen = str.length();
     3     // 如果拼接的字符串长度为 0 ,返回当前字符串本身.
     4     if (otherLen == 0) {
     5         return this;
     6     }
     7     
     8     int len = value.length;
     9     // 该方法可以拷贝 value 数组中的值到长度为 len + otherLen 的数组中
    10     // 前面是 value 字符,后面是空
    11     char buf[] = Arrays.copyOf(value, len + otherLen);
    12     
    13     // 将要拼接的字符串放入新数组 buf 后面为空的位置。
    14     str.getChars(buf, len);
    15     
    16     // 重新通过 new 关键字创建了一个新的字符串,原字符串是不变的。
    17     return new String(buf, true);
    18 }

      注意:最后重新通过 new 关键字创建了一个新的字符串,原字符串是不变的。这里也体现了字符序列的不可变性。

    9、indexOf() 方法

      返回指定字符第一次出现的此字符串中的索引。
      源码示例:

     1 public int indexOf(int ch) {
     2     // 从第一个字符开始搜索
     3     return indexOf(ch, 0);
     4 }
     5 
     6 // 从第 fromIndex 个字符开始搜索
     7 public int indexOf(int ch, int fromIndex) {
     8     final int max = value.length;
     9     // 小于0, 默认从 0 开始搜索
    10     if (fromIndex < 0) {
    11         fromIndex = 0;
    12     } else if (fromIndex >= max) {
    13         // Note: fromIndex might be near -1>>>1.
    14         
    15         // 大于了字符串的长度,默认直接找不到,返回 -1
    16         return -1;
    17     }
    18 
    19     //一个char占用两个字节,如果ch小于2的16次方(65536),绝大多数字符都在此范围内
    20     if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
    21         // handle most cases here (ch is a BMP code point or a
    22         // negative value (invalid code point))
    23         final char[] value = this.value;
    24         
    25         // 循环从fromIndex开始查找每一个字符是否是ch
    26         for (int i = fromIndex; i < max; i++) {
    27             if (value[i] == ch) {
    28                 return i;
    29             }
    30         }
    31         
    32         // 找不到,返回 -1
    33         return -1;
    34     } else {
    35         // 当字符大于65536,判断是否是有效字符,然后依次进行比较
    36         return indexOfSupplementary(ch, fromIndex);
    37     }
    38 }

    10、split() 方法

      将该字符串按指定的正则表达式进行切割。对于 split(String regex,int limit) 中 limit 的取值有三种情况:
      ①、limit > 0 ,则pattern(模式)应用 n - 1 次

    1 String str = "a,b,c";
    2 String[] c1 = str.split(",", 2);
    3 
    4 System.out.println(c1.length); // 2
    5 System.out.println(Arrays.toString(c1)); // {"a","b,c"}

      ②、limit = 0 ,则pattern(模式)应用无限次并且省略末尾的空字串

    1 String str = "a,b,c,,";
    2 String[] c1 = str.split(",", 0);
    3 
    4 System.out.println(c1.length); // 3
    5 System.out.println(Arrays.toString(c1)); // {"a","b","c"}

      ③、limit < 0 ,则pattern(模式)应用无限次

    1 String str = "a,b,c,,";
    2 String[] c1 = str.split(",", -1);
    3 
    4 System.out.println(c1.length); // 5
    5 System.out.println(Arrays.toString(c1)); // {"a","b","c","",""}

      源码示例:

     1 public String[] split(String regex) {
     2     return split(regex, 0);
     3 }
     4 
     5 public String[] split(String regex, int limit) {
     6     
     7     /* 1、单个字符,且不是".$|()[{^?*+\"其中一个
     8      * 2、两个字符,第一个是"",第二个大小写字母或者数字
     9      */
    10     /* fastpath if the regex is a
    11      (1)one-char String and this character is not one of the
    12         RegEx's meta characters ".$|()[{^?*+\", or
    13      (2)two-char String and the first char is the backslash and
    14         the second is not the ascii digit or ascii letter.
    15      */
    16     char ch = 0;
    17     if (((regex.value.length == 1 &&
    18          ".$|()[{^?*+\".indexOf(ch = regex.charAt(0)) == -1) ||
    19          (regex.length() == 2 &&
    20           regex.charAt(0) == '\' &&
    21           (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
    22           ((ch-'a')|('z'-ch)) < 0 &&
    23           ((ch-'A')|('Z'-ch)) < 0)) &&
    24         (ch < Character.MIN_HIGH_SURROGATE ||
    25          ch > Character.MAX_LOW_SURROGATE))
    26     {
    27         int off = 0;
    28         int next = 0;
    29         
    30         // 判断模式
    31         boolean limited = limit > 0;
    32         ArrayList<String> list = new ArrayList<>();
    33         while ((next = indexOf(ch, off)) != -1) {
    34             // 当参数limit <= 0 或者 集合list的长度小于 limit-1
    35             if (!limited || list.size() < limit - 1) {
    36                 list.add(substring(off, next));
    37                 off = next + 1;
    38             } else {    // last one
    39                 //assert (list.size() == limit - 1);
    40                 // 判断最后一个list.size() == limit - 1
    41                 list.add(substring(off, value.length));
    42                 off = value.length;
    43                 break;
    44             }
    45         }
    46         // If no match was found, return this
    47         // 如果没有一个能匹配的,返回一个新的字符串,内容和原来的一样
    48         if (off == 0)
    49             return new String[]{this};
    50 
    51         // Add remaining segment
    52         // 当 limit<=0 时,limited==false,或者集合的长度 小于 limit时,截取添加剩下的字符串
    53         if (!limited || list.size() < limit)
    54             list.add(substring(off, value.length));
    55 
    56         // Construct result
    57         // 当 limit == 0 时,如果末尾添加的元素为空(长度为0),则集合长度不断减1,直到末尾不为空
    58         int resultSize = list.size();
    59         if (limit == 0) {
    60             while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
    61                 resultSize--;
    62             }
    63         }
    64         String[] result = new String[resultSize];
    65         return list.subList(0, resultSize).toArray(result);
    66     }
    67     return Pattern.compile(regex).split(this, limit);
    68 }

    11、replace() 和 replaceAll() 方法

      ①将原字符串中所有的oldChar字符都替换成newChar字符,返回一个新的字符串。
      ②将匹配正则表达式regex的匹配项都替换成replacement字符串,返回一个新的字符串。
      源码示例:

     1 public String replace(char oldChar, char newChar) {
     2     if (oldChar != newChar) {
     3         int len = value.length;
     4         int i = -1;
     5         char[] val = value; /* avoid getfield opcode */
     6 
     7         // 找到 value 中的 oldChar 起始位置
     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             // 将前面的字段放入buf
    17             for (int j = 0; j < i; j++) {
    18                 buf[j] = val[j];
    19             }
    20             // 遍历 i 后面的字符
    21             while (i < len) {
    22                 char c = val[i];
    23                 // 将 oldChar 替换成 newChar 放入buf
    24                 buf[i] = (c == oldChar) ? newChar : c;
    25                 i++;
    26             }
    27             // 重新通过 new 关键字创建了一个新的字符串,原字符串是不变的。
    28             return new String(buf, true);
    29         }
    30     }
    31     return this;
    32 }

    12、substring() 方法

      ①返回一个从索引 beginIndex 开始一直到结尾的子字符串。
      ②返回一个从索引 beginIndex 开始,到 endIndex 结尾的子字符串。
      源码示例:

     1 public String substring(int beginIndex) {
     2     if (beginIndex < 0) {
     3         throw new StringIndexOutOfBoundsException(beginIndex);
     4     }
     5     
     6     // 表示从 beginIndex 开始
     7     int subLen = value.length - beginIndex;
     8     if (subLen < 0) {
     9         throw new StringIndexOutOfBoundsException(subLen);
    10     }
    11     
    12     // 如果索引值beginIdex == 0,直接返回原字符串
    13     // 如果不等于0,则返回从beginIndex开始,一直到结尾
    14     return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    15 }

    13、intern() 方法

      这是一个本地方法:返回String对象在常量池中的引用。详情可以参考这篇文章。

    1 public native String intern();

      调用一个String对象的intern()方法,如果常量池中:
      有,直接返回该字符串的引用(存在堆中就返回堆中,存在池中就返回池中)。
      没有,则将该对象添加到池中,并返回池中的引用。

     1 String str1 = "hello"; // 字面量 只会在常量池中创建对象
     2 String str2 = str1.intern();
     3 System.out.println(str1 == str2); //true
     4 
     5 String str3 = new String("world"); // new 关键字只会在堆中创建对象
     6 String str4 = str3.intern();
     7 System.out.println(str3 == str4); // false
     8 
     9 String str5 = str1 + str2; // 变量拼接的字符串,会在常量池中和堆中都创建对象
    10 String str6 = str5.intern(); // 这里由于池中已经有对象了,返回池中的引用
    11 System.out.println(str5 == str6); // true
    12 
    13 String str7 = "hello1" + "world1"; // 常量拼接的字符串,只会在常量池中创建对象
    14 String str8 = str7.intern();
    15 System.out.println(str7 == str8); // true

    三、String 真的不可变吗?

      String 字符串是由许多单个字符组成的,存放在char[] value 字符数组中。
      value 被 final 修饰,只能保证引用不被改变,但是 value 所指向的堆中的数组,才是真实存放的数据,只要能够操作堆中的数组,依旧能改变数据。而且 value 是基本类型构成,那么一定是可变的,即使被声明为 private,我们也可以通过反射来改变。
      代码示例:

     1 public class Main {
     2     public static void main(String[] args) throws Exception {
     3         String str = "vae";
     4         System.out.println(str); // vae
     5         // 获取String类中名为 value 的字段
     6         Field fieldStr = String.class.getDeclaredField("value");
     7         // 因为value是private的,这里修改其访问权限
     8         fieldStr.setAccessible(true);
     9 
    10         // 获取str对象上的value属性的值
    11         char[] value = (char[]) fieldStr.get(str);
    12 
    13         // 将第一个字符修改为 V(小写改大写)
    14         value[0] = 'V';
    15         System.out.println(str); // Vae
    16     }
    17 }

      显然:String 被改变了。但是在代码里,几乎不会使用反射的机制去操作 String 字符串,所以,依然认为 String 类型是不可变的。

      那么,为什么String 类被设计成不可变呢?

      安全:
      ①引发安全问题。比如:数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接;在socket编程中,主机名和端口都是以字符串的形式传入。若改变字符串指向的对象的值,会造成安全漏洞。
      ②保证线程安全。在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程。
      ③HashCode。当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关。若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。
      性能:
      当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的String.intern()方法也失效,每次创建新的 String 将在堆内开辟出新的空间,占据更多的内存。

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    matplotlib.pyplot.gca().set_xlim(初始值,末值) 与 matplotlib.pyplot.xticks()的区别
    MySql学习-5.查询2
    MySql学习-4.查询1
    SQL函数学习(一):substring()函数
    SQL函数学习(二):DATEADD() 函数
    SQL函数学习(三):convert()函数
    sql标识符和格式
    PIVOT就是行转列,UNPIVOT就是列传行
    行列转换之静态、动态、PIVOT方法
    sql Server函数大全
  • 原文地址:https://www.cnblogs.com/originator/p/15335759.html
Copyright © 2011-2022 走看看