【JKD源码】String 三兄弟

一、String

  • String 对象是不可变。
  • 一个 String 字符串实际上是一个char 数组。

1.1 属性

private final char[] value;
private int hash;

1.2 构造器

在这里插入图片描述

1.3 equals

  1. 相比较 == ,相等直接返回 true。
  2. 使用 instanceof 判断是否为字符串类型,不是则直接返回 false。
  3. 转换为字符串类型,并判断两字符串的length是否相等,不相等直接返回 false。
  4. length 相等,依次比较每一个 char 类型。
public boolean equals(Object var1) {
    if (this == var1) {
        return true;
    } else {
        if (var1 instanceof String) {
            String var2 = (String)var1;
            int var3 = this.value.length;
            if (var3 == var2.value.length) {
                char[] var4 = this.value;
                char[] var5 = var2.value;

                for(int var6 = 0; var3-- != 0; ++var6) {
                    if (var4[var6] != var5[var6]) {
                        return false;
                    }
                }

                return true;
            }
        }

        return false;
    }
}

1.3 hashCode

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

 public int hashCode() {
    int var1 = this.hash;
    if (var1 == 0 && this.value.length > 0) {
        char[] var2 = this.value;

        for(int var3 = 0; var3 < this.value.length; ++var3) {
            var1 = 31 * var1 + var2[var3];
        }

        this.hash = var1;
    }

    return var1;
}

为什么选择 31 作为优选乘子

  • 31 是一个 不大不小的质数,可被 JVM 优化,31 * i = (i << 5) - i
  • 如果选择的乘子过小,例如 2。假如 n = 6,则 2^5 = 32。说明当字符串长度很小时,计算出来的哈希值也会很小。导致哈希值分布在一个较小的区间,分布性不佳,冲突率上升
  • 如果选择的乘子过大,例如 101。101^5 = 10 510 100 501,值太大超出了 int 类型的范围,结果溢出,导致信息丢失

1.4 常量池

  • JDK1.6 及之前,常量池放在方法区中;JDK1.7 及以后,常量池放在中。
  • 字面量会存入字符串常量池中。
  • 字符串变量拼接的原理是 StringBuilder
  • 字符串常量拼接的原理是 编译器优化
  • intern() 方法,可以主动将串池中还没有的字符串对象放入常量池。
// 创建 字符串对象"a"后,会在字符串常量池中查找是否已存在。
// 如果串池中不存在,就将 对象"a"存入串池Hashtable中;若已经存在,就会直接使用串池中的对象。
String s1 = "a";
String s2 = "b";
String s3 = "ab";

// new StringBuilder().append("a").append("b").toString();
// 即 s4 = new String("ab"); 存放在堆中
String s4 = s1 + s2;

// javac 在编译期的优化,结果已经在编译期间确定 s5 = "ab"
String s5 = "a" + "b";
// 串池["a", "b"]
// 堆 new String("a"); new String("b"); new String("ab");
String s1 = new String("a") + new String("b");

// 将这个字符串放入串池。有则不放入(s1 存堆中),无则放入且返回给s1(s1 串池中)。【注意】
// 串池["a", "b", "ab"]
String s2 = s1.intern();
System.out.println(s2 == "ab"); // true
System.out.println(s1 == "ab"); // true, 【注意】s1 也为 true。若intern方法不放入,才为 false

1.5 String 并非不可变

public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
  • String 类被 final 所修饰,可以被认为是不可变的对象。
  • value 被 final 所修饰,只能保证引用值不被改变,但是 value 所指向的堆中的数组,才是真正的数据。
  • 只要能够操作堆中的数组,就能改变数据。
  • 可通过反射来改变,但是几乎不会使用反射来操作 String 字符串。所以认为 String 类型是不可变的。
String str = "hello";
System.out.println(str); //hello

// 反射 获取 字段 value
Field field = String.class.getDeclaredField("value");
// value 为 private,改变访问权限
field.setAccessible(true);
// 获取 str 对象上 value属性 的值
char[] value = (char[]) field.get(str);
// 修改
value[0] = 'H';
System.out.println(str); // Hello

String 类设计成不可变的原因:

  1. 引发安全问题。例如数据库中的用户名、密码都是以String形式传入来获得数据库的连接,它们的值是不可变的,可以防止黑客的改变而造成的安全漏洞。
  2. 保证线程安全。在并发场景下,多个线程同时读写资源,由于字符串不可变,不会引发线程安全的问题而保证了线程。

二、StringBuilder

  • StringBuilder 对象是可变的,可通过 append()、insert() 等方法来改变这个字符串对象的字符序列。
  • StringBuilder 对象可以调用 toString() 方法将其转换为一个 String 对象。

2.1、父类

在这里插入图片描述

abstract class AbstractStringBuilder implements Appendable, CharSequence {
   
    char[] value;

    int count; // 实际字符的个数

    AbstractStringBuilder() {}

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

2.2、构造器

默认 数组长度为 16

public StringBuilder() {
	super(16);
}

public StringBuilder(int var1) {
	super(var1);
}

public StringBuilder(String var1) {
	super(var1.length() + 16);
	this.append(var1);
}

public StringBuilder(CharSequence var1) {
	this(var1.length() + 16);
	this.append(var1);
}

2.3 append 方法

(1)父类中的 append

public AbstractStringBuilder append(StringBuffer sb) {
     if (sb == null)
         return appendNull();
     int len = sb.length();
     ensureCapacityInternal(count + len); // 保证容量
     sb.getChars(0, len, value, count);
     count += len;
     return this;
 }

(2)扩容

扩容为 原容量*2 + 2

private void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}


private int newCapacity(int minCapacity) {
     // overflow-conscious code
     int newCapacity = (value.length << 1) + 2;
     if (newCapacity - minCapacity < 0) {
         newCapacity = minCapacity;
     }
     return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
         ? hugeCapacity(minCapacity)
         : newCapacity;
 }

三、StringBuffer

StringBuffer与 StringBuilder 功能相似,只是大部分方法都被synchronized所修饰,所以StringBuffer 是线程安全的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值