Truffle Strings ガイド

Truffle Stringsは、Truffleのプリミティブな文字列型であり、言語間で共有できます。言語実装者は、相互運用性を容易にし、パフォーマンスを向上させるために、Truffle Stringsを言語の文字列型として使用することをお勧めします。

TruffleStringは、非常に多くの文字列エンコーディングをサポートしていますが、特に最も一般的に使用される以下のエンコーディングに最適化されています。

  • UTF-8
  • UTF-16
  • UTF-32
  • US-ASCII
  • ISO-8859-1
  • BYTES

TruffleString API #

TruffleStringによって公開されるすべての操作は、内部Nodeとして、および静的メソッドまたはインスタンスメソッドとして提供されます。静的/インスタンスメソッドは、それぞれのノードのキャッシュされていないバージョンを実行するための単なるショートハンドであるため、ユーザーは可能な限り提供されているノードを使用する必要があります。すべてのノードは{操作名}Nodeという名前で、すべてのコンビニエンスメソッドは{操作名}Uncachedという名前です。

遅延連結や特定の文字列プロパティの遅延評価など、一部の操作は遅延評価をサポートしています。これらの操作のほとんどは、パラメータboolean lazyを提供しており、ユーザーは呼び出しサイトごとに遅延評価を有効または無効にすることができます。

CodePointAtIndexなど、インデックス値を扱う操作は、コードポイントベースのインデックスとバイトベースのインデックスの2つのバリアントで使用できます。バイトベースのインデックスは、操作名にByteIndexという接尾辞または接頭辞が付いていることで示されます。それ以外の場合は、インデックスはコードポイントに基づいています。たとえば、CodePointAtIndexのインデックスパラメータはコードポイントベースですが、CodePointAtByteIndexはバイトベースのインデックスを使用します。

現在利用可能な操作のリストを以下に示し、カテゴリ別にグループ化します。

新しいTruffleStringの作成

  • FromCodePoint: 指定されたコードポイントから新しいTruffleStringを作成します。
  • FromLong: 指定されたlong値から新しいTruffleStringを作成します。
  • FromByteArray: 指定されたバイト配列から新しいTruffleStringを作成します。
  • FromCharArrayUTF16: 指定されたchar配列からUTF-16 TruffleStringを作成します。
  • FromIntArrayUTF32: 指定されたint配列からUTF-32 TruffleStringを作成します。
  • FromJavaString: 指定されたjava.lang.StringからTruffleStringを作成します。
  • FromNativePointer: 指定されたネイティブポインタから新しいTruffleStringを作成します。
  • Encoding#getEmpty: 指定されたエンコーディングの空のTruffleStringを取得します。

文字列プロパティのクエリ

  • isEmpty: 文字列が空かどうかを確認します。
  • CodePointLength: コードポイント単位の文字列の長さを取得します。
  • byteLength: バイト単位の文字列の長さを取得します。
  • IsValid: 文字列が正しくエンコードされているかどうかを確認します。
  • GetCodeRange: 文字列の内容に関する大まかな情報を取得します(この文字列のすべてのコードポイントがASCII / LATIN-1 / BMP範囲内にあるかなど)。
  • GetByteCodeRange: 16/32ビットベースのエンコーディングを考慮せずに、文字列の内容に関する大まかな情報を取得します。
  • CodeRangeEquals: 文字列のコード範囲が指定されたコード範囲と等しいかどうかを確認します。
  • isCompatibleTo: 文字列が指定されたエンコーディングと互換性があるか、または指定されたエンコーディングで表示できるかどうかを確認します。
  • isManaged: 文字列がネイティブポインタによってバッキングされていないことを確認します。
  • isNative: 文字列がネイティブポインタによってバッキングされていることを確認します。
  • isImmutable: 文字列がTruffleStringのインスタンスであることを確認します。
  • isMutable: 文字列がMutableTruffleStringのインスタンスであることを確認します。

比較

  • Equal: 2つの文字列が等しいかどうかを確認します。この操作はエンコーディングに依存することに注意してください。
  • RegionEqual: コードポイントベースのオフセットと長さで定義された指定された領域で、2つの文字列が等しいかどうかを確認します。
  • RegionEqualByteIndex: バイトベースのオフセットと長さで定義された指定された領域で、2つの文字列が等しいかどうかを確認します。
  • CompareBytes: 2つの文字列をバイト単位で比較します。
  • CompareCharsUTF16: 2つのUTF-16文字列をchar単位で比較します。
  • CompareIntsUTF32: 2つのUTF-32文字列をint単位で比較します。
  • HashCode: 文字列のハッシュコードを取得します。ハッシュコードは文字列のバイトに基づいているため、同じコードポイントを持つがエンコードが異なる文字列は、異なるハッシュコードを持つ場合があります。

