Java 之 String总结篇

来源:互联网 发布:mac不能调节音量 编辑:程序博客网 时间:2024/06/06 07:04

String 存储结构

一般而言,Java 对象在虚拟机的结构如下:

  • 对象头(object header):8 个字节
  • Java 原始类型数据:如 int, float, char 等类型的数据,各类型数据占内存如 1. Java各数据类型所占内存.
  • 引用(reference):4 个字节
  • 填充符(padding)

表 1.Java 各数据类型所占内存

数据类型

占用内存(字节数)

boolean

1

byte

char

2

short

int

4

float

long

8

double

然而,一个 Java 对象实际还会占用些额外的空间,如:对象的 class 信息、ID、在虚拟机中的状态。在 Oracle JDK 的 Hotspot 虚拟机中,一个普通的对象需要额外 8 个字节。

如果对于 String(JDK6)的成员变量声明如下:

  private final char value[];

  private final int offset;

  private final int count;

  private int hash;

那么因该如何计算该 String 所占的空间?

首先计算一个空的 char 数组所占空间,在Java 里数组也是对象,因而数组也有对象头,故一个数组所占的空间为对象头所占的空间加上数组长度,即 8 + 4 = 12 字节 , 经过填充后为 16 字节。

那么一个空 String 所占空间为:

对象头(8 字节)+char 数组(16 字节)+ 3 个 int(3 ×4 = 12 字节)+1 个 char 数组的引用 (4 字节 ) =40 字节。

因此一个实际的 String 所占空间的计算公式如下:

8*( ( 8+2*n+4+12)+7 ) / 8 =8*(int) ( ( ( (n) *2 )+43) /8 )

其中,n 为字符串长度。

 

String 拼接的方法选择

在1.5之后jvm对字符串做很多优化,如:

String a ="aaaaaaaaaaa"+"bbbbbbbbbbb";

通过Xjad进行反编译发现这段代码,发现代码直接在编译期把两个String结合了,如下:

String a ="aaaaaaaaaaabbbbbbbbbbb";

如果是编译期得不到值的情况呢,比如:

String b = a+"cccccccccccc";

反编译后发现被反编译成StringBuilder.append的形式,如下:

String b = (newStringBuilder(String.valueOf(a))).append("cccccccccccc").toString();

 

String 构造的方法选择

常见的创建一个 String 可以用赋值操作符"="或用 new 和相应的构造函数。两种有何区别,举例如下:

 String a1 = “Hello”;

 String a2 = new String(“Hello”);

第一种方法创建字符串时 JVM 会查看内部的缓存池是否已有相同的字符串存在:如果有,则不再使用构造函数构造一个新的字符串,直接返回已有的字符串实例;若不存在,则分配新的内存给新创建的字符串。

第二种方法直接调用构造函数来创建字符串,如果所创建的字符串在字符串缓存池中不存在则调用构造函数创建全新的字符串,如果所创建的字符串在字符串缓存池中已有则再拷贝一份到 Java 堆中。

使用构造函数 string() 带来的内存性能隐患和缓解

仍然以之前的从 csv 文件中截取String 为例,先前我们通过用 new String() 去除返回的 String 中附带的原始String 的方法优化了 subString导致的内存消耗问题。然而,当我们下意识地使用 newString去构造一个全新的字符串而不是用赋值符来创建(重用)一个字符串时,就导致了另一个潜在的性能问题,即:重复创建大量相同的字符串。说到这里,您也许会想到使用缓存池的技术来解决这一问题,大概有如下两种方法:

方法一,使用 String 的intern()方法返回 JVM 对字符串缓存池里相应已存在的字符串引用,从而解决内存性能问题,但这个方法并不推荐!原因在于:首先,intern() 所使用的池会是 JVM中一个全局的池,很多情况下我们的程序并不需要如此大作用域的缓存;其次,intern() 所使用的是 JVMheap 中 PermGen 相应的区域,在 JVM 中PermGen 是用来存放装载类和创建类实例时用到的元数据。程序运行时所使用的内存绝大部分存放在 JVM heap 的其他区域,过多得使用 intern()将导致 PermGen 过度增长而最后返回OutOfMemoryError,因为垃圾收集器不会对被缓存的 String 做垃圾回收。所以我们建议使用第二种方式。

方 法二,用户自己构建缓存,这种方式的优点是更加灵活。创建 HashMap,将需缓存的 String 作为 key和 value 存放入 HashMap。假设我们准备创建的字符串为 key,将 Map cacheMap 作为缓冲池,那么返回 key 的代码如下:

 private String getCacheWord(String key) {

     String tmp = cacheMap.get(key);

     if(tmp != null) {

            return tmp;

     } else {

             cacheMap.put(key, key);

             return key;

     }

 }

 

 

0 0