String源码学习分析

1.String简介

String是java.lang包下的一个类,它不属于基本数据类型,是我们使用频率非常高的一个类。它是一个被final修饰的类,不能够被继承。


String源码学习分析_第1张图片
String类图

从上面的类图可以知道:
1.String实现了Serializable接口,支持序列化
2.String实现了Comparable接口,可以进行比较。
3.String实现了CharSequence接口,实现了length()、charAt()、subSequence()等方法。

2.String的成员变量

String源码学习分析_第2张图片
String的成员变量

1.String类型其实就是一个字符数组,这个字符数组用final修饰了,不能修改。所以API中进行修改字符串的方法都是返回一个新的字符串,需要重新引用才行。
2.String缓存了hash值,不用每次都计算hash值,提高了效率。

3.String类的一些方法

StringAPI中方法太多了,我就不一一介绍了,挑几个有价值的方法的分析一下。

1.Java中char类型是Unicode编码的,我们用的字符串可能是"ISO-8859-1"等其它字符集编码,如果直接使用很有可能会出现乱码的问题。

String的一个构造方法,这个构造方法可以传递一个编码名称,让String可以根据编码名称进行解码,以解决乱码的问题。


String源码学习分析_第3张图片
String的一个构造方法

通过源码分析,我们可以知道,它是通过StringCoding工具类进行转码操作的。StringCoding这个工具类会通过Charset去判断字符集是否支持,不支持就抛出异常UnsupportedEncodingException,最终会通过相应的Decoder进行解码。

2.String中split()方法,进行字符串的切割。
public String[] split(String regex, int limit) {
    char ch = 0;
    //特殊情况判断
    // 单字符情况下regex不等于正则表达式的元字符(meta character):.$|()[{^?*+\\
    //双字符情况下regex第一个字符是反斜杠,第二个字符不是Unicode编码中的数字或字母
    if (((regex.value.length == 1 &&
         ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
         (regex.length() == 2 &&
          regex.charAt(0) == '\\' &&
          (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
          ((ch-'a')|('z'-ch)) < 0 &&
          ((ch-'A')|('Z'-ch)) < 0)) &&
        (ch < Character.MIN_HIGH_SURROGATE ||
         ch > Character.MAX_LOW_SURROGATE))
    {
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList list = new ArrayList<>();
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
                list.add(substring(off, next));
                off = next + 1;
            } else {
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        //没有匹配到,将原字符串返回
        if (off == 0)
            return new String[]{this};
        //如果没有限制或者小于限制,将剩余的字符串添加进去
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));
        int resultSize = list.size();
        if (limit == 0) {//将结果集最后为""的字符串一个一个删除
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    return Pattern.compile(regex).split(this, limit);
}

从源码中分析可以得知,split()有两个参数,在limit等于0的情况下,从最后一个子字符串往前,所有的空字符串""都会被清除,所以有可能得到的String数组会小于期望值,如果limit>0,会最多切割limit-1次,没有切割的部分直接添加到结果集中。

注意:参数regex是一个 regular-expression的匹配模式,所以特殊字符需要转义,例如"|"这个字符,需要写成"\|"。

3.String类中有一个本地方法intern()

intern()方法

这个方法的作用是:在JDK1.6中,是将该字符串添加到常量池中并且返回指向该常量的引用,如果常量池中已经存在,则直接返回指向该常量的引用,在JDK1.7以后(包括1.7),常量池从方法区的PermGenSpace移动到了堆中,首先判断这个常量是否存在于常量池,如果存在,判断存在内容是引用还是常量,如果是引用,返回引用地址指向堆空间对象,如果是常量,直接返回常量池常量如果不存在,将当前对象引用复制到常量池,并且返回的是当前对象的引用。
详细分析可以参考一下: 美团技术团队《深入解析String#intern》

4.String相关的一些问题

为什么经常用String作为HashMap的Key呢?

String缓存了hash code,提高了效率。其次是,String是一个不可变类,如果HashMap使用可以改变的类,会存在数据找不到的问题。例如用一个用一个可变类Student,Student stu = new Student();map.put(stu,value);如果stu的值发生了改变,map中的存储的key也会发生改变。下次通过stu访问map的时候,stu的hash值发生了改变,就找不到stu原来所在的位置了。

遇到的一个BUG,有网友能给出答案吗?

从美团技术团队《深入解析String#intern》这篇文章,我可以很清楚的intern的用法了,在JDK1.6和JDK1.7以后的区别,但是,我在测试的时候,遇到了下面一个难以解释的问题~!
测试环境:JDK1.8,Junit4.10

String源码学习分析_第4张图片
测试代码

测试结果

在同样的环境下进行测试,但是两段类似的代码,执行的结果竟然不一样。
但是将test1()中的代码放到main方法中执行,输出的结果又是true。
String源码学习分析_第5张图片
测试代码

测试结果

根据intern在1.8中的作用,s1.intern()应该是将字符串"1"在内存中的引用放置常量池中,s2="11",s2得到的应该是s1在常量池中的引用,所以s1==s2应该结果为true
为什么在Junit测试中,结果为false?而且就单独"1"会出现这个问题,其他字符都不会。

你可能感兴趣的:(String源码学习分析)