众所周知,有时Java使用对象池作为包装器和String类型,有时它不会.
例如:
Integer i1 = 1;
Integer i2 = 1;
Integer i3 = new Integer(1);
String s1 = "String";
String s2 = "String";
String s3 = new String ("String");
System.out.println("(i1 == i2) " + (i1 == i2));
System.out.println("(i2 == i3) " + (i2 == i3));
System.out.println("(s1 == s2) " + (s1 == s2));
System.out.println("(s2 == s3) " + (s2 == s3));
Execution result:
(i1 == i2) true
(i2 == i3) false
(s1 == s2) true
(s2 == s3) false
Run Code Online (Sandbox Code Playgroud)
如您所见,基元的装箱从池中获取对象,通过字符串文字创建字符串也会从池中获取对象.这些对象实际上是同一个对象(operator ==对它们返回true).
创建包装器和字符串的其他机制不会从池中获取对象.以这些方式创建的对象实际上是不同的对象(operator ==对它们返回 false).
令我困惑的是池部分使用的事实.
如果是内存问题,为什么不一直使用池?如果它不是内存问题 - 为什么要使用它?
问题是 - 实现这种行为的原因是什么(=部分使用池)?
这个问题相当理论化,但它具有实际应用 - 它可以帮助理解如何正确使用自定义对象池,当然也可以理解Java的工作方式总是很好.
小智 5
如果是内存问题,为什么不一直使用池?
一直汇集所有内容比有时失败的简单缓存更昂贵:您需要更复杂的数据结构(可以使用简单的小数组来汇集小整数)和算法,并且即使在游泳池不会帮助你.此外,您将汇集许多永远不需要的对象,这会浪费内存:您需要更多(无用的)缓存条目,并且您需要管理该缓存(或者让无用的对象保持活动状态).
如果它不是内存问题 - 为什么要使用它?
这是一个记忆问题.此优化可以节省大量内存.汇集每个对象并不一定会减少内存使用,因为不是每个对象都被大量使用.这是一个折衷.所采用的方法为一些常见用例节省了大量内存,而不会过度减慢其他操作或浪费大量内存.
这是一个速度问题,Integer每次分配一个新的都会耗费时间和内存。但出于同样的原因,在启动时分配太多会占用大量内存和时间。
遗憾的是,正如您所发现的,它会导致一些反直觉的行为。
结果就是我们做出了这种奇怪的妥协。Java 标准中讨论了这种行为的原因。(5.7)
如果装箱的值 p 为 true、false、字节、\u0000 到 \u007f 范围内的字符,或者 -128 到 127 之间的 int 或短数字,则令 r1 和 r2 为任意两个装箱转换的结果p。r1 == r2 的情况总是如此。理想情况下,装箱给定的原始值 p 总是会产生相同的引用。实际上,使用现有的实现技术这可能不可行。上述规则是务实的妥协。上面的最后一个子句要求始终将某些公共值装箱到无法区分的对象中。实现可以延迟或急切地缓存它们。
对于其他值,此公式不允许程序员对装箱值的身份进行任何假设。这将允许(但不要求)共享部分或全部这些引用。
这确保了在大多数常见情况下,该行为将是所需的行为,而不会造成过度的性能损失,尤其是在小型设备上。例如,内存限制较少的实现可能会缓存所有字符和短整型,以及 -32K - +32K 范围内的整数和长整型。
太长了;博士
让它完美地工作是不可能的,而且根本不工作也太奇怪了。所以我们让它在“大部分”时间里工作。
| 归档时间: |
|
| 查看次数: |
2342 次 |
| 最近记录: |