Truffle 字串指南

Truffle 字串是 Truffle 的原始字串類型,可在語言之間共用。為了更容易互通和獲得更好的效能,建議語言實作者使用 Truffle 字串作為其語言的字串類型。

TruffleString 支援多種字串編碼,但特別針對最常用的編碼進行了最佳化

  • UTF-8
  • UTF-16
  • UTF-32
  • US-ASCII
  • ISO-8859-1
  • 位元組

TruffleString API #

TruffleString 公開的所有操作都以內部 Node 的形式提供,並且作為靜態方法或實例方法提供。使用者應盡可能使用提供的節點,因為靜態/實例方法只是執行各自節點的未快取版本的簡寫。所有節點都命名為 {NameOfOperation}Node,所有便捷方法都命名為 {nameOfOperation}Uncached

某些操作支援延遲求值,例如延遲串聯或某些字串屬性的延遲求值。這些操作大多提供一個參數 boolean lazy,允許使用者在每次呼叫的基礎上啟用或停用延遲求值。

處理索引值的操作(例如 CodePointAtIndex)有兩種變體:基於程式碼點的索引和基於位元組的索引。基於位元組的索引以操作名稱中的 ByteIndex 後綴或前綴表示,否則索引基於程式碼點。例如,CodePointAtIndex 的索引參數是基於程式碼點的,而 CodePointAtByteIndex 使用基於位元組的索引。

以下列出目前可用的操作,並按類別分組。

建立新的 TruffleString

查詢字串屬性

  • isEmpty:檢查字串是否為空。
  • CodePointLength:取得字串的程式碼點長度。
  • byteLength:取得字串的位元組長度。
  • IsValid:檢查字串的編碼是否正確。
  • GetCodeRange:取得字串內容的粗略資訊 (此字串中的所有程式碼點是否都來自 ASCII/LATIN-1/BMP 範圍?)。
  • GetByteCodeRange:取得字串內容的粗略資訊,而不考慮基於 16/32 位元的編碼。
  • CodeRangeEquals:檢查字串的程式碼範圍是否等於給定的程式碼範圍。
  • isCompatibleTo:檢查字串是否與給定的編碼相容/是否可以以給定的編碼檢視。
  • isManaged:檢查字串是否未由原生指標支援。
  • isNative:檢查字串是否由原生指標支援。
  • isImmutable:檢查字串是否為 TruffleString 的實例。
  • isMutable:檢查字串是否為 MutableTruffleString 的實例。

比較

  • Equal:檢查兩個字串是否相等。請注意,此操作對編碼敏感!
  • RegionEqual:檢查兩個字串在由基於程式碼點的偏移量和長度定義的給定區域中是否相等。
  • RegionEqualByteIndex:檢查兩個字串在由基於位元組的偏移量和長度定義的給定區域中是否相等。
  • CompareBytes:逐位元組比較兩個字串。
  • CompareCharsUTF16:逐字元比較兩個 UTF-16 字串。
  • CompareIntsUTF32:逐 int 比較兩個 UTF-32 字串。
  • HashCode:取得字串的雜湊碼。雜湊碼基於字串的位元組,因此具有相同程式碼點但編碼不同的字串可能具有不同的雜湊碼。

轉換

存取程式碼點和位元組

搜尋

  • ByteIndexOfAnyByte: 尋找字串中任何一組給定位元組的首次出現位置,並傳回其基於位元組的索引。
  • CharIndexOfAnyCharUTF16: 尋找 UTF-16 字串中任何一組給定字元的首次出現位置,並傳回其基於字元的索引。
  • IntIndexOfAnyIntUTF32: 尋找 UTF-32 字串中任何一組給定整數的首次出現位置,並傳回其基於整數的索引。
  • IndexOfCodePoint: 尋找字串中給定碼位的首次出現位置,並傳回其基於碼位的索引。
  • ByteIndexOfCodePoint: 尋找字串中給定碼位的首次出現位置,並傳回其基於位元組的索引。
  • ByteIndexOfCodePointSet: 尋找字串中包含在給定集合中的碼位的首次出現位置,並傳回其基於位元組的索引。
  • LastIndexOfCodePoint: 尋找字串中給定碼位的最後一次出現位置,並傳回其基於碼位的索引。
  • LastByteIndexOfCodePoint: 尋找字串中給定碼位的最後一次出現位置,並傳回其基於位元組的索引。
  • IndexOfString: 尋找字串中給定子字串的首次出現位置,並傳回其基於碼位的索引。
  • ByteIndexOfString: 尋找字串中給定子字串的首次出現位置,並傳回其基於位元組的索引。
  • LastIndexOfString: 尋找字串中給定子字串的最後一次出現位置,並傳回其基於碼位的索引。
  • LastByteIndexOfString: 尋找字串中給定子字串的最後一次出現位置,並傳回其基於位元組的索引。

