每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。
演示如何使用对象封送示例将小字符串编码为长原语,以及如何提高应用的序列化性能。
高效的代码不仅运行得更快;如果它使用较少的计算资源,则运行起来可能更便宜。特别是,分布式云应用程序可以受益于快速、轻量级的序列化。
开源 Java 序列化器
Chronicle-Wire是一个开源 Java 序列化程序,可以读取和写入不同的消息格式,例如 JSON、YAML 和原始二进制数据。此序列化程序可以在压缩数据格式化(在同一空间中存储更多数据)与压缩数据(减少所需存储量)之间找到一个中间地带。相反,数据存储在尽可能少的字节中,而不会导致性能下降。这是通过编组一个对象来完成的。
什么是对象编组?为什么使用它?
编组是序列化的另一个名称。换句话说,它是将对象的内存表示转换为另一种格式的过程。使用wire,我们可以编写与书面格式无关的编组代码,因此可以使用相同的编组代码来生成/读取 YAML、JSON 或二进制表示。因为我们可以生成人类可读的表示,我们可以简单地实现一个toString()方法只需写入一个可读的连线实例(以及等于和哈希码,假设序列化形式等同于对象的标识)。此外,对于可读的有线实例,我们可以将数值写入字符串表示(例如,时间戳长转换器)或使用长转换以数字形式存储短文本值,以便紧凑写入二进制表示。这允许您选择最适合应用程序的格式。例如,在读取手工制作的配置文件时,我们可以使用 YAML。在通过电线发送到另一台机器或将其存储在机器可读文件中时,我们可以使用二进制文件。或者,我们也可以在它们之间进行转换,例如调试通过线路传输的二进制消息,我们可以从二进制格式读取并使用 YAML 格式记录。这都可以使用相同的代码执行。
LongConverter 示例
本示例介绍了一个简单的普通旧 Java 对象 (POJO) 示例。
public class LongConversionExampleA {
public static class House {
long owner;
public void owner(CharSequence owner) {
this.owner =
Base64LongConverter.INSTANCE.parse(owner);
}
@Override
public String toString() {
return “House{” +
“owner=” + owner +
‘}’;
}
}
public static void main(String[] args) {
House house = new House();
house.owner(“Bill”);
System.out.println(house);
}
}
我们通过将 String 对象存储为 long 来开始该过程。此处使用ABase64LongConverter来解析提供的 CharSequence 并将结果作为 long 返回。示例代码可以在LongConversionExampleA中看到。
public class LongConversionExampleA {
public static class House {
long owner;
public void owner(CharSequence owner) {
this.owner =
Base64LongConverter.INSTANCE.parse(owner);
}
@Override
public String toString() {
return “House{” +
“owner=” + owner +
‘}’;
}
}
public static void main(String[] args) {
House house = new House();
house.owner(“Bill”);
System.out.println(house);
}
}
然后将房主的姓名打印为一个数字,因为它已被存储为长:
House{owner=670118}
打印 YAML 示例
然后我们可以扩展这个类以使用 Chronicle 的基类之一
SelfDescribeingMarshallable,它允许我们简单地实现一个toString()方法,并且可以重建对象。这对于从文件构建单元测试中的示例数据很有用。这也意味着您可以在日志文件中转储对象并重建原始对象。下面的代码中演示的是 .addAlias;这允许引用 House 而不是 to
net.openhft.chronicle.LongConversionExampleB$House。
LongConversionExampleB说明了如何将输出打印为 YAML:
public class LongConversionExampleB {
static {
ClassAliasPool.CLASS_ALIASES.addAlias(
LongConversionExampleB.House.class);
}
public static class House extends
SelfDescribingMarshallable {
@LongConversion(Base64LongConverter.class)
long owner;
public void owner(CharSequence owner) {
this.owner =
Base64LongConverter.INSTANCE.parse(owner);
}
}
public static void main(String[] args) {
House house = new House();
house.owner(“Bill”);
System.out.println(house);
}
}
运行它时,不是打印数字,而是打印以下内容:
!House {
Owner: Bill
}
打印 JSON 示例
如果我们希望输出为 JSON,我们可以从以下行中删除LongConversionExampleB:
System.out.println(house);
并将其替换为 Wire,因为这是一种更轻量级的替代方案:
Wire wire = WireType.JSON.apply(
Bytes.allocateElasticOnHeap());
wire.getValueOut().object(house);
System.out.println(wire);
这将输出以下内容:
{“owner”: “Bill”}
为什么这有帮助?为什么我们不能将它最初存储为字符串而不是长字符串?
将其存储为 long 是存储此数据的更有效方式。虽然通常有 8 字节到长,但通过使用@LongConversion(Base64LongConverter.class),我们可以将 10 个 Base64 编码字符存储到 8 字节长中。
这怎么可能?
通常,当我们谈论一个字节时,一个字节可以表示 256 个不同字符中的一个。
然而,由于我们使用了 Base64LongConverter,我们无法表示 256 个字符之一,而是说 8 位字节只能表示 64 个字符之一:
.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+
通过限制一个字节可以表示的字符数,可以将更多的字符压缩成一个长字符。
如果这 64 个字符不包括您需要的字符怎么办?或者如果仍然太多怎么办?
Chronicle Wire 有不同的版本LongConverter,从 aBase64LongConverter到 Base32LongConverter。此外,还可以自定义您自己的基本编码。毕竟,更少的字符会带来更紧凑的数据存储方式,这反过来又意味着数据的读写速度更快,谁不想这样呢?
字段组示例
虽然上面的示例可以很好地存储少量字符,但如果存储一些更长的字符,例如家庭地址呢?
这是我们可以利用Chronicle Bytes@FieldGroup的地方:
import
net.openhft.chronicle.bytes.Bytes;
import
net.openhft.chronicle.bytes.FieldGroup;
在下面的LongConversionExampleC中,我们将介绍如何将多个 long 存储到FieldGroup. 在此示例中,@FieldGroup最多可以存储 5 个长字符,因此最多可以存储 40 个字符。
在“如何在 Java 序列化中获得 C++ 速度”一文中可以看到以原始 long 形式存储它的好处。
public static class House extends
SelfDescribingMarshallable {
@FieldGroup(“address”)
// 5 longs, each at 8 bytes = 40 bytes, so we can store a String with up to 39 ISO-8859 characters (as the first byte contains the length)
private long text4a, text4b, text4c, text4d, text4e;
private transient Bytes address = Bytes.forFieldGroup(this, “address”);
public void address(CharSequence owner) {
address.append(owner);
}
}
下面的示例继续说明如何创建一个byte[]来存储字节,将房屋对象写入其中,然后读取它们。
public static void main(String[] args) {
House house = new House();
house.address(“82 St John Street, Clerkenwell, London”);
// creates a buffer to store bytes
final Bytes<?> t = allocateElasticOnHeap();
// the encoding format
final Wire wire = BINARY.apply(t);
// writes the house object to the bytes
wire.getValueOut().object(house);
// dumps out the contents of the bytes
System.out.println(t.toHexString());
System.out.println(t);
// reads the house object from the bytes
final House object = wire.getValueIn().object(House.class);
// prints the value of text4
System.out.println(object.address);
}
当我们使用toHexString( )时,这个例子打印出我们的数据,如图 1 所示。这是生成十六进制转储的标准方法。绿色部分代表“偏移量”。从字符串开头到当前位置的字节数。红色部分突出显示存储数据的“十六进制值”。为了阅读这个,我们可以取十六进制数 48(在顶行),首先将其转换为十进制 – HEX 48,因为十进制是 72。然后我们取这个十进制 72 并使用ASCII 字符图表,它告诉我们这是字符“H”。如果我们在蓝色部分看到“ASCI IOS-8859”,我们会看到它对应于“H”中的第三个字符。
十六进制字符串输出
图 1. toHexString() 输出
@Base64
如上面的示例所示,我们使用了:
@LongConversion(Base64LongConverter.class)
应该注意的是,这可以简化为:
@Base64
在下面的代码块中可以看到一个正在实现的示例:
package
net.openhft.chronicle.wire;
import
net.openhft.chronicle.bytes.Bytes;
import
net.openhft.chronicle.wire.converter.Base64;
public class Example {
public static class Base64LongConverterValue extends
SelfDescribingMarshallable {
@LongConversion(Base64LongConverter.class)
long value;
public Base64LongConverter value(String msg) {
value =
Base64LongConverter.INSTANCE.parse(msg);
return this;
}
}
public static class Base64Value extends
SelfDescribingMarshallable {
@Base64
long value;
public Base64Value value(String msg) {
value = Base64.INSTANCE.parse(msg);
return this;
}
}
public static void main(String[] args) {
new Example().start();
}
private static void start() {
Bytes b =
Bytes.allocateEleasticOnHeap();
Wire w = WireType.JSON.apply(b);
w.getValueOut().object(new Base64Value().value(“hello”));
System.out.println(w.toString());
}
}
创建自己的注释
Base64创建包含您自己选择的 64 个字符的注释很容易。下面是我们如何创建@Base64,它利用 SymbolsLongConverter.
package
net.openhft.chronicle.wire.converter;
import
net.openhft.chronicle.wire.*;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@LongConversion(Base64.class)
public @interface Base64 {
LongConverter INSTANCE = new SymbolsLongConverter(
“
.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_”);
}
添加时间戳
下面的示例演示了如何在每次创建事件时创建时间戳。
public class NanoTimeTest {
@Test
public void yaml() {
Wire wire = Wire.newYamlWireOnHeap();
UseNanoTime writer = wire.methodWriter(UseNanoTime.class);
long ts = NanoTime.INSTANCE.parse(“2022-06-17T12:35:56”);
writer.time(ts);
writer.event(new Event(ts));
assertEquals(“” +
“time: 2022-06-17T12:35:56n” +
“…n” +
“event: {n” +
” start: 2022-06-17T12:35:56n” +
“}n” +
“…n”, wire.toString());
}
interface UseNanoTime {
void time(@NanoTime long time);
void event(Event event);
}
static class Event extends
SelfDescribingMarshallable {
@NanoTime
private long start;
Event(long start) {
this.start = start;
}
}
}
JLBH 基准表现
为了探索这些示例的效率,创建了这个
TriviallyCopyableJLBH.java测试。从第 23-26 行可以看出,我们可以在运行TriviallyCopyableHouse(“House1”)或 House(“House2”)之间切换BinaryWire。需要注意的重要一点是,使用普通可复制对象来提高 java 序列化速度。这表明我们可以每秒序列化和反序列化 100,000 条消息。Trivially Copyable 版本甚至更快,尤其是在较高的百分位数上。
基准性能
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!