String源码深度剖析——硬盒

举报
莫逸风 发表于 2022/07/20 12:54:54 2022/07/20
【摘要】 基于JDK1.8,String类的深度剖析

String源码解读(JDK1.8)

1. String类关系

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
1.1 final

String类被final关键字修饰,不能被继承,赋值后不可修改。

1.2 实现接口
  • java.io.Serializable:详解

    序列化接口,标记接口(Marker Interface),实现了标记接口的类仅仅是给自身贴了个”标记“,未实现该接口无法被序列化。

  • Comparable<String>

    比较器,值提供一个compareTo方法,用于比较两个字符串大小,下面会讲解该方法。

  • CharSequence

    CharSequence是字符值的可读序列。此接口提供对多种不同类型的字符序列的统一只读访问。

2. 成员变量

查看String源码我们可以看到,String内部存储实际为char数组

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
  /** 用于存储字符串的值 */
  private final byte[] value;
  /** 字符串的HashCode */
  private int hash; // Default to 0
}

3. 构造方法

String字符串有以下四个常用的构造方法:

// String 为参数的构造方法
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
// char[] 为参数构造方法
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
// StringBuffer 为参数的构造方法
public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
// StringBuilder 为参数的构造方法
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

4. equals、compareTo比较字符串

4.1 equals()方法比较字符串

String重写了Object中的equals()方法

  1. 引用是否相同。
  2. 判断变量是否为String。
  3. 比较数组长度。
  4. 循环遍历数组中每个字符。
public boolean equals(Object anObject) {
    // 对象引用相同直接返回 true
    if (this == anObject) {
        return true;
    }
    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            // 把两个字符串都转换为 char 数组对比
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 循环比对两个字符串的每一个字符
            while (n-- != 0) {
                // 如果其中有一个字符不相等就 true false,否则继续对比
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

equalsIgnoreCase:忽略字符串的大小写进行字符串对比

4.2 compareTo()方法比较字符串

compareTo() 方法用于比较两个字符串,返回的结果为 int 类型的值,源码如下:

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    // 获取到两个字符串长度最短的那个 int 值
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;
    int k = 0;
    // 对比每一个字符
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            // 有字符不相等就返回差值
            return c1 - c2;
        }
        k++;
    }
  	// 返回长度的差值
    return len1 - len2;
}

从源码中可以看出,compareTo() 方法会循环对比所有的字符,当两个字符串中有任意一个字符不相同时,则 return char1-char2。

两个方法都是用于比较字符串的,他们有一下不同:

  • equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数;
  • equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。
  • 当 equals() 方法返回 true 时,或者 compareTo() 方法返回 0 时,则表示两个字符串相同。

5. substring、concat、replace字符串处理

5.1 substring()切割字符串
public String substring(int beginIndex, int endIndex) {
  // 开始小于0,结束大于字符串长度,结束小于开始均抛出StringIndexOutOfBoundsException
  if (beginIndex < 0) {
    throw new StringIndexOutOfBoundsException(beginIndex);
  }
  if (endIndex > value.length) {
    throw new StringIndexOutOfBoundsException(endIndex);
  }
  int subLen = endIndex - beginIndex;
  if (subLen < 0) {
    throw new StringIndexOutOfBoundsException(subLen);
  }
  // 0到value.length返回自身,否则新建一个String返回
  return ((beginIndex == 0) && (endIndex == value.length)) ? this
    : new String(value, beginIndex, subLen);
}
5.2 concat()连接字符串
public String concat(String str) {
  int otherLen = str.length();
  if (otherLen == 0) {
    return this;
  }
  int len = value.length;
  // 新建一个字符数组长度为两个字符串的长度
  char buf[] = Arrays.copyOf(value, len + otherLen);
  // 值复制
  str.getChars(buf, len);
  // 新建String
  return new String(buf, true);
}
5.3 replace()替换字符
public String replace(char oldChar, char newChar) {
  if (oldChar != newChar) {
    int len = value.length;
    int i = -1;
    char[] val = value; /* avoid getfield opcode */
		// 找到第一个oldChar
    while (++i < len) {
      if (val[i] == oldChar) {
        break;
      }
    }
    if (i < len) {
      char buf[] = new char[len];
      for (int j = 0; j < i; j++) {
        buf[j] = val[j];
      }
      while (i < len) {
        char c = val[i];
        // 挨个替换oldChar
        buf[i] = (c == oldChar) ? newChar : c;
        i++;
      }
      // 新建String
      return new String(buf, true);
    }
  }
  return this;
}

从这三个字符串处理方法可以看出,三个方法操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。所以进行这些操作后,最原始的字符串并没有被改变。

6. 其他重要方法

indexOf():查询字符串首次出现的下标位置
lastIndexOf():查询字符串最后出现的下标位置
contains():查询字符串中是否包含另一个字符串
toLowerCase():把字符串全部转换成小写
toUpperCase():把字符串全部转换成大写
length():查询字符串的长度
trim():去掉字符串首尾空格
split():把字符串分割并返回字符串数组
join():把字符串数组转为字符串

7. 关联问题

7.1 String 为什么要用 final 修饰?

final:安全,高效

将String设计成不可变的,将不会出现其内部值发生改变而造成的严重系统问题,在日常使用中一个常见场景如dto类,用作数据传输其内部值就应该置为final不可变,已确保上层调用的安全性。

高效可以用JVM中的字符串常量池来举例,字符串常量池为我们缓存了字符串,可以提高系统运行的效率,如果创建两个对象s1,s2都等于字符串”Java“,其在常量池中只存在一个字符串Java,只是有两个引用指向它。

7.2 == 和 equals 的区别是什么?

== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。

Object的equals就是==,而String重写了equals,用于比较字符串的值。

7.3 String 和 StringBuilder、StringBuffer 有什么区别?

因为 String 类型是不可变的,所以在字符串拼接的时候如果使用 String 的话性能会很低,因此我们就需要使用另一个数据类型 StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全,如下源码所示:

@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}

@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

因为它使用了 synchronized 来保证线程安全,所以性能不是很高,于是在 JDK 1.5 就有了 StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer,所以在非并发操作的环境下可使用 StringBuilder 来进行字符串拼接。

7.4 String 的 intern() 方法有什么含义?

new String都是在堆上创建字符串对象。

当调用 intern() 方法时,如果常量池中存在该值直接返回,否则编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。

通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。

7.5 String 类型在 JVM中是如何存储的?

String 常见的创建方式有两种,new String() 的方式和直接赋值的方式

  1. 直接赋值:先去字符串常连池中查找是否已经有此值,如果有则会把引用地址直接指向此值,否则会在常连池中创建,然后再把引用指向此值。
  2. new String(“abc”):首先"abc"在构造之前是一个常量池中的字符串,然后会在堆上创建一个字符串对象,将内存中的对象引用返回。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。