根据java官网文档的描述,String类代表字符串,是常量,他们的值在创建之后是不可变的,究竟String类型是怎么实现这些的呢?

final关键字

在探讨String类型的原理之前,我们应该先弄清楚关于final关键字的使用:

1> 如果final修饰的是类的话,那么这个类是不能被继承的

2> 如果final修饰的是方法的话,那么这个方法是不能被重写的

3> 如果final修饰的是变量的话,那么这个变量的值在运行期间是不能被修改的

当然,关于具体的赋值等操作,可以参考《对象与内存管理》中的最后一点,这里就不再重复了。

String类与final的不解之缘

现在,我们开始探讨String类吧,下面只是String类的部分源代码:

 1 public final class String
 2     implements java.io.Serializable, Comparable<String>, CharSequence
 3 {
 4     /** The value is used for character storage. */
 5     private final char value[]; //用来存储字符串转换而来的字符数组
 6 
 7     /** The offset is the first index of the storage that is used. */
 8     private final int offset; //字符串起始字符在字符数组的位置
 9 
10     /** The count is the number of characters in the String. */
11     private final int count; //字符串分解成字符数组后字符的数目
12 }

从上面代码,我们知道:

1> String类是被final修饰的,从安全角度来说,通过final修饰后的String类是不能被其他类继承的,在最大程度上的保护了该类,从效率上来说,提高了该类的效率,因为final修饰后会比没有用final修饰的快。

2> value[], offet, count也是被final修饰的,这时候三个变量的值必须在编译期间就被确定下来,并且在运行期间不能再被修改了。因此,每次我们每次进行字符串修改、拼接的时候,并不能直接修改当前String对象的值,只能重新创建一个新的对象。

3>我们创建String对象的时候,String对象还使用字符数组(char[])来存储我们的字符串的。

CodePoint & CodeUnit

   1. CodePoint:一个完整的字符就是一个CodePoint(代码点),例如:'A', 'B', 'C'

   2. CodeUnit :一个char就是一个CodeUnit(代码单元),想'A', 'B'这些,他们就只使用了了一个CodeUnit;但是如果是使用UTF-16表示的字符,就需要两个CodeUnit

   下面我们看一个代码:

 1 public class CodePoint {
 2     public static void main(String[] args){
 3         /* 
 4          * 这个字符串中有一个Unicode编码表中附加级别的字符。(是一个数学符号)
 5          * '\u' 后面只能跟 4 个字符,\u1D56B 表示的是 \u1D56 字符和字母 B,
 6          * 所以使用 codePointCount 获得的代码点数量就是 2 了。
 7          */
 8         String str1="\u1D56B"; 
 9         /*
10          *  这个字符串是上面那个字符串采用UTF-16编码方法拆成的两个连续的代码单元中的值
11          *  下面这种方式是采用代理对的方式来表示这个字符,
12          *  虽然说采用两个 Unicode 的代理对来表示,但这只是表示 Unicode 中的一个代码点
13          */
14         String str2="\uD875\uDD6B";  
15 
16         System.out.println(str1);
17         System.out.println(str1.length());  //打印结果:2
18         System.out.println(str1.codePointCount(0,str1.length())); //打印结果:2
19         
20         System.out.println(str2);
21         System.out.println(str2.length()); //打印结果:2
22         System.out.println(str2.codePointCount(0,str2.length())); //打印结果:1
23    }
24 }

 从上面的打印结果我们也可以看出,length() 和 codePointCount()方法的区别是:

   1> codePointCount方法返回的是代码点的数量
   2> length()方法返回的是代码单元的数量

String类常用的构造方法

其实呢,一般情况下,我们使用String类的时候很少通过构造方法来创建String对象的,因为这是不推荐的,但是不知道大家知不知道,

1 String str = "abc";  //只创建了一个String对象

这种通过直接量创建String对象其实就等效于下面使用了通过字符串构造方法创建对象的。

1 char data[] = {'a', 'b', 'c'};
2 String str = new String(data);  //只创建了一个String对象

但是一般情况下使用第二这种方式太麻烦了,所以我们都推荐使用第一种方式创建String对象。

下面我们开始讲解一下String类常用的构造方法吧。

