重读 JDK8 源码之 String 源码剖析

前言

String ,即字符串类,是 Java 最重要的几个类之一。相信大家在平时的学习或工作中经常与 String 打交道,对 String 的一些用法都是了然于胸了,那么,大家是否有思考过一个问题,String 到底是怎么实现的呢?不会没关系,接下来我会带领大家脱下 String 的伪装,去看 String 最本质的东西。

定义

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

显然,由 final 修饰的 String 是不可变的,Serializable 接口表示可以序列化,Comparable 接口用于比较两个字符串的大小,而 CharSequence 接口表示是一个有序字符的集合。

字段属性

private final char value[];

private int hash; // Default to 0

很明显,String 的底层是一个字符数组,value 用于存储字符串,hash 则缓存了该字符串的哈希码。

构造方法

我们来看看 String 的构造方法,可以发现我们创建一个 String 对象的方法其实还是相当多的。
在这里插入图片描述
下面我们来看下部分构造函数的源码,其实这部分都不难,大家看代码都能看明白的。

	public String() {
        this.value = "".value;
    }

	public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    
	public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

一些重要的方法

length()

length 方法返回字符串的长度,从源码可见,实际上返回的便是 value 字符数组的长度。

	public int length() {
        return value.length;
    }
isEmpty()

根据字符数组的长度进行判空。

	public boolean isEmpty() {
        return value.length == 0;
    }
charAt(int index)

该方法用于获取字符串指定位置的字符,它会判断你传入的位置是否符合规范,符合则返回字符,否则抛出异常。

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

循环遍历两个字符数组,只有当数组长度与每一个字符均相同时才返回 true。

	public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
compareTo(String anotherString)

按字母顺序比较两个字符串,若两个字符串某位置不同,会返回这一位置的字符 Unicode 值之差,在满足上面的情况下,会返回两个字符串长度之差。

	public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        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;
    }
hashCode()

哈希码的算法很简单,大概可以抽象为这个公式:

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

	public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
indexOf(int ch, int fromIndex)

该方法返回指定字符第一次出现的索引。

	public int indexOf(int ch, int fromIndex) {
        final int max = value.length;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            // Note: fromIndex might be near -1>>>1.
            return -1;
        }

		//一个char占用两个字节,如果ch小于2的16次方,绝大多数字符都在此范围内
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] value = this.value;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
        	//当字符大于2的16次方时,处理的少数情况,该方法会首先判断是否是有效字符,然后依次进行比较
            return indexOfSupplementary(ch, fromIndex);
        }
    }
substring(int beginIndex)

该方法返回一个从索引 beginIndex 开始到结尾的子字符串。首先它会判断范围,若范围有问题会直接抛出异常,然后判断开始索引是否为 0,是的话直接返回 this 就好了,否则就新建一个新范围的 String 对象给它。

	public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
concat(String str)

该方法用于连接字符串。

	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);
        return new String(buf, true);
    }
replace(char oldChar, char newChar)

该方法用于做字符替换。

	public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            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 = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }
valueOf(Object obj)

该方法是一个静态方法,用于将其他对象转化为字符串对象,由于它首先会对对象进行判空,所以该方法还是比较安全的。

	public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

如何创建字符串

参考这篇博客:深入理解Java中的String(大坑)

总结

首先我们先来讨论下 String 的不可变问题,String 真的不可变嘛?我们通过前面的学习,知道字符串对象底层实现是一个字符数组,这是被 final 修饰的,但这只能保证引用不变,value 指向的数组才是真实数据,只要我们能对其操作,仍然能改变数据,即使被声明为私有的,我们也能通过反射来进行改变。

那为什么 String 是不可变的呢?我们可以从性能以及安全两方面来进行考虑。

从性能的角度来看,只有 String 不可变,字符串常量池才有意义,它可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,节约堆内存。

从安全的角度来看,首先,若 String 可变,容易引发安全问题,例如用户名、密码等数据的传入,由于 String 的不可变,它们的值自然是不变的,否则可能会被黑客攻击,改变相应的值,从而造成安全漏洞。然后,String 的不可变在多线程环境下不会引发线程的问题,保证了线程安全。最后,String 的不可变保证了 HashCode 的不可变,针对某些容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值