合併

  • Concat: 連接兩個字串。
  • Substring: 從給定字串建立子字串,以基於碼位的偏移量和長度為界。
  • SubstringByteIndex: 從給定字串建立子字串,以基於位元組的偏移量和長度為界。
  • Repeat: 將給定字串重複 n 次。

實例化 #

TruffleString 可以從碼位、數字、基本陣列或 java.lang.String 建立。

可以使用 TruffleString.FromByteArrayNode 建立任何編碼的字串,該節點預期一個包含已編碼字串的位元組陣列。透過將 copy 參數設定為 false,此操作可以是非複製的。

重要事項:TruffleString 會假設陣列內容是不可變的,在將其傳遞給此操作的非複製變體後,請勿修改陣列。

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            @Cached TruffleString.FromByteArrayNode fromByteArrayNode) {
        byte[] array = {'a', 'b', 'c'};
        return fromByteArrayNode.execute(array, 0, array.length, TruffleString.Encoding.UTF_8, false);
    }
}

為了更輕鬆地建立獨立於系統位元組順序的 UTF-16 和 UTF-32 字串,TruffleString 提供了 TruffleString.FromCharArrayUTF16NodeTruffleString.FromIntArrayUTF32Node

TruffleString 也可以透過 TruffleStringBuilder 建立,它是 TruffleString 對應於 java.lang.StringBuilder 的版本。

TruffleStringBuilder 提供以下操作

請參閱以下範例

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            @Cached TruffleStringBuilder.AppendCharUTF16Node appendCharNode,
            @Cached TruffleStringBuilder.AppendJavaStringUTF16Node appendJavaStringNode,
            @Cached TruffleStringBuilder.AppendIntNumberNode appendIntNumberNode,
            @Cached TruffleStringBuilder.AppendStringNode appendStringNode,
            @Cached TruffleString.FromCharArrayUTF16Node fromCharArrayUTF16Node,
            @Cached TruffleStringBuilder.AppendCodePointNode appendCodePointNode,
            @Cached TruffleStringBuilder.ToStringNode toStringNode) {
        TruffleStringBuilder sb = TruffleStringBuilder.create(TruffleString.Encoding.UTF_16);
        sb = appendCharNode.execute(sb, 'a');
        sb = appendJavaStringNode.execute(sb, "abc", /* fromIndex: */ 1, /* length: */ 2);
        sb = appendIntNumberNode.execute(sb, 123);
        TruffleString string = fromCharArrayUTF16Node.execute(new char[]{'x', 'y'}, /* fromIndex: */ 0, /* length: */ 2);
        sb = appendStringNode.execute(sb, string);
        sb = appendCodePointNode.execute(sb, 'z');
        return toStringNode.execute(sb); // string content: "abc123xyz"
    }
}

編碼 #

每個 TruffleString 都以特定的內部編碼進行編碼,該編碼在實例化期間設定。

TruffleString 針對以下編碼進行了完全最佳化

  • UTF-8
  • UTF-16
  • UTF-32
  • US-ASCII
  • ISO-8859-1
  • 位元組

支援許多其他編碼,但未完全最佳化。若要使用它們,必須在 Truffle 語言註冊中設定 needsAllEncodings = true 來啟用它們。

TruffleString 的內部編碼不會公開。語言不應查詢字串的編碼,而應將 expectedEncoding 參數傳遞給字串編碼很重要的所有方法(幾乎所有操作)。如果字串在兩種編碼中是位元組等效的,這允許在編碼之間轉換時重複使用字串物件。可以使用 SwitchEncodingNode 將字串轉換為不同的編碼,如下列範例所示

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;

abstract static class SomeNode extends Node {

    @Specialization
    static void someSpecialization(
            @Cached TruffleString.FromJavaStringNode fromJavaStringNode,
            @Cached TruffleString.ReadByteNode readByteNode,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNode,
            @Cached TruffleString.ReadByteNode utf8ReadByteNode) {

        // instantiate a new UTF-16 string
        TruffleString utf16String = fromJavaStringNode.execute("foo", TruffleString.Encoding.UTF_16);

        // read a byte with expectedEncoding = UTF-16.
        // if the string is not byte-compatible with UTF-16, this method will throw an IllegalArgumentException
        System.out.printf("%x%n", readByteNode.execute(utf16String, /* byteIndex */ 0, TruffleString.Encoding.UTF_16));

        // convert to UTF-8.
        // note that utf8String may be reference-equal to utf16String!
        TruffleString utf8String = switchEncodingNode.execute(utf16String, TruffleString.Encoding.UTF_8);

        // read a byte with expectedEncoding = UTF-8
        // if the string is not byte-compatible with UTF-8, this method will throw an IllegalArgumentException
        System.out.printf("%x%n", utf8ReadByteNode.execute(utf8String, /* byteIndex */ 0, TruffleString.Encoding.UTF_8));
    }
}