変換

  • SwitchEncoding: 文字列を指定されたエンコーディングに変換します。
  • ForceEncoding: 指定された文字列と同じバイトを含み、指定されたエンコーディングに割り当てられた文字列を作成します。
  • AsTruffleString: MutableTruffleStringを不変のTruffleStringに変換します。
  • AsManaged: ネイティブポインタによってバッキングされたTruffleStringを、Javaバイト配列によってバッキングされたTruffleStringに変換します。
  • ToValidString: TruffleStringを正しくエンコードされたバージョンに変換します。
  • CopyToByteArray: 文字列の内容をバイト配列にコピーします。
  • GetInternalByteArray: 文字列の内部バイト配列を取得します。
  • CopyToNativeMemory: 文字列の内容をネイティブポインタにコピーします。
  • GetInternalNativePointer: ネイティブ文字列のポインタオブジェクトを取得します。
  • ToJavaString: 文字列をjava.lang.Stringに変換します。
  • ParseInt: 文字列の内容をint値として解析します。
  • ParseLong: 文字列の内容をlong値として解析します。
  • ParseDouble: 文字列の内容をdouble値として解析します。

コードポイントとバイトへのアクセス

  • Materialize: 文字列のコードポイントまたはバイトを反復処理するループ内に материализация コードを回避するには、このノードを使用します。
  • ReadByte: 文字列から単一のバイトを読み取ります。
  • ReadCharUTF16: UTF-16文字列から単一のcharを読み取ります。
  • CodePointAtIndex: 指定されたコードポイントベースのインデックスにある文字列から単一のコードポイントを読み取ります。
  • CodePointAtByteIndex: 指定されたバイトベースのインデックスにある文字列から単一のコードポイントを読み取ります。
  • CreateCodePointIterator: 文字列のコードポイントの反復処理に適したTruffleStringIteratorオブジェクトを返します。
  • CreateBackwardCodePointIterator: 文字列の末尾から開始して、文字列のコードポイントを反復処理するのに適した TruffleStringIterator オブジェクトを返します。
  • ByteLengthOfCodePoint: 指定されたバイトインデックスから始まるコードポイントが占めるバイト数を返します。
  • CodePointIndexToByteIndex: 指定されたコードポイントインデックスを、指定された文字列のバイトインデックスに変換します。
  • ByteIndexToCodePointIndex: 指定されたバイトインデックスを、指定された文字列のコードポイントインデックスに変換します。

検索

  • ByteIndexOfAnyByte: 文字列内で、指定されたバイトセットのいずれかが最初に出現する位置を検索し、そのバイトベースのインデックスを返します。
  • CharIndexOfAnyCharUTF16: UTF-16文字列内で、指定された文字セットのいずれかが最初に出現する位置を検索し、その文字ベースのインデックスを返します。
  • IntIndexOfAnyIntUTF32: UTF-32文字列内で、指定された整数セットのいずれかが最初に出現する位置を検索し、その整数ベースのインデックスを返します。
  • IndexOfCodePoint: 文字列内で、指定されたコードポイントが最初に出現する位置を検索し、そのコードポイントベースのインデックスを返します。
  • ByteIndexOfCodePoint: 文字列内で、指定されたコードポイントが最初に出現する位置を検索し、そのバイトベースのインデックスを返します。
  • ByteIndexOfCodePointSet: 文字列内で、指定されたセットに含まれるコードポイントが最初に出現する位置を検索し、そのバイトベースのインデックスを返します。
  • LastIndexOfCodePoint: 文字列内で、指定されたコードポイントが最後に出現する位置を検索し、そのコードポイントベースのインデックスを返します。
  • LastByteIndexOfCodePoint: 文字列内で、指定されたコードポイントが最後に出現する位置を検索し、そのバイトベースのインデックスを返します。
  • IndexOfString: 文字列内で、指定された部分文字列が最初に出現する位置を検索し、そのコードポイントベースのインデックスを返します。
  • ByteIndexOfString: 文字列内で、指定された部分文字列が最初に出現する位置を検索し、そのバイトベースのインデックスを返します。
  • LastIndexOfString: 文字列内で、指定された部分文字列が最後に出現する位置を検索し、そのコードポイントベースのインデックスを返します。
  • LastByteIndexOfString: 文字列内で、指定された部分文字列が最後に出現する位置を検索し、そのバイトベースのインデックスを返します。

