面试中的高频知识点--String

本文详细阐述了JDK9中String类结构变化的原因,介绍了byte[]替代char[]以节省空间的方法,以及String的不可变性和内存分配特点。讨论了字符串常量池、内存优化、拼接操作和intern()方法的效率提升。

String的基本特性

String s1 = "atguigu" ;   			// 字面量的定义方式
String s2 =  new String("hello");     // new 对象的方式
  1. String被声明为final的,不可被继承
  2. String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小
  3. String在jdk8及以前内部定义了final char value[]用于存储字符串数据。JDK9时改为byte[]

为什么 JDK9 改变了 String 的结构

为什么改为 byte[] 存储?

  1. String类的当前实现将字符存储在char数组中,每个字符使用两个字节(16位)。
  2. 从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数字符串对象只包含拉丁字符(Latin-1)。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间将不会使用,产生了大量浪费。
  3. 之前 String 类使用 UTF-16 的 char[] 数组存储,现在改为 byte[] 数组 外加一个编码标识存储。该编码表示如果你的字符是ISO-8859-1或者Latin-1,那么只需要一个字节存。如果你是其它字符集,比如UTF-8,你仍然用两个字节存
  4. 结论:String再也不用char[] 来存储了,改成了byte [] 加上编码标记,节约了一些空间
  5. 同时基于String的数据结构,例如StringBuffer和StringBuilder也同样做了修改
// 之前
private final char value[];
// 之后
private final byte[] value

String 的基本特性

String:代表不可变的字符序列。简称:不可变性。

  1. 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
  2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  4. 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

一道笔试题:

public class StringExer {
   
   
    String str = new String("good");
    char[] ch = {
   
   't', 'e', 's', 't'};

    public void change(String str, char ch[]) {
   
   
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
   
   
        StringExer ex = new StringExer();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);//good
        System.out.println(ex.ch);//best
    }

}

分析:change函数被调用时,第一个形参str接收了类的成员变量str的值(虽然名称都是str,但是却是两个独立的String类型的引用变量),注意这两个str自身都是变量且都指向了堆内存中的String对象"good",当我们在change函数内部将str指向了另一个String对象"test ok"后,类的成员变量str仍然保持指向"good",所以最终打印出来就是"good";对于第二个形参ch,它也是接收了类的成员变量ch的值拷贝,这一点和str没有差别,即两个ch都指向了字符数组{ ‘a’, ‘b’, ‘c’ }的首地址,但是ch[0]表示的是字符数组中’a’的地址,修改了它也就修改了字符数组的第一个元素,这个改变在change函数返回之后也会存在。所以本题中两个形参传参的本质区别在于,修改str只是将形参指向了新的对象,对外部的实参没有任何影响,而修改ch[0]是实实在在的修改了字符数组的首元素。
扩展:
1.可以试验一下,在Example中再定义一个字符数组char[] ch2={‘d’};然后在change函数中把ch[0] = ‘g’;这句改成ch=ch2;,那么就会和str传参一样的,change函数返回后不会对类的成员ch有任何影响。
2.本题和“String类是一个final类,不能被继承”以及“String底层的字符数组被声明为private final char value[];所以其值不能被修改”这些String的特性无关。

String 的底层结构

字符串常量池是不会存储相同内容的字符串的

  1. String的String Pool(字符串常量池)是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern()方法时性能会大幅下降。
  2. 使用-XX:StringTablesize可设置StringTable的长度
  3. 在JDK6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快,StringTablesize设置没有要求
  4. 在JDK7中,StringTable的长度默认值是60013,StringTablesize设置没有要求
  5. 在JDK8中,StringTable的长度默认值是60013,StringTable可以设置的最小值为1009

测试不同 StringTable 长度下,程序的性能

/**
 * 产生10万个长度不超过10的字符串,包含a-z,A-Z
 */
public class GenerateString {
   
   
    public static void main(String[] args) throws IOException {
   
   
        FileWriter fw =  new FileWriter("words.txt");

        for (int i = 0; i < 100000; i++) {
   
   
            //1 - 10
           int length = (int)(Math.random() * (10 - 1 + 1) + 1);
            fw.write(getString(length) + "\n");
        }

        fw.close();
    }

    public static String getString(int length){
   
   
        String str = "";
        for (int i = 0; i < length; i++) {
   
   
            //65 - 90, 97-122
            int num = (int)(Math.random() * (90 - 65 + 1) + 65) + (int)(Math.random() * 2) * 32;
            str += (char)num;
        }
        return str;
    }
}

public class StringTest2 {
   
   
    public static void main(String[] args) {
   
   

        BufferedReader br = null;
        try {
   
   
            br = new BufferedReader(new FileReader("words.txt"));
            long start = System.currentTimeMillis();
            String data;
            while((data = br.readLine()) != null){
   
   
                data.intern(); //如果字符串常量池中没有对应data的字符串的话,则在常量池中生成
            }

            long end = System.currentTimeMillis();

            System.out.println("花费的时间为:" + (end - start));//1009:143ms  100009:47ms
        } catch (IOException e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            if(br != null){
   
   
                try {
   
   
                    br.close();
                } catch (IOException e) {
   
   
                    e.printStackTrace();
                }

            }
        }
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值