編碼之間的位元組等效性是透過 UTF-16 和 UTF-32 上的字串壓縮來確定的,因此,例如,壓縮的 UTF-16 字串在位元組上等效於 ISO-8859-1,並且如果其所有字元都在 ASCII 範圍內(請參閱 CodeRange),則在位元組上也等效於 UTF-8。

若要檢查您的程式碼是否正確切換編碼,請使用系統屬性 truffle.strings.debug-strict-encoding-checks=true 執行您的單元測試。這會在切換編碼時停用重複使用字串物件,並使編碼檢查更加嚴格:所有在單一字串上運作的操作都會強制執行完全比對,而在兩個字串上運作的操作仍允許位元組等效的重新解譯。

所有具有多個字串參數的 TruffleString 操作都要求字串的編碼與結果編碼相容。因此,字串需要具有相同的編碼,或者呼叫端必須確保兩個字串都與結果編碼相容。這使已經知道 SwitchEncodingNodes 不會執行任何操作的呼叫端可以直接跳過它們,以減少資源佔用。

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;

abstract static class SomeNode extends Node {

    @Specialization
    static boolean someSpecialization(
            TruffleString a,
            TruffleString b,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNodeA,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNodeB,
            @Cached TruffleString.EqualNode equalNode) {
        TruffleString utf8A = switchEncodingNodeA.execute(a, TruffleString.Encoding.UTF_8);
        TruffleString utf8B = switchEncodingNodeB.execute(b, TruffleString.Encoding.UTF_8);
        return equalNode.execute(utf8A, utf8B, TruffleString.Encoding.UTF_8);
    }
}

字串屬性 #

TruffleString 公開以下屬性

  • byteLength:字串的位元組長度,透過 byteLength 方法公開。
  • codePointLength:字串的碼位長度,透過 CodePointLengthNode 公開。
  • isValid:可以使用 IsValidNode 查詢,以檢查字串是否已正確編碼。
  • codeRange:提供有關字串內容的粗略資訊,透過 GetCodeRangeNode 公開。此屬性可以具有以下值
    • ASCII:此字串中的所有碼位都是基本拉丁 Unicode 區塊(也稱為 ASCII)的一部分 (0x00 - 0x7f)。
    • LATIN-1:此字串中的所有碼位都是 ISO-8859-1 字元集 (0x00 - 0xff) 的一部分,該字元集等效於基本拉丁和拉丁-1 補充 Unicode 區塊的聯集。字串中至少有一個碼位大於 0x7f。僅適用於 ISO-8859-1、UTF-16 和 UTF-32。
    • BMP:此字串中的所有碼位都是 Unicode 基本多文種平面 (BMP) (0x0000 - 0xffff) 的一部分。字串中至少有一個碼位大於 0xff。僅適用於 UTF-16 和 UTF-32。
    • VALID:此字串已正確編碼,且包含至少一個超出其他適用碼位範圍的碼位(例如,對於 UTF-8,這表示有一個碼位超出 ASCII 範圍,對於 UTF-16,這表示有一個碼位超出 BMP 範圍)。
    • BROKEN:此字串未正確編碼。無法確定有關其內容的任何其他資訊。
  • hashCode:字串的雜湊碼,透過 HashCodeNode 公開。雜湊碼取決於字串的編碼;在比較雜湊碼之前,必須始終將字串轉換為通用編碼!

請參閱以下範例,了解如何查詢 TruffleString 公開的所有屬性

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            TruffleString string,
            @Cached TruffleString.CodePointLengthNode codePointLengthNode,
            @Cached TruffleString.IsValidNode isValidNode,
            @Cached TruffleString.GetCodeRangeNode getCodeRangeNode,
            @Cached TruffleString.HashCodeNode hashCodeNode) {
        System.out.println("byte length: " + string.byteLength(TruffleString.Encoding.UTF_8));
        System.out.println("codepoint length: " + codePointLengthNode.execute(string, TruffleString.Encoding.UTF_8));
        System.out.println("is valid: " + isValidNode.execute(string));
        System.out.println("code range: " + getCodeRangeNode.execute(string));
        System.out.println("hash code: " + hashCodeNode.execute(string, TruffleString.Encoding.UTF_8));
    }
}