結合

  • Concat: 2つの文字列を連結します。
  • Substring: コードポイントベースのオフセットと長さで境界付けられた、指定された文字列から部分文字列を作成します。
  • SubstringByteIndex: バイトベースのオフセットと長さで境界付けられた、指定された文字列から部分文字列を作成します。
  • Repeat: 指定された文字列を *n* 回繰り返します。

インスタンス化 #

TruffleString は、コードポイント、数値、プリミティブ配列、または java.lang.String から作成できます。

任意のエンコーディングの文字列は、エンコード済みの文字列を含むバイト配列を受け取る TruffleString.FromByteArrayNode を使用して作成できます。 この操作は、`copy` パラメータを `false` に設定することで、コピーを伴わない操作にすることができます。

重要: TruffleStrings は配列の内容が不変であると想定しているため、コピーを伴わない操作に渡した後に配列を変更しないでください。

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.FromCharArrayUTF16Node` と `TruffleString.FromIntArrayUTF32Node` を提供します。

TruffleString は、java.lang.StringBuilder に相当する TruffleString の `TruffleStringBuilder` を介して作成することもできます。

TruffleStringBuilder は以下の操作を提供します

  • AppendByte: 文字列ビルダーに1バイト追加します。
  • AppendCharUTF16: UTF-16文字列ビルダーに1文字追加します。
  • AppendCodePoint: 文字列ビルダーに1つのコードポイントを追加します。
  • AppendIntNumber: 文字列ビルダーに整数値を追加します。
  • AppendLongNumber: 文字列ビルダーにlong値を追加します。
  • AppendString: 文字列ビルダーにTruffleStringを追加します。
  • AppendSubstringByteIndex: バイトベースのオフセットと長さで定義された部分文字列を文字列ビルダーに追加します。
  • AppendJavaStringUTF16: 文字ベースのオフセットと長さで定義されたJava文字列の部分文字列を文字列ビルダーに追加します。
  • ToString: 文字列ビルダーから新しいTruffleStringを作成します。

以下の例を参照してください

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
  • BYTES

他の多くのエンコーディングがサポートされていますが、完全に最適化されていません。 これらを使用するには、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` を使用してユニットテストを実行します。 これにより、エンコーディングを切り替えるときに文字列オブジェクトの再利用が無効になり、エンコーディングチェックがより厳密になります。単一の文字列で動作するすべての操作は完全一致を強制しますが、2つの文字列で動作する操作はバイト等価の再解釈を許可します。

複数の文字列パラメータを持つすべての 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: この文字列のすべてのコードポイントは、ASCII としても知られる基本ラテンUnicodeブロックの一部です(0x00 - 0x7f)。
    • LATIN-1: この文字列のすべてのコードポイントは、基本ラテンとラテン-1補助Unicodeブロックの結合に相当する ISO-8859-1 文字セット(0x00 - 0xff)の一部です。 文字列内の少なくとも1つのコードポイントは 0x7f より大きいです。 ISO-8859-1、UTF-16、および UTF-32 にのみ適用されます。
    • BMP: この文字列のすべてのコードポイントは、Unicode基本多言語面(BMP)(0x0000 - 0xffff)の一部です。 文字列内の少なくとも1つのコードポイントは 0xff より大きいです。 UTF-16 および UTF-32 にのみ適用されます。
    • VALID: この文字列は正しくエンコードされており、他の該当するコード範囲外のコードポイントを少なくとも1つ含んでいます(たとえば、UTF-8 の場合、これは ASCII 範囲外のコードポイントが1つあることを意味し、UTF-16 の場合、これは BMP 範囲外のコードポイントが1つあることを意味します)。
    • 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));
    }
}

文字列の等価性と比較 #

TruffleString オブジェクトは、`EqualNode` を使用して等価性をチェックする必要があります。 `HashCodeNode` と同様に、等価比較は文字列のエンコーディングに依存するため、比較の前に、文字列は常に共通のエンコーディングに変換する必要があります。 `Object#equals(Object)` は `EqualNode` と同様に動作しますが、このメソッドには `expectedEncoding` パラメータがないため、文字列の共通エンコーディングが自動的に決定されます。 文字列のエンコーディングが等しくない場合、`TruffleString` は一方の文字列がもう一方の文字列のエンコーディングとバイナリ互換性があるかどうかを確認し、互換性がある場合は内容を照合します。 そうでない場合、文字列は等しくないと見なされ、自動変換は適用されません。

TruffleString の `hashCode` メソッドと `equals` メソッドは文字列のエンコーディングに依存するため、`TruffleString` オブジェクトは、たとえば `HashMap` でキーとして使用する前に、常に共通のエンコーディングに変換する必要があります。

