全面理解String

字符串设计和实现考量

我在前面介绍过,String 是lmmutable类的典型实现,原生的保证了基础线程安全,因为你无法对它内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可变,Immutable 对象在拷贝时不需要额外复制数据。

我们再来看看StringBuffer实现的一些细节,它的线程安全是通过把各种修改数据的方法都加上synchronized关键字实现的,非常直白。实,这种简单粗暴的实现方式,非常适合我们常见的线程安全类实现,不必纠结于synchronized性能之类的,有人

”过早优化是万恶之源”, 考虑可靠性、正确性和代码可读性才 是大多数应用开发最重要的因素。

为了实现修改字符序列的目的,StringBuffer 和StringBuilder底层都是利用可修改的(char, JDK 9以后是byte)数组,二者都继承了AbstractStringBuilder,里面包含了基本操作,区别仅在于最终的方法是否加了synchronized.

另外,这个内部数组应该创建成多大的呢?如果太小,拼接的时候可能要重新创建足够大的数组;如果太大,又会浪费空间。目前的实现是,构建时初始字符串长度加16 (这意味着,如果没有构建对象时输入最初的字符串,那么初始值就是16)。我们如果确定

拼接会发生非常多次,而且大概是可预计的,那么就可以指定合适的大小,避免很多次扩容的开销。扩容会产生多重开销,因为要抛弃原有数组,创建新的(可以简单认为是倍数)数组,还要进行arraycopy.

前面我讲的这些内容,在具体的代码书写中,应该如何选择呢?

在没有线程安全问题的情况下,全部拼接操作是应该都用StringBuider实现吗?毕竟这样书写的代码,还是要多敲很多字的,可读性也不理想,下面的对比非常明显。

其实,在通常情况下,没有必要过于担心,要相信Java还是非常智能的。

我们来做个实验,把下面一段代码,利用不同版本的JDK编译,然后再反编译,例如:

先编译再反编译,比如使用JDK9:

JDK8:

而在JDK9中,反编译机构就更简单了:

2.字符串缓存

我们粗略统计过,把常见应用进行堆转储(Dump Heap),然后分析对象组成,会发现平均25%的对象是字符串,并且其中约半数是重复的。如果能避免创建重复字符串,可以有效降低内存消耗和对象创建开销。

String在Java 6以后提供了intern0 方法,目的是提示JVM把相应字符串缓存起来,以备重复使用。在我们创建字符串对象并调用intern()方法的时候,如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来。- 般来说,JVM会将所有的类似"abc"这样的文本字符串,或者字符串常星之类缓存起来。

看起来很不错是吧?但实际情况估计会让你大跌眼镜。一般使用Java 6这种历史版本,并不推荐大量使用intern,为什么呢?魔鬼存在于细节中,被缓存的字符串是存在所谓PermGen里的,也就是臭名昭著的“永久代”,这个空间是很有限的,也基本不会被FullGC之外的垃圾收集照顾到。  以,如果使用不当, O0M就会光顾。

在后续版本中,这个缓存被放置在堆中,这样就极大避免了永久代占满的问题,甚至永久代在JDK 8中被MetaSpace (元数据区)替代了。而且,默认缓存大小也在不断地扩大中,从最初的1009,到7u40以后被修改为60013。你可以使用下面的参数直接打印具体数字,可以拿自己的JDK立刻试验一下。

-xx:+PrintStringTableStatistics

你也可以使用下面的JVM参数手动调整大小,但是绝大部分情兄下并不需要调整,除非你确定它的大小已经影响了操作效率。

-xx:StringTableSize=N

Intern是一一种显式地排重机制,但是它也有一 定的副作用,因为需要开发者写代码时明确调用,一 是不方便,每一 一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一 种污染代码的实践。幸好在E Oracle  )K 8u20之后, 推出;  一个新的特性,也就是G1 GC下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是JVM底层的改变,并不需要Java类库做什么修改。

注意这个功能目前是默认关闭的,你需要使用下面参数开启,并且记得指定使用G1 GC:

_yx.icectrinneduniication-XX: +UseStringDeduplication

前面说到的几个方面,只是Java底层对字符串各种优化的一角,在运行时,字符串的一些基础操作会直接利用JVM内部的Intrinsic 机制,往往运行的就是特殊优化的本地代码,而根本就不是Java代码生成的字节码。Intrinsic 可以简单理解为,是一种利用native方式hard-coded的逻辑,算是一种特别的内联,很多优化还是需要直接使用特定的CPU指令,具体可以看相关源码,搜索"string" 以查找相关Intrinsic 定义。当然,你也可以在启动实验应用时,使用下面参数,了解intrinsic发生的状态。

可以看出,仅仅是字符串一个实现,就需要Java平台工程师和科学家付出如此大且默默无闻的努力,我们得到的很多便利都是来源于此。

我会在专栏后面的JVM和性能等主题,详细介绍JVM内部优化的一些方法,如果你有兴趣可以再深入学习。即使你不做JVM开发或者暂时还没有使用到特别的性能优化,这些知识也能帮 助你增加技术深度。

3.String自身的的演化

如果你仔细观察过Java的字符串,在历史版本中,它是使用char数组来存数据的,这样非常直接。但是Java中的char是两个bytes大小,拉丁语系语言的字符,根本就不需要太宽的char,这样无区别的实现就造成了一定的浪费。密度是编程语言平台永恒的话题,因为归根结底绝大部分任务是要来操作数据的。

其实在Java 6的时候,Oracle JDK就提供了压缩字符串的特性,但是这个特性的实现并不是开源的,而且在实践中也暴露出了-些问题,所以在最新的JDK版本中已经将它移除了。

在Java9中,我们引入了Compact Strings的设计,对字符串进行了大刀阔斧的改进。将数据存储方式从char数组,改变为一个byte数组加上一个标识编码的所谓coder,并且将相关字符串操作类都进行了修改。另外,所有相关的Intrinsic之类也都进行了

写,以保证没有任何性能损失。

虽然底层实现发生了这么大的改变,但是Java字符串的行为并没有任何大的变化,所以这个特性对于绝大部分应用来说是透明的,绝大部分情况不需要修改已有代码。

当然,在极端情况下,字符串也出现了-些能力退化,比如最大字符串的大小。你可以思考下,原来char数组的实现,字符串的最大长度就是数组本身的长度限制,但是替换成byte数组,同样数组长度下,存储能力是退化了一倍的! 还好这是存在于理论中的极限,还没有发现现实应用受此影响。

在通用的性能测试和产品实验中,我们能非常明显地看到紧凑字符串带来的优势,即更小的内存占用、更快的操作速度。

今天我从String、StringBuffer 和StringBuilder的主要设计和实现特点开始,分析了字符串缓存的intern机制、非代码侵入性的虚拟机层面排重、Java 9中紧凑字符的改进,并且初步接触了JVM的底层优化机制intrinsic。 从实践的角度,不管是CompactStrings还是底层intrinsic优化,都说明了使用Java基础类库的优势,它们往往能够得到最大程度、最高质量的优化,而且只要升级JDK版本,就能零成本地享受这些益处。

总结:

1 String

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(一)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(二)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Redis常见面试题汇总(300+题)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
00+题)

[外链图片转存中…(img-QDQClakN-1713529143935)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值