字串相等性和比較 #

應使用 EqualNode 檢查 TruffleString 物件是否相等。就像 HashCodeNode 一樣,相等性比較對字串的編碼很敏感,因此在任何比較之前,應始終將字串轉換為通用編碼。Object#equals(Object) 的行為類似於 EqualNode,但由於此方法沒有 expectedEncoding 參數,它將自動確定字串的通用編碼。如果字串的編碼不相等,TruffleString 將檢查一個字串的二進位是否與另一個字串的編碼相容,如果是,則比對它們的內容。否則,將認為字串不相等,不會套用自動轉換。

請注意,由於 TruffleStringhashCodeequals 方法對字串編碼很敏感,因此在例如將它們用作 HashMap 中的鍵之前,必須始終將 TruffleString 物件轉換為通用編碼。

TruffleString 還提供三個比較節點 CompareBytesNodeCompareCharsUTF16NodeCompareIntsUTF32Node,分別逐位元組、逐字元和逐整數比較字串。

串連 #

串連是透過 ConcatNode 完成的。此操作要求兩個字串都使用 expectedEncoding,這也是結果字串的編碼。透過 lazy 參數支援延遲串連。當以延遲方式串連兩個字串時,會延遲新字串內部陣列的配置和初始化,直到另一個操作需要直接存取該陣列。可以使用 MaterializeNode 明確觸發此類「延遲串連字串」的實體化。在迴圈中存取字串之前執行此操作很有用,例如在以下範例中

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            TruffleString utf8StringA,
            TruffleString utf8StringB,
            @Cached TruffleString.ConcatNode concatNode,
            @Cached TruffleString.MaterializeNode materializeNode,
            @Cached TruffleString.ReadByteNode readByteNode) {
        // lazy concatenation
        TruffleString lazyConcatenated = concatNode.execute(utf8StringA, utf8StringB, TruffleString.Encoding.UTF_8, /* lazy */ true);

        // explicit materialization
        TruffleString materialized = materializeNode.execute(lazyConcatenated, TruffleString.Encoding.UTF_8);

        int byteLength = materialized.byteLength(TruffleString.Encoding.UTF_8);
        for (int i = 0; i < byteLength; i++) {
            // string is guaranteed to be materialized here, so no slow materialization code can end up in this loop
            System.out.printf("%x%n", readByteNode.execute(materialized, i, TruffleString.Encoding.UTF_8));
        }
    }
}

子字串 #

可以使用 SubstringNodeSubstringByteIndexNode 來建立子字串,它們分別使用基於碼位和基於位元組的索引。子字串也可以是 lazy 的,這表示不會為結果字串建立新的陣列,而是重複使用父字串的陣列,並且僅使用傳遞給子字串節點的偏移量和長度來存取。目前,lazy 子字串的內部陣列永遠不會被修剪(即,被字串精確長度的新陣列取代)。請注意,這種行為實際上會在建立 lazy 子字串時產生記憶體洩漏。一個極端的例子是,假設有一個 100 MB 大小的字串,從這個字串建立的任何 lazy 子字串都會保持這個 100 MB 的陣列處於活動狀態,即使原始字串被垃圾回收器釋放。使用 lazy 子字串時請務必謹慎。

java.lang.String 的互通性 #

TruffleString 提供了 FromJavaStringNode 用於將 java.lang.String 轉換為 TruffleString。若要從 TruffleString 轉換為 java.lang.String,請使用 ToJavaStringNode。如有必要,此節點會在內部將字串轉換為 UTF-16,並從該表示法建立一個 java.lang.String

Object#toString() 是使用未快取的 ToJavaStringNode 版本實作的,應避免在快速路徑上使用。

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static void someSpecialization(
            @Cached TruffleString.FromJavaStringNode fromJavaStringNode,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNode,
            @Cached TruffleString.ToJavaStringNode toJavaStringNode,
            @Cached TruffleString.ReadByteNode readByteNode) {
        TruffleString utf16String = fromJavaStringNode.execute("foo", TruffleString.Encoding.UTF_16);
        TruffleString utf8String = switchEncodingNode.execute(utf16String, TruffleString.Encoding.UTF_8);
        System.out.println(toJavaStringNode.execute(utf8String));
    }
}

TruffleString 也公開了 #toStringDebug() 以用於除錯目的。請勿將此方法用於除錯以外的任何用途,因為其傳回值未指定,且可能隨時變更。