TruffleString は、文字列をバイト単位、文字単位、および整数単位で比較するための3つの比較ノード `CompareBytesNode`、`CompareCharsUTF16Node`、および `CompareIntsUTF32Node` も提供します。

連結 #

連結は `ConcatNode` を介して行われます。 この操作では、両方の文字列が `expectedEncoding` である必要があります。これは、結果の文字列のエンコーディングでもあります。 *遅延連結* は、`lazy` パラメータを介してサポートされます。 2つの文字列が遅延連結されると、新しい文字列の内部配列の割り当てと初期化は、別の操作でその配列への直接アクセスが必要になるまで遅延されます。 このような「遅延連結文字列」の具体化は、`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));
        }
    }
}

部分文字列 #

部分文字列は、それぞれコードポイントベースとバイトベースのインデックスを使用する`SubstringNode`と`SubstringByteIndexNode`を介して作成できます。部分文字列は`lazy`にすることもできます。これは、結果の文字列に対して新しい配列が作成されるのではなく、親文字列の配列が再利用され、部分文字列ノードに渡されたオフセットと長さでアクセスされることを意味します。現在、lazyな部分文字列の内部配列はトリミングされません(つまり、文字列の正確な長さの新しい配列に置き換えられません)。この動作は、lazyな部分文字列が作成されるたびに事実上のメモリリークを引き起こすことに注意してください。これが問題となる可能性のある極端な例:サイズが100メガバイトの文字列が与えられた場合、この文字列から作成されたlazyな部分文字列は、元の文字列がガベージコレクターによって解放された場合でも、100メガバイトの配列を保持し続けます。lazyな部分文字列は注意して使用してください。

`java.lang.String`との相互運用性 #

TruffleStringは、`java.lang.String`を`TruffleString`に変換するための`FromJavaStringNode`を提供します。`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バイトのオブジェクトヘッダー、圧縮されたoopsを使用したポインターごとに4バイト、8バイトのメモリ配置)。`java.lang.String`オブジェクトには、1つのポインターフィールド、1つの`int`フィールド、および1つの`byte`フィールドが含まれており、同じ条件では、合計オブジェクトサイズは24バイトになります。このメモリフットプリントの違いは、多数の小さな文字列が生成される場合に悪影響を与える可能性があります。
  • `TruffleString`は`java.lang.String`と同様に文字列の圧縮を行います。
  • Webアプリケーションで非常に一般的なUTF-8など、他のエンコーディングに文字列を変換する必要がある場合、`TruffleString`は、文字列に特殊文字が含まれていない場合、この操作をノーオペレーションにすることができます。たとえば、ASCIIのみの文字列は、ほとんどすべてのエンコーディングとして再解釈でき、ASCIIのみのUTF-16文字列をUTF-8に変換することはノーオペレーションです。文字列のトランスコーディングが避けられない場合、`TruffleString`はトランスコードされた文字列を元の文字列にキャッシュするため、トランスコーディングは文字列とエンコーディングごとに1回だけ行われます。
  • サードパーティライブラリを使用するために、`TruffleString`オブジェクトは`java.lang.String`に変換してから戻す必要があります。これをできるだけ安価にするために、`TruffleString`は`java.lang.String`から`TruffleString`に変換するときにJava Stringの内部バイト配列を再利用し、`TruffleString`オブジェクトから作成されたJava Stringをオブジェクト自体にキャッシュします。
  • `TruffleString`は、`java.lang.String`にはない追加機能を提供します。
    • lazyな連結と文字列ビュー。これにより、言語が行う必要がある配列コピー操作の量が大幅に削減されます。
    • ネイティブメモリへの文字列ビュー。ネイティブメモリを使用する前にJava配列にコピーする必要がなくなります。
    • `codeRange`プロパティを介した文字列コンテンツの分類。これにより、ASCIIのみなどの文字列の特殊化が可能になります。これにより、一部の文字列操作の複雑さが大幅に軽減されます。
  • すべての`TruffleString`操作のパフォーマンスは、`java.lang.String` counterpartsと同等かそれ以上である必要があります。

コードポイントイテレータ #

`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`を介して内部バイト配列またはネイティブポインターのバイトを上書きできます。内部配列またはネイティブポインターの内容は外部から変更することもできますが、対応する`MutableTruffleString`には`notifyExternalMutation()`を介してこれを通知する必要があります。`MutableTruffleString`はTruffleの相互運用型ではなく、言語境界を渡す前に`TruffleString.AsTruffleString`を介して不変の`TruffleString`に変換する必要があります。

お問い合わせ