您現在的位置是:首頁 > 人文

透過高效的物件編組改進應用程式的序列化

由 軟體科技與技術狠活 發表于 人文2023-01-16
簡介}}然後將房主的姓名列印為一個數字,因為它已被儲存為長:House{owner=670118}列印 YAML 示例然後我們可以擴充套件這個類以使用 Chronicle 的基類之一 SelfDescribeingMarshallable,它允

為什麼要引用十六進位制

每日分享最新,最流行的軟體開發知識與最新行業趨勢,希望大家能夠一鍵三連,多多支援,跪求關注,點贊,留言。

演示如何使用物件封送示例將小字串編碼為長原語,以及如何提高應用的序列化效能。

高效的程式碼不僅執行得更快;如果它使用較少的計算資源,則執行起來可能更便宜。特別是,分散式雲應用程式可以受益於快速、輕量級的序列化。

開源 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:56\n” +

“。。。\n” +

“event: {\n” +

“ start: 2022-06-17T12:35:56\n” +

“}\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 版本甚至更快,尤其是在較高的百分位數上。

基準效能

透過高效的物件編組改進應用程式的序列化

圖 2。 TriviallyCopyable 和 BinaryWire 之間的基準效能

*序列化和反序列化訊息的微秒

結論

總體而言,LongConversion 是有益的,因為比較原始 long 比比較字串更有效。即使我們考慮到 String 最初可以使用它們的hashcode()。 此外,原始 long 直接儲存在 Object 中(此示例使用“House”物件),因此在訪問它們時,您不必經歷訪問物件(例如字串)時所獲得的間接級別。其參考。

將資料儲存到原語TriviallyCopyable中,可以透過簡單地將 java 物件的記憶體複製為序列化位元組來序列化物件。上圖顯示該技術改善了序列化和反序列化延遲。

推薦文章