java.lang.String 的差異 #

java.lang.String 切換到 TruffleString 時,應考慮以下項目

  • TruffleString 實例的靜態額外負荷大於 java.lang.String 物件的額外負荷。一個 TruffleString 物件包含 2 個指標欄位、4 個 int 欄位和 4 個 byte 欄位,這通常會導致總物件大小為 40 個位元組(物件標頭為 12 個位元組,每個指標 4 個位元組(壓縮 oops),8 位元組的記憶體對齊)。一個 java.lang.String 物件包含一個指標欄位、一個 int 欄位和一個 byte 欄位,在相同條件下,總物件大小為 24 個位元組。這種記憶體佔用空間的差異可能會對產生大量小字串的某些情況產生負面影響。
  • TruffleStringjava.lang.String 一樣會進行字串壓縮。
  • 如果您的語言需要將字串轉換為其他編碼,例如 UTF-8(這在 Web 應用程式中非常常見),如果字串不包含特殊字元,TruffleString 可以將此操作變成空操作。例如,僅限 ASCII 的字串可以重新解釋為幾乎任何編碼,並且將僅限 ASCII 的 UTF-16 字串轉換為 UTF-8 是空操作。在不可避免地需要轉碼字串的情況下,TruffleString 會將轉碼後的字串快取在原始字串中,因此每個字串和編碼只會執行一次轉碼。
  • 為了使用第三方程式庫,必須將 TruffleString 物件轉換為 java.lang.String,然後再轉換回來。為了盡可能降低成本,當從 java.lang.String 轉換為 TruffleString 時,TruffleString 會重複使用 Java String 的內部位元組陣列,並且會將從 TruffleString 物件建立的 Java String 快取在物件本身中。
  • TruffleString 提供了 java.lang.String 中沒有的其他功能
    • Lazy 連接和字串檢視,可以顯著減少您的語言可能必須執行的陣列複製操作數量。
    • 進入原生記憶體的 String 檢視,完全避免了在使用原生記憶體之前將其複製到 Java 陣列的需求。
    • 透過 codeRange 屬性進行的 String 內容分類,允許對僅限 ASCII 等字串進行特殊化。這可以顯著降低某些字串操作的複雜性。
  • 所有 TruffleString 操作的效能應與其 java.lang.String 對應項相同或更好。

碼位迭代器 #

TruffleString 提供了 TruffleStringIterator 作為迭代字串碼位的一種方式。此方法應優先於在迴圈中使用 CodePointAtIndexNode,尤其是在可變寬度編碼(如 UTF-8)上,因為 CodePointAtIndexNode 可能必須在每次呼叫時重新計算給定碼位索引的等效位元組索引。

請參閱範例

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringIterator;

abstract static class SomeNode extends Node {

    @Specialization
    static void someSpecialization(
            TruffleString string,
            @Cached TruffleString.CreateCodePointIteratorNode createCodePointIteratorNode,
            @Cached TruffleStringIterator.NextNode nextNode,
            @Cached TruffleString.CodePointLengthNode codePointLengthNode,
            @Cached TruffleString.CodePointAtIndexNode codePointAtIndexNode) {

        // iterating over a string's codepoints using TruffleStringIterator
        TruffleStringIterator iterator = createCodePointIteratorNode.execute(string, TruffleString.Encoding.UTF_8);
        while (iterator.hasNext()) {
            System.out.printf("%x%n", nextNode.execute(iterator));
        }

        // suboptimal variant: using CodePointAtIndexNode in a loop
        int codePointLength = codePointLengthNode.execute(string, TruffleString.Encoding.UTF_8);
        for (int i = 0; i < codePointLength; i++) {
            // performance problem: codePointAtIndexNode may have to calculate the byte index corresponding
            // to codepoint index i for every loop iteration
            System.out.printf("%x%n", codePointAtIndexNode.execute(string, i, TruffleString.Encoding.UTF_8));
        }
    }
}

可變字串 #

TruffleString 也提供了一個名為 MutableTruffleString 的可變字串變體,該變體也被 TruffleString 的所有節點接受。MutableTruffleString非執行緒安全的,並允許透過 WriteByteNode 覆寫其內部位元組陣列或原生指標中的位元組。內部陣列或原生指標的內容也可以在外部修改,但必須透過 notifyExternalMutation() 通知相應的 MutableTruffleStringMutableTruffleString 不是 Truffle 互通類型,必須在傳遞語言邊界之前透過 TruffleString.AsTruffleString 轉換為不可變的 TruffleString

與我們聯繫