1>无参数的构造方法,这个创建的String对象的值为"",注意是是"",这个就等效于我们String str = "";具体关于参数的相信不用讲大家都应该知道了吧,不记得了的朋友可以看回前面final中列举出的代码注释。

 1 /**
 2  * Initializes a newly created {@code String} object so that it represents
 3  * an empty character sequence.  Note that use of this constructor is
 4  * unnecessary since Strings are immutable.
 5  */
 6 public String() {
 7     this.offset = 0;
 8     this.count = 0;
 9     this.value = new char[0];
10 }

 

 2>使用Stirng对象作为构造方法的参数,需要注意的是,通过该构造方法创建String对象将会产生2个字符串对象,所以不推荐使用(具体为什么是两个,可以参考博文《小学徒进阶系列—JVM对String的处理》)

 1 public String(String original) {
 2     int size = original.count;    //获取源字符串的字符数量
 3     char[] originalValue = original.value;    //获取源字符串的字符数组
 4     char[] v;    //用于新字符串对象存储字符数组
 5     if (originalValue.length > size) {
 6         int off = original.offset;    //获取源字符串起始字符在字符数组的位置
 7         v = Arrays.copyOfRange(originalValue, off, off+size);    //返回将源字符数组复制到新的字符数组中
 8     } else {
 9         // The array representing the String is the same
10         // size as the String, so no point in making a copy.
11         v = originalValue;
12     }
13     this.offset = 0;
14     this.count = size;
15     this.value = v;
16 }

 其实在构造方法中的各行代码里,我想大家在看这行代码的时候,最想知道的应该是Arrays.copyOfRange(char[] original,int from,int to)中各个参数的含义吧,官网中的解释是这样子的:

public static char[] copyOfRange(char[] original,
                                 int from,
                                 int to)
将指定数组的指定范围复制到一个新数组。该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)(必须大于等于 from)可以大于 original.length,在这种情况下,'\\u000' 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。

参数: original
- 将要从其复制一个范围的数组 from - 要复制的范围的初始索引(包括) to - 要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
返回: 包含取自原数组指定范围的新数组,截取或用
0 填充以获得所需长度
抛出: ArrayIndexOutOfBoundsException
- 如果 from < 0 或 from > original.length() IllegalArgumentException - 如果 from > to NullPointerException - 如果 original 为 null

 3>使用字符数组作为String构造方法的参数,前面你我们已经知道了,使用String str = "abc",相当于使用该构造方法创建对象

1 public String(char value[]) {
2     int size = value.length;
3     this.offset = 0;
4     this.count = size;
5     this.value = Arrays.copyOf(value, size);
6 }   

 

 4>同样使用字符数组作为String构造方法的参数,但是并不是全部都是用来构造字符串对象的,而是只使用从offerset起的count个字符作为String对象的值。

 1 public String(char value[], int offset, int count) {
 2     if (offset < 0) {
 3         throw new StringIndexOutOfBoundsException(offset);
 4     }
 5     if (count < 0) {
 6         throw new StringIndexOutOfBoundsException(count);
 7     }
 8     // Note: offset or count might be near -1>>>1.
 9     if (offset > value.length - count) {
10         throw new StringIndexOutOfBoundsException(offset + count);
11     }
12     this.offset = 0;
13     this.count = count;
14     this.value = Arrays.copyOfRange(value, offset, offset+count);
15 }

 

String类常用的方法

在这里,我重点讲解关于String类常用的方法,同时分析它的源代码,具体使用方法和执行结果,读者可以自行尝试哦,我就不累赘的写出来啦,而且我把这些代码缩起来了,避免整篇博文都是源代码,看的辛苦,大家需要看哪个方法的源代码,就直接展开哪个方法就行了,好啦,言归正传,我们开始分析吧。

1> public char charAt(int index)

返回指定索引处的 char 值。索引范围为从 0 到 length() - 1。序列的第一个 char 值位于索引 0 处,第二个位于索引 1 处,依此类推,这类似于数组索引。下面是该方法的源码解析:

1 public char charAt(int index) {
2     if ((index < 0) || (index >= count)) {    //如果index索引 < 0 或者 > 字符串长度, 
3         throw new StringIndexOutOfBoundsException(index);    //抛出超出边界的异常
4     }
5     return value[index + offset];    //如果索引合法,直接取出在字符数组中索引对应的值
6 }
View Code

相关文章:

  • 2021-08-22
  • 2022-01-06
  • 2021-10-13
  • 2021-11-15
  • 2021-11-02
  • 2021-05-04
  • 2022-01-22
  • 2021-05-31
猜你喜欢
  • 2021-11-08
  • 2021-12-05
  • 2021-08-28
  • 2021-05-21
  • 2022-01-28
  • 2021-11-12
  • 2021-05-15
相关资源
相似解决方案