Java8源码 java.lang 包 02、String类

1、类的定义

String 类

public final class String implements Serializable, Comparable<String>, CharSequence 

String 类被声明为 final 的,意味着它不可以被继承。

  • 实现了 Serializable 接口使它可以被序列化
  • 实现了 Comparable 接口便于字符串之前的比较
  • 实现了 CharSequence 接口,该接口是 char 值的一个可读序列,代表一个有序字符集合。

CharSequence 接口

CharSequence 接口中声明了如下几个方法:

    public interface CharSequence {
   
        // 获取字符序列长度
        int length();
        // 获取某个指定位置的字符
        char charAt(int index);
        // 获取子序列
        CharSequence subSequence(int start, int end);
        // 将字符序列转换为字符串
        public String toString();
    }

对于一个抽象类或者是接口类,不能使用 new 来进行赋值,但是可以通过以下的方式来进行实例的创建:

CharSequence cs = "hello"; 	// 等价于 CharSequence cs = new String("hello");

CharSequence 就是字符序列,而 String 本质上是通过字符数组实现的!
CharSequence 和 String 都能用于定义字符串,但 CharSequence 的值是可读可写序列,而 String 的值是只读序列。

2、成员变量

    // 字符数组,用于存储字符串中的字符 
    private final char[] value;
    // 缓存字符串的hash值,默认是0
    private int hash;
  • final 修饰 value,表示其一旦被赋值,内存地址是绝对无法修改的
  • private 修饰 value,外部绝对访问不到,且 String 类也没用提供任何访问方法

String 是不可变类。即类值一旦被初始化,就不能再被改变了,如果被修改,将会是新的类。也就是说 String s = “a”; s = “b” 时并不是对变量 s 进行了修改,而是重新指向了新的字符串对象。

String 为什么要设计成不可变类呢?

  • 不可变对象不能被写,避免了引用传值,所以线程安全。
  • 方便使用字符串常量池,节省开销

不可变类的设计通常要遵循以下几个原则:

  1. 将类声明为 final,所以它不能被继承。
  2. 将所有的成员声明为私有的,这样就不允许直接访问这些成员。
  3. 对变量不要提供 setter 方法。
  4. 将所有可变的成员声明为 final,这样只能对它们赋值一次。
  5. 通过构造器初始化所有成员,进行深拷贝(deep copy)。
  6. 在 getter 方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。

常量池

1)class 文件中的常量池

在 class 文件中,除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用。

编写如下代码,并查看其 class 文件内容:

    public class Person {
   
        final int age = 10;
        String name = "老王";
    }

常量池
在上图中可以看到,字面量 10 和 “老王” 出现在 Constant pool 列表中。

字面量是用于表达源代码中一个固定值的表示法。数字,字符串等都有字面量表示。

2)运行时常量池

根据《java虚拟机规范》的规定,class 文件的常量池中的信息,将在类加载后进入方法区中的常量池存储。

3、构造方法

1)利用字节数组来生成字符串

byte 是网络传输或存储的序列化形式。在很多传输和存储的过程中需要将 byte[] 数组和 String 进行相互转化。

不带编码

  • String(byte[] bytes)

        public String(byte[] bytes) {
         
            this(bytes, 0, bytes.length);
        }
    
  • String (byte[] bytes, int offset, int length)

        public String(byte[] bytes, int offset, int length) {
         
            checkBounds(bytes, offset, length);
            // 将字节数组解码为字符数组
            this.value = StringCoding.decode(bytes, offset, length);
        }
    
        private static void checkBounds(byte[] bytes, int offset, int length) {
         
            if (length < 0)
                throw new StringIndexOutOfBoundsException(length);
            if (offset < 0)
                throw new StringIndexOutOfBoundsException(offset);
            if (offset + length > bytes.length)
                throw new StringIndexOutOfBoundsException(offset + length);
        }
    

    如果没有指明解码使用的字符集,StringCoding 的 decode方法首先调用系统的默认编码格式;
    如果没有指定编码格式(charsetName),则默认使用 ISO-8859-1编码格式进行编码操作。

带编码

字符集(charset)是一个系统支持的所有抽象字符的集合。常见的字符集有 ascii字符集、Unicode字符集。

字符编码(charsetName)是对字符集的一套编码规则,将具体的字符进行“数字化”,便于计算机理解和处理。例如常用的 UTF-8 字符编码就是对 Unicode 字符集的一种具体编码规范。

  • String (byte[] bytes, Charset charset)
  • String (byte[] bytes, int offset, int length, Charset charset)
  • String (byte[] bytes, String charsetName)
  • String (byte[] bytes, int offset, int length, String charsetName)
    public String(byte[] bytes, int offset, int length, Charset charset) {
   
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }

其他逻辑不变,参数从 Charset 换成 String,即为后两种的实现

可以看到利用字节数组的核心逻辑是:

    char[] value = StringCoding.decode(charsetName, bytes, offset, length);
    char[] value = StringCoding.decode(charset, bytes, offset, length);
    char[] value = StringCoding.decode(bytes, offset, length);

StringCoding 类的 decode 方法如下:

    private final static ThreadLocal<SoftReference<StringDecoder>> decoder
            = new ThreadLocal<>();

    static char[] decode(String charsetName, byte[] ba, int off, int len)
            throws UnsupportedEncodingException {
   
      // 从线程级缓存中获取反序列化器
        StringDecoder sd = deref(decoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
   	  // 缓存中没有反序列化器,或者虽然有,但是之前反序列化的字符集与这次不同,则重新生成decoder
        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
                              || csn.equals(sd.charsetName()))) {
   
            sd = null;
            try {
   
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    sd = new StringDecoder(cs, csn);
            } catch (IllegalCharsetNameException x) {
   }
            if (sd == null)
                throw new UnsupportedEncodingException(csn);
            // 将decoder放入线程级缓存,以备下次使用
            set(decoder, sd);
        }
        // 完成反序列化
        return sd.decode(ba, off, len);
    }

    // 从缓存中获取反序列器,此处使用了软引用,便于jvm在内存不足时,释放该缓存
    private static <T> T deref(ThreadLocal<SoftReference<T>> tl) {
   
        SoftReference<T> sr = tl.get();
        if (sr == null)
            return null;
        return sr.get();
    }

    // 判断字符集是否支持,并加载字符集处理类
    private static Charset lookupCharset(String csn) {
   
        if (Charset.isSupported(csn)) {
   
            try {
   
                return Charset.forName(csn);
            } catch (UnsupportedCharsetException x) {
   
                throw new Error(x);
            }
        }
        return null;
    }

    // 将对象的软引用放入线程级缓存
    private static void set(ThreadLocal tl, Object ob) {
   
        tl.set(new SoftReference(ob));
    }

就是利用了线程级缓存来缓存 decoder,这样就不必每次都实例化新的 decoder,同时线程级缓存也确保了反序列化的操作是线程安全的。其中 ThreadLocal 和 SoftReference 结合的用法值得借鉴

2)利用字符数组来生成字符串

  • String(char[] value)

        public String(char[] value) {
         
            this.value = Arrays.copyOf(value, value.length);
        }
    
  • String(char[] value, int offset, int count)

        public String(char[] value, int offset, int count) {
         
            if (offset < 0) {
         
                throw new StringIndexOutOfBoundsException(offset);
            }
            if (count <= 0) {
         
                if (count < 0) {
         
                    throw new StringIndexOutOfBoundsException(count);
                }
                if (offset <= value.length) {
         
                    this.value = "".value;
                    return;
                }
            }
            // Note: offset or count might be near -1>>>1.
            if (offset + count > value
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值