jdk源码阅读笔记-String

时间:2023-03-08 18:01:02
jdk源码阅读笔记-String

本人自学java两年,有幸初入这个行业,所以功力尚浅,本着学习与交流的态度写一些学习随笔,什么错误的地方,热烈地希望园友们提出来,我们共同进步!这是我入园写的第一篇文章,写得可能会很乱。

一、什么是String

  String是java中的一个类,位于java.lang包中,也就是我们平常所说的字符串。字符串是我们在编程的过程中用到最多的类,同时在面试中也经常会出现相关的面试题,所以掌握String字符串的特性是很有必要,不仅可以提高对java的理解,同时还能有助于提高编写程序的质量。

  从源码中我们可以看到,其实String内部维护的是一个char数组,对于字符串的操作,其实就是对内部数组的操作。值得注意的是,char数组是用final关键字修饰,也就是说每一次创建字符串的时候,都会创建一个对象(String不变性)

 /** The value is used for character storage. */
private final char value[];

二、String对象的创建

  最常用的字符串创建的方式有两种,第一种是直接用字面量的引用来创建,第二种是用new关键字来创建。两种创建方式存在很大的区别,上面有提到,字符串是不可变的,所以每次创建时都会创建一个新的对象,如果这样一直创建下去的话,势必会浪费很多的内存,java为了解决这个问题提高程序的性能,用第一种方式创建字符串的时候,首先,会先判断创建的字符串在常量池中是否已经存在,如果存在,则将引用指向它,否则创建新的字符串并在常量池中缓存起来,这样一来创建相同的字符串时只需要将引用指向常量池中已存在的字符串即可,无需再创建,从而节约内存;第二种用new关键字创建,这种方式无论常量池中是否已经创建都会创建一个新的对象,所以本人推荐在平常创建字符串的时候使用第一种方式。如下代码可验证:

     String a = "aaa";
        String b = "aaa";
        String c = new String("aaa");
        String d = new String("aaa");
        System.out.println(a == b);//发回true
        System.out.println(a == c);//返回false
        System.out.println(c == d);//返回false

三、String对象的使用

  String中有非常多的api,下面我将介绍一种常用的api:

  1、public char charAt(int index)

  获取指定位置的字符,index指定位置,源码如下:

    public char charAt(int index) {
if ((index < ) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}

这个方法非常简单,通过传入的数字来获取到数组中对应的下标位置的值,然后直接返回该值即可;

应用场景:获取字符串中指定位置的字符,但是一次只能获取一个字符。

  2、public boolean startsWith(String prefix, int toffset)

  这个方法可以用来判断字符串是否以指定的字符或字符串开始,如果是返回true,否则返回false。有两个重载的方法:

  (1)startsWith(String prefix, int toffset):prefix是指需要与原字符串匹配的字符串,toffset是指开始匹配的位置:

 public boolean startsWith(String prefix, int toffset) {
char ta[] = value;//原字符串
int to = toffset;
char pa[] = prefix.value;//匹配字符串
int po = ;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
if ((toffset < ) || (toffset > value.length - pc)) {
return false;
}
    //这里是主要的判断逻辑
while (--pc >= ) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}

  (2)startsWith(String prefix):默认从第一个字符串开始匹配

 public boolean startsWith(String prefix) {
return startsWith(prefix, );
}

  3、public String substring(int beginIndex, int endIndex)

  这个方法是用来截取指定字符串长度方法,返回一个新的字符串。

    public String substring(int beginIndex, int endIndex) {
if (beginIndex < ) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
     //截取字符串长度
int subLen = endIndex - beginIndex;
if (subLen < ) {
throw new StringIndexOutOfBoundsException(subLen);
}
    //调用构造方法
return ((beginIndex == ) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}

构造方法:

 public String(char value[], int offset, int count) {
if (offset < ) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= ) {
if (count < ) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}

这个构造方法主要看Arrays.copyOfRange复制数组的方法,其中value是原数组,offset是开始复制的位置,offset+count是需要复制的长度,该方法返回一个新的数组存放在String维护的数组中,完成字符串的截取。

  4、public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)

  该方法是将当前字符串从 srcBegin 位置到 srcEnd 结束为长度截取字符串放入到 dst 中,从 dstBegin 位置开始放

 public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < ) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

从源码中可以看到,该方法调用了数组复制的方法 System.arraycopy ,其实看了String源码都知道 该类中大量使用了arraycopy这个方法,原因是String内部维护的是一个不可变的数组,所以在对数组进行操作的时候都是要创建新的数组(新的字符串)进行操作。所以非常有必要说一下System.arraycopy这个方法,源码如下:

     /*
   * @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @exception IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @exception ArrayStoreException if an element in the <code>src</code>
* array could not be stored into the <code>dest</code> array
* because of a type mismatch.
* @exception NullPointerException if either <code>src</code> or
* <code>dest</code> is <code>null</code>.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);

这是一个本地静态方法,没有返回值

  src:原数组

  srcPos:原数组开始被复制的位置

  dest:目标数组

  destPos:开始放复制字符的地方

  length:复制的长度

  5、public String concat(String str)

  连接两个字符串,这个方法比较少用到,在写代码时基本没有用过:

public String concat(String str) {
int otherLen = str.length();
if (otherLen == ) {
return this;
}
     //当前字符串长度
int len = value.length;
      //复制当前字符串维护的数组到 buf中,长度为当前字符串长度加拼接字符串长度
char buf[] = Arrays.copyOf(value, len + otherLen);
     //将 str 的字符放到 buf 中
str.getChars(buf, len);
return new String(buf, true);
}

步骤:创建一个两个拼接字符串长度之和大小的数组,然后将两个字符串放入改数组中即可

  6、public static String valueOf(char c)

  这个方法是将指定数据类型转换成字符串类型,有非常多重载的方法,8个基本数据类型基本都有,比较简单,就不用多说了。

  7、public String replace(char oldChar, char newChar)

  该方法将字符串中出现的字符 oldChar 全部替换成 newChar

public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -;
char[] val = value; /* avoid getfield opcode */
       //获取到需要替换字符第一次出现的地方
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
         //临时字符数组
char buf[] = new char[len];
         //将第一次出现替换字符之前的所有字符放到临时字符数组中
for (int j = ; j < i; j++) {
buf[j] = val[j];
}
         //替换字符
while (i < len) {
char c = val[i];
            //将oldChar替换成newChar
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}

  8、public String replaceAll(String regex, String replacement)

  将当前字符串中出现的regex替换成replacement

  9、public String trim()

  去掉字符串前后的空格(不能去除中间部分)

public String trim() {
int len = value.length;
int st = ;
char[] val = value; /* avoid getfield opcode */ while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - ] <= ' ')) {
len--;
}
return ((st > ) || (len < value.length)) ? substring(st, len) : this;
}

  9、public String[] split(String regex, int limit)

  将字符串按照regex规则分割成若干字符串数组,并返回。limit:如果大于0,最多返回limit大小的数组,如果为0,则返回全部可能存在的字符串数组,该方法还有个重载方法,就是不用传入limit参数,与limit等于0的情况是一样的。

  结语:

  1、第一次写东西,所以写得很乱

     2、可能因为自己功力不行,所以感觉整片文章都是粘贴源码的,没有太多实质性的东西

     3、喜欢java的同学,欢迎一起讨论,我们共同进步