在阅读了这篇测量几种对象类型的内存消耗的旧文章后,我惊讶地发现String
在Java中使用了多少内存:
length: 0, {class java.lang.String} size = 40 bytes length: 7, {class java.lang.String} size = 56 bytes
虽然文章有一些提示,以尽量减少这种情况,但我发现它们并不完全令人满意.char[]
用于存储数据似乎是浪费的.大多数西方语言的明显改进是使用byte[]
和编码类似UTF-8,因为你只需要一个字节来存储最频繁的字符,而不是两个字节.
当然可以使用String.getBytes("UTF-8")
和new String(bytes, "UTF-8")
.甚至String实例本身的开销也会消失.但后来有你失去像非常方便的方法equals()
,hashCode()
,length()
,...
Sun有一个专利的byte[]
字符串表示,据我可以告诉.
用于在Java编程环境中高效表示字符串对象的框架
......可以实现这些技术,以便在适当时将Java字符串对象创建为单字节字符的数组...
但是我找不到该专利的API.
我为什么在意?
在大多数情况下,我没有.但我使用包含大量字符串的巨大缓存来处理应用程序,这些字符串可以从更有效地使用内存中受益.
有人知道这样的API吗?或者是否有另一种方法可以保持Strings的内存占用空间小,即使以CPU性能或更丑陋的API为代价?
请不要重复上述文章中的建议:
自己的变种String.intern()
(可能与SoftReferences
)
存储单个char[]
并利用当前String.subString(.)
实现来避免数据复制(讨厌)
更新
我从Sun目前的JVM(1.6.0_10)上的文章中运行了代码.它产生了与2002年相同的结果.
警告:此解决方案现在在较新的Java SE版本中已过时.请参阅下面的其他临时解决方案.
如果您使用HotSpot JVM,那么从Java 6更新21开始,您可以使用此命令行选项:
-XX:+UseCompressedStrings
在JVM选项页上写着:
对字符串使用byte [],可以表示为纯ASCII.(在Java 6 Update 21性能发布中引入)
UPDATE:此功能在以后的版本打破,本来应该由提到的在Java SE 6u25再次固定6u25 B03发行说明(但我们没有看到它在6u25最终发行说明).出于安全原因,错误报告7016213不可见.因此,请务必小心使用并先检查.如同任何-XX
选项,它被认为是实验性,并且更改,恕不在意,所以它可能并不总是最好不要使用在生产服务器的启动纸条.
UPDATE 2013-03 (这要感谢评论阿列克谢鲆):请参阅此相关的问题和其接受的答案.现在这个选择似乎已经死了.这在bug 7129417报告中得到进一步证实.
结束证明手段警告:(丑陋)针对特定需求的解决方案
这是一个开箱即用和低级别的,但因为你问...不要打信使!
如果您需要ASCII就可以了,那么为什么不推出自己的实现呢?
如你所说,你可以byte[]
代替char[]
内部.但那还不是全部.
为了做到更轻量级,而不是将字节数组包装在一个类中,为什么不简单地使用一个辅助类,它主要包含在这些字节数组上运行的静态方法?当然,它会感觉很漂亮C-ish,但它会起作用,并且会为你节省与物体相关的巨大开销String
.
当然,它会遗漏一些不错的功能......除非你重新实现它们.如果你真的需要它们,那么没有太多选择.感谢OpenJDK和许多其他好的项目,你可以很好地推出你自己的fugly LiteStrings
类,它只是操作byte[]
参数.每当你需要召唤一个功能时,你会觉得要洗澡,但你会节省大量的记忆.
我建议,使其酷似String
类的合同,并提供有意义的适配器和建设者从和转换String
,你可能也想有适配器和StringBuffer
和StringBuilder
,以及您可能需要其他一些事情的镜子实现.绝对是一些工作,但可能是值得的(请参阅下面的"让它计数!"部分).
您可以很好地压缩内存中的字符串,并在需要时动态解压缩它们.毕竟,你只需要能够在访问它们时阅读它们,对吧?
当然,暴力意味着:
更复杂(因此维护较少)的代码,
更强大的处理能力
需要相对较长的字符串才能使压缩相关(或者通过实现自己的存储系统将多个字符串压缩为一个,以使压缩更有效).
对于一个令人头疼的问题,当然你可以做到这一切:
C-ish帮助班,
字节数组,
即时压缩存储.
一定要开源.:)
让它成为可数!顺便说一句,请参阅N. Mitchell和G. Sevitsky撰写的关于构建内存高效Java应用程序的精彩演示文稿:[ 2008版 ],[ 2009版 ].
从这个演示文稿中,我们看到一个8-char字符串在32位系统上占64字节(64位系统为96!),其中大部分是由于JVM开销.从这篇文章中我们看到一个8字节的数组只吃"24"字节:12个字节的标头,8个1字节+ 4个字节的对齐).
如果你真的操纵了很多东西(并且可能加快了一些事情,因为你花费更少的时间来分配内存,但是不要引用我并对它进行基准测试,那么这听起来可能是值得的;加上它会很大程度上取决于你的实施).
在Terracotta,我们有一些情况,我们压缩大字符串,因为它们是在网络周围发送的,实际上是压缩它们直到需要解压缩.我们通过将char []转换为byte [],压缩byte [],然后将该byte []编码回原始char []来实现.对于哈希和长度等特定操作,我们可以在不解码压缩字符串的情况下回答这些问题.对于像大XML字符串这样的数据,您可以通过这种方式获得大量压缩.
在网络中移动压缩数据是一个明确的胜利.保持压缩取决于用例.当然,我们有一些旋钮可以关闭它并改变压缩开启的长度等.
这一切都是通过java.lang.String上的字节代码检测来完成的,我们发现这是非常精细的,因为在启动时使用了早期的String,但如果你遵循一些指导则是稳定的.
文章指出了两件事:
字符数组以8个字节的块增加.
char []和String对象之间的大小差异很大.
开销是由于包含char []对象引用和三个int:一个偏移量,一个长度和用于存储String的哈希码的空间,加上简单地作为对象的标准开销.
与String.intern()略有不同,或者String.substring()使用的字符数组对所有字符串使用单个char [],这意味着您不需要将对象引用存储在包装器类似String的对象中.您仍然需要偏移量,并且您会对总共可以拥有的字符数量(大)进行限制.
如果使用字符串标记的特殊结尾,则不再需要长度.这样可以节省4个字节的长度,但是标记需要花费两个字节,加上额外的时间,复杂性和缓冲区溢出风险.
如果您不经常需要,那么不存储哈希值的时空权衡可能对您有所帮助.
对于我曾经使用的应用程序,我需要对大量字符串进行超快速和内存有效处理,我能够以编码形式保留数据,并使用字节数组.我的输出编码与我的输入编码相同,我不需要将字节解码为字符,也不需要再次编码回字节输出.
另外,我可以将输入数据保留在最初读入的字节数组中 - 内存映射文件.
我的对象包括一个int偏移量(适合我的情况的限制),一个int长度和一个int哈希码.
java.lang.String是我想要做的熟悉的锤子,但不是最好的工具.
我认为你应该非常谨慎地从2002年的javaworld.com文章中提出任何想法和/或假设.从那以后的六年里,编译器和JVM发生了很多很多变化.至少,首先针对现代JVM测试您的假设和解决方案,以确保解决方案甚至值得付出努力.
内部UTF-8编码有其优点(例如您指出的较小的内存占用量),但它也有缺点.
例如,确定UTF-8编码字符串的字符长度(而不是字节长度)是O(n)操作.在java字符串中,确定字符长度的成本是O(1),而生成UTF-8表示的是O(n).
这都是关于优先事项的.
数据结构设计通常被视为速度和空间之间的权衡.在这种情况下,我认为Java字符串API的设计者根据这些标准做出了选择:
String类必须支持所有可能的unicode字符.
尽管unicode定义了1字节,2字节和4字节变体,但4字节字符(实际上)非常罕见,因此可以将它们表示为代理对.这就是为什么java使用2字节的char原语.
当人们调用length(),indexOf()和charAt()方法时,他们对字符位置感兴趣,而不是字节位置.为了创建这些方法的快速实现,有必要避免内部UTF-8编码.
像C++这样的语言通过定义三种不同的字符类型并迫使程序员在它们之间进行选择,使程序员的生活变得更加复杂.大多数程序员开始使用简单的ASCII字符串,但是当他们最终需要支持国际字符时,修改代码以使用多字节字符的过程非常痛苦.我认为Java设计者通过说所有字符串都包含2个字节的字符来做出一个很好的折衷选择.