String对我们来说太熟悉了,因为它无处不在,更因为用String可以描述这个世界几乎所有的东西,甚至于为了描述精确的数值都需要String出马(因为计算机眼中的二进制和人类眼中的十进制间总有那么点隔膜)。因为熟悉而变得简单,也容易被忽略。今天记录一下关于String的容易被忽略的两个问题。
- 字符串重用——节省内存
因为字符串太多,如果能够重用则能够节省很大的内存。首先看下面一个例子:
String string1 = “HELLOHELLO”;
String string2 = “HELLO” + “HELLO”;
上面创建了几个字符串?1 or 2?后者是动态创建的,不过相信JVM可以对其直接优化的,因为编译时已经知道内容了,猜测是一个instance,即同一个char数组。Heapdump出来后观察果然是一个。
String string3 = args[0]+ args[1];
输入参数HELLO HELLO? 字符串变成几个?没错啊,是两个HELLOHELLO了。Dump heap后观察,果然是两个了。(其实不用dump healp,debug也可以看出来,string1和string3中的char[]指向地址是不一样的)。
依此延伸,可以而知由java反序列化而来的那些string也是不一样的。实例如下;
public final static void main(String[] args) throws Exception {
new StringDeserialized().testDescirialized();
}
public void testDescirialized() throws Exception {
String testString = “HELLOHELLO”;
ObjectOutputStream dataOutStream = new ObjectOutputStream(new FileOutputStream(“./stringdeserialized.data”));
for (int i = 0; i < 1000; i++)
dataOutStream.writeObject(testString);
dataOutStream.close();
List<String> readAgainList = new ArrayList<String>(100);
for (int i = 0; i < 100; i++) {
ObjectInputStream dataInputStream = new ObjectInputStream(new FileInputStream(“./stringdeserialized.data”));
readAgainList.add((String) dataInputStream.readObject());
dataInputStream.close();
}
Thread.sleep(Integer.MAX_VALUE);
}
截图是heap dump出来的,有HELLOHELLO的个数有101个,占用的size>8080。对于JVM的内存使用可参考 http://www.javamex.com/tutorials/memory/object_memory_usage.shtml
问题来了,系统维护的数据大多是字符串信息,比如configserver,而很多的信息都是同一个字符串,那么反复的从网络序列化而来,占用多的Heap。当然自己可以写一个weak hashmap来维护,重用这些字符串。大家知道JVM中有String Pool,使用它无疑最好不过。查找String源码,发现intern()的注释如下:
* When the intern method is invoked, if the pool already contains a
* string equal to this <code>String</code> object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this <code>String</code> object is added to the
* pool and a reference to this <code>String</code> object is returned.
于是改变上面一行代码为:
readAgainList.add(((String) dataInputStream.readObject()).intern());
再次Heap dump分析如下,另外可以看出一个包含10个字符的String占用的Heap是80byte:
- 字符串序列化的速度
目前CS处理为了支持所谓的任意类型数据,CS采用了一个技巧,用Swizzle来保存java序列化后的byte类型,Server端无需反序列化就能保存任意类型的data;这样的坏处有两个:通用的Java序列化效率不高;协议不通用,对其他语言支持不行。因为目前的数据信息基本都是String类型,而对对String数据的专门处理,可以通过String内部的byte数组(UTF-8)类表示,这样也便于其他语言解析。可以考虑增加对publish(String)的支持。于是做了如下测试来比较对String不同serialize/deserialize的速率和大小。
结果是writeUTF最小最快,对于100char的String,差距是数量级的相当明显,虽然Swizzle使用了一个技巧,当对同一个swizzle instance多次传输时,无需重复的序列化。
PS:Swizzle简单的说就是把信息包装起来,然后把序列化的byte流缓存起来,这样如果同样的一个信息要推送/发送N次,就无能减少N-1次的序列化时间。
public class CompareSerialization {
public String generateTestData(int stringLength) {
Random random = new Random();
StringBuilder builder = new StringBuilder(stringLength);
for (int j = 0; j < stringLength; j++) {
builder.append((char) random.nextInt(127));
}
return builder.toString();
}
public int testJavaDefault(String data) throws Exception {
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
outputStream = new ObjectOutputStream(byteArray);
outputStream.writeObject(data);
outputStream.flush();
inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray.toByteArray()));
inputStream.readObject();
return byteArray.size();
}
finally {
outputStream.close();
inputStream.close();
}
}
public int testJavaDefaultBytes(String data) throws Exception {
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
outputStream = new ObjectOutputStream(byteArray);
outputStream.writeBytes(data);
outputStream.flush();
inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray.toByteArray()));
byte[] bytes = new byte[byteArray.size()];
inputStream.read(new byte[byteArray.size()]);
new String(bytes);
return byteArray.size();
}
finally {
outputStream.close();
inputStream.close();
}
}
public int testSwizzle(Swizzle data) throws Exception {
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
outputStream = new ObjectOutputStream(byteArray);
outputStream.writeObject(data);
outputStream.flush();
inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray.toByteArray()));
inputStream.readObject();
return byteArray.size();
}
finally {
outputStream.close();
inputStream.close();
}
}
public int testStringUTF(String data) throws Exception {
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
outputStream = new ObjectOutputStream(byteArray);
outputStream.writeUTF(data);
outputStream.flush();
inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray.toByteArray()));
inputStream.readUTF();
return byteArray.size();
}
finally {
outputStream.close();
inputStream.close();
}
}
public final static void main(String[] args) throws Exception {
CompareSerialization compare = new CompareSerialization();
String data = compare.generateTestData(Integer.parseInt(args[0]));
Swizzle swizzle = new Swizzle(data);
System.out.println(“testJavaDefault size on networking:” + compare.testJavaDefault(data));
System.out.println(“testJavaDefaultBytes size on networking:” + compare.testJavaDefaultBytes(data));
System.out.println(“testStringUTF size on networking:” + compare.testStringUTF(data));
System.out.println(“testSwizzle size on networking:” + compare.testSwizzle(swizzle));
// warm up
for (int i = 0; i < 100; i++) {
compare.testJavaDefault(data);
compare.testJavaDefaultBytes(data);
compare.testStringUTF(data);
compare.testSwizzle(swizzle);
}
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
compare.testJavaDefault(data);
}
long endTime = System.currentTimeMillis();
System.out.println(“testJavaDefault using time:” + (endTime – startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
compare.testJavaDefaultBytes(data);
}
endTime = System.currentTimeMillis();
System.out.println(“testJavaDefaultBytes using time:” + (endTime – startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
compare.testStringUTF(data);
}
endTime = System.currentTimeMillis();
System.out.println(“testStringUTF using time:” + (endTime – startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
compare.testSwizzle(swizzle);
}
endTime = System.currentTimeMillis();
System.out.println(“testSwizzle using time:” + (endTime – startTime));
}
}
相关推荐
在Java编程语言中,String是一个非常重要的类,它表示不可变的字符序列。本文将对Java中String类的常用方法进行详细的总结,帮助开发者更好地理解和使用这些方法。 首先,创建和初始化一个String对象通常有两种方式...
#### 小结 本题目考察了序列化与反序列化的基本概念,并通过具体的编码和解码任务让参与者深入理解这一技术的实际应用。在实现过程中需要注意边界条件和异常处理,确保程序的健壮性和准确性。无论是 Java 还是 ...
这种协议设计考虑了网络通信的性能,包括数据序列化和反序列化,使得跨语言通信变得更加高效。 七、异常处理 Thrift支持自定义异常,可以在IDL文件中定义。服务端抛出的异常会被封装成TException,客户端接收到时...
#### 小结 通过本文的学习,我们不仅深入了解了Java中流的基本概念、分类和使用方法,还掌握了序列化的原理和实现方式。无论是进行数据读写还是对象状态的保存与恢复,掌握流和序列化的相关知识都是Java开发者不可...
### Java笔试题小结 #### 1. Java 字符串(String) **问题**: String 类的特点是什么?与 StringBuffer 的区别? **分析与解答**: `String` 类在 Java 中是不可变的,即一旦创建了一个字符串对象,其内容就不能...
`Boolean` 类实现了 `Serializable` 和 `Comparable<Boolean>` 接口,这意味着 `Boolean` 对象可以被序列化并且可以进行比较。 - **用途**:`Boolean` 类提供了一些静态方法用于转换 `boolean` 和 `String` 类型,...
此外,JAXB(Java Architecture for XML Binding)是另一种强大的工具,它可以将XML与Java对象直接映射,方便数据的序列化和反序列化。对于多级XML,JAXB提供了一种更加面向对象的方式来处理数据,但需要额外的配置...
《Java图书管理小系统》是针对初学者设计的一个结课项目,旨在帮助学习者掌握Java编程基础以及简单的文件操作。这个系统并未与数据库进行交互,而是选择了直接对文件进行读写,简化了系统的复杂性,使得初学者可以...
1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序设计简介 24 2.1.2 程序的三种基本结构 25 2.1.3 面向对象程序设计简介 27 2.1.4 面向对象的基本特征 28 2.2 UML...
### 小结 通过上述章节的学习,我们了解到Java是一种功能强大且广泛应用的编程语言。无论是从语言基础、数据类型、运算符还是编程风格等方面,Java都为程序员提供了一套全面且灵活的工具。掌握这些基础知识对于...
- `ObjectInputStream` 和 `ObjectOutputStream` 支持序列化和反序列化 Java 对象。 - **字符流** - `InputStreamReader` 和 `OutputStreamWriter` 用于读写字符数据,通常与编码解码相关联。 - **随机存取文件*...
8. **输入/输出流**:讲解I/O流的概念,包括文件操作,缓冲流,对象序列化等。 9. **集合框架**:介绍ArrayList、LinkedList、HashSet、HashMap等常用集合类的使用,以及泛型的概念。 10. **多线程**:讲述线程的...
在Java中,序列化是一个将对象转化为字节流的过程,便于存储或在网络间传输。`serialVersionUID`是一个长整型变量,用于版本控制。当类被序列化时,系统会自动计算出该类的`serialVersionUID`,如果在反序列化时,...
3.7.6 循环语句小结78 3.7.7 break语句79 3.7.8 continue语句82 3.8 JavaDebug技术84 3.9 本章练习85 第4章 4.1 一维数组90 4.1.1 为什么要使用数组90 4.1.2 什么是数组91 4.1.3 如何使用数组92 4.1.4 经验之谈-数组...
这种方式是将对象序列化后,然后将序列化后的对象反序列化到一个新的对象中。例如: ```java ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); ...
**小结**: - `int` 适用于数值计算和频繁的操作。 - `Integer` 更适合用于集合处理和可能为空的场景。 #### 四、String与StringBuffer的区别 **String**:表示不可变的字符序列,一旦创建后无法修改。 **...
小结 Hadoop 中的数据类型和序列化机制是非常重要的。所有的数据类型都需要实现 Writable 接口,以便这些类型定义的数据可以被网络传输和文件存储。掌握了 Hadoop 中的数据类型和序列化机制,你将能更好地使用 ...
- **序列化标识符**:为了让自定义异常类支持序列化,需要提供一个`serialVersionUID`字段。 例如: ```java public class BelowZeroException extends Exception { private static final long serialVersionUID ...
7.7序列化261 7.8本章小结264 第4篇Java中的高级技术 第8章Java的多线程机制266 8.1线程的概念266 8.1.1多线程的特点266 8.1.2线程的状态267 8.2Thread类268 8.2.1Thread类的构造方法268 8.2.2Thread类的...