【Java基础面试题027】Java的StringBuilder是怎么实现的?
回答重点
StringBuilder主要是为了解决String对象的不可变性问题,还有单线程环境下StringBuffer的性能问题。提供了高效动态的字符串拼接和修改操作。大致需要实现append、insert等功能
大致核心实现如下:
- 内部使用字符数组(char[] value)来存储字符序列
- 通过方法如append()、insert()等操作,直接修改内部的字符数组,而不会像String那样创建新的字符串常量池对象
- 每次进行字符串操作时,如果当前容量不足,它会通过扩展数组容量来容纳新的字符,按2倍的容量扩展,以减少扩展次数,提高性能
扩展知识
深入剖析StringBuilder
对于这类题目,因为已经有现有的实现作为参考,所以回答诸如此类的问题,不要急,先回想一下平日用这个StringBuilder都用了哪些方法
可以看我这一篇博客:【Java】StringBuilder类和StringBuffer类的简单教程-CSDN博客
- append
- insert
- delete
- replace
- charAt
- ...
大致就这么几个,每必要说太全,这不是小学课文背诵,关键方法提出来就行了
脑子浮现这几个方法后哦,直接按照上述的回答重点说出来即可。
实际上StringBuilder底层使用char数组来存储字符,并且用count来记录存放的字符数。
回答重点提到了char数组,这里可能会被面试官插入问:String底层不也是用char数组存放的吗?二者有啥区别?
展示的机会就来了!
String是不可变类,内部的成员变量char[]被final修饰了,所以是不可变的,是典型的Immutable类,因此其不可变性,保证了线程安全,能实现字符串常量池等
OK,咱们继续!
由于StringBuilder底层用char[] 存放字符,而数组是连续内存结构,为了防止频繁地复制和申请内存,需要提供capacity参数在初始化数组的时候设置大小为16,这样在预先已经知晓大字符串的情况下,可以减少数组的扩容次数,有效提升效率
这里一定要点破:数组是连续内存的结构,并且要体现出你有节省内存和提高效率的意识,如果了解HashMap应该会有经验
我们来看下调用AbstractStringBuilder这个父类的构造器
可以看到,就是直接new申请数组没啥花头
append
我们来看下append操作
可以看到append有多种实现,毕竟我们平日里啥类型都直接append,那底层是怎么实现这些类型转换的呢?
我们拿append(int)来举个例子,其他类型本地都是一样的
获取参数的位数
并没有用复杂的位运算,用的是查表法,数组中列出各个位数的边界值,然后根据数组下标判断出位数
如果是负数,要加一位符号位
判断是否扩容
判断条件:(原数组长度 + 新整数位数) - 原数组长度 > 0
如果长度不够,新建指定大小数组并复制
扩容两倍 + 2,多的2是用来微调的,考虑空间使用和性能的平衡
主要逻辑在途中标识了,将上面主要思路说一下即可
面试官可能会问:怎么扩容的呀?
先扩容两倍+2,Arrays.copyOf()拷贝
insert
先转为字符串
delete
总结
这样看下来,想必对 StringBuilder的内部实现已经很清晰了吧!就是数组的操作,而数组的特性就是内存连续,下标访问快。
针对内存连续这点,又要保持StringBuilder的动态性,那不可避免的就需要扩容操作,扩容操作简单来说就是申请一个更大char数组,把老char数组的数据拷贝过去。
对了,从源码来看,StringBuilder没有实现缩容操作。
所以回答这个设计题的时候,先说下需要实现哪些关键方法:append、delete 等等,然后点明底层是char数组实现,在执行append、insert等操作的时候需要先判断数组容量是否足够容纳字符来判断是否需要扩容,然后修改之类的操作就是调用System.arraycopy来完成字符串的变更。
因为原生的 StringBuilder 没有实现缩容操作,所以你可以提一下在 delete的时候,判断下,如果删除的字符过多,为了节省内存,实现缩容的操作。
然后还可以再提一下,char 数组是可以优化的,底层可以用byte 数组+一个 coder 标志位来实现,这样更节省内存,因为char占用两个字节,这样对于latin 系的字符来说,太大了,就很浪费,所以用 byte 数组,然后配备一个 coder来标识所用的编码。
嘿嘿,其实jdk9之后就是这样实现的,但是你可以假装不知道呀,装的像你自己想出来的优化,你看看这多细呀~疯狂加分!