一、概述

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 将在堆内开辟出新的空间,占据更多的内存。

JDK1.8源码(三)——java.lang.String类的更多相关文章

  1. JDK1.8源码(三)——java.lang.String 类

    String 类也是java.lang 包下的一个类,算是日常编码中最常用的一个类了,那么本篇博客就来详细的介绍 String 类. 1.String 类的定义 public final class ...

  2. JDK1.8源码(一)——java.lang.Object类

    本系列博客将对JDK1.8版本的相关类从源码层次进行介绍,JDK8的下载地址. 首先介绍JDK中所有类的基类——java.lang.Object. Object 类属于 java.lang 包,此包下 ...

  3. JDK1.8源码(二)——java.lang.Integer 类

    上一篇博客我们介绍了 java.lang 包下的 Object 类,那么本篇博客接着介绍该包下的另一个类 Integer.在前面 浅谈 Integer 类 博客中我们主要介绍了 Integer 类 和 ...

  4. JDK1.8源码(二)——java.lang.Integer类

    一.初识 1.介绍 int 是Java八大基本数据类型之一,占据 4 个字节,范围是 -2^31~2^31 - 1,即 -2147483648~2147483647.而 Integer 是 int 包 ...

  5. JDK1.8源码(八)——java.lang.ThreadLocal类

    https://www.cnblogs.com/xdd666/p/14734047.html ThreadLocal https://www.cnblogs.com/yanfei1819/p/1473 ...

  6. JDK1.8源码(五)——java.util.Vector类

    JDK1.8源码(五)--java.lang. https://www.cnblogs.com/IT-CPC/p/10897559.html

  7. Java源码学习 -- java.lang.String

    java.lang.String是使用频率非常高的类.要想更好的使用java.lang.String类,了解其源代码实现是非常有必要的.由java.lang.String,自然联想到java.lang ...

  8. 从源码分析java.lang.String.isEmpty()

    今天在写代码的时候用到了java.lang.String.isEmpty()的这个方法,之前也用过,今天突发奇想,就看了看源码,了解了解它的实现方法,总结出来,大家可以交流交流. 通常情况下,我们使用 ...

  9. JDK1.8源码(四)——java.util.Arrays类

    一.概述 1.介绍 Arrays 类是 JDK1.2 提供的一个工具类,提供处理数组的各种方法,基本上都是静态方法,能直接通过类名Arrays调用. 二.类源码 1.asList()方法 将一个泛型数 ...

随机推荐

  1. GET请求与POST请求详解

    一.GET请求 常用于获取服务器数据.常见的发起GET请求的方式有:url.href.src.form. 二.GET请求的格式 例子:index.php?userName=harry&pass ...

  2. 关于phpmyadmin getshell

    思考一个问题:如何在获得一个PHP MySQL 搭建网站的phpmyadmin界面后(无论用什么办法,进到phpmyadmin里),进行一个getshell的操作? ...... 0x01山重水复 当 ...

  3. Python数学建模系列(一):规划问题之线性规划

    @ 目录 前言 线性规划 样例1:求解下列线性规划问题 scipy库求解 样例2:求解下列线性规划问题 pulp库求解 样例3.运输问题 说明 结语 前言 Hello!小伙伴! 非常感谢您阅读海轰的文 ...

  4. yield 关键字的认知

    namespace ConsoleDemo{ class Program { static void Main(string[] args) { string[] str = { "1&qu ...

  5. C# wpf中关于binding的converter无效的情况

    最近碰到bingding设置了convert转换无效的问题.困扰了我好久.这里记录分析一下. 先说下现象 我把TextBox的text属性  绑定到了对应的 convert.代码如下 希望吧pd_no ...

  6. Windows10 Dev - Background Execution

    The Universal Windows Platform (UWP) introduces new mechanisms, which allow the applications to perf ...

  7. Java File常见用法

    一.构造方法 File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例. File(String pathname) 通过将给定的路径名字符 ...

  8. 2020年秋游戏开发-Gluttonous Snake

    此作业要求参考https://edu.cnblogs.com/campus/nenu/2020Fall/homework/11577 GitHub地址为https://github.com/15011 ...

  9. rasa form的中断形式 自然机器语言学习 人工智能

    Forms形式 最常见的对话模式之一是从用户那里收集一些信息以便做某事(预订餐厅.调用 API.搜索数据库等).这也称为**槽填充**. 用法# 要在 Rasa Open Source 中使用表单,您 ...

  10. Linux centos7 scp命令

    1. 命令简介 scp(secure copy) 是 linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令,可以在两台 Linux 主机进行复制操作 # 语法 scp [-1246BCpqr ...