1. ホーム
  2. java

[解決済み] Java文字列から印刷不可能な文字をすべて取り除く最速の方法

2023-05-21 01:05:27

質問

から印字不可能な文字をすべて取り除く最速の方法は何ですか? String から印字できない文字をすべて取り除く最速の方法は何ですか?

今のところ、138バイト、131文字のStringで試行錯誤しています。

  • 文字列の replaceAll() - 最も遅い方法
    • 517009件/秒
  • パターンをプリコンパイルし、Matcherの replaceAll()
    • 637836件/秒
  • StringBufferを使用し、コードポイント取得は codepointAt() でコードポイントを取得し、StringBufferに追加します。
    • 711946件/秒
  • StringBufferを使って、文字列を取得する。 charAt() を使って一文字ずつ取得し、StringBufferに追加します。
    • 1052964件/秒
  • プリアロケート char[] バッファを確保し、文字列は charAt() を使って一文字ずつ取得し、このバッファを埋めてから、文字列に変換します。
    • 2022653件/秒
  • プリ・アロケート 2 char[] を使用して、既存の文字列のすべての文字を一度に取得します。 getChars() を使い、古いバッファを一つずつ繰り返し、新しいバッファを満たし、新しいバッファを文字列に変換します。 私なりの最速版
    • 2502502件/秒
  • 同じものを2つのバッファで使用 - 使用するのは byte[] , getBytes() で、エンコーディングを "utf-8" に指定します。
    • 857485件/秒
  • 2と同じもの byte[] バッファで、エンコーディングを定数で指定しています。 Charset.forName("utf-8")
    • 791076件/秒
  • 2と同じもの byte[] バッファを使用しますが、エンコーディングは 1 バイトのローカルエンコーディングとして指定します (ほとんどまともなことではありません)。
    • 370164 件/秒

私のベストトライは次のようなものでした。

    char[] oldChars = new char[s.length()];
    s.getChars(0, s.length(), oldChars, 0);
    char[] newChars = new char[s.length()];
    int newLen = 0;
    for (int j = 0; j < s.length(); j++) {
        char ch = oldChars[j];
        if (ch >= ' ') {
            newChars[newLen] = ch;
            newLen++;
        }
    }
    s = new String(newChars, 0, newLen);

さらに高速化する方法について、何かご意見はありますか?

非常に奇妙な質問に答えてくれたことにボーナスポイントがあります: なぜ "utf-8" という文字セット名を直接使用すると、事前に割り当てられた静的 const Charset.forName("utf-8") ?

更新情報

  • からの提案 ラチェットフリーク は 3105590 件/秒の素晴らしい結果を出し、+24% の改善となりました!
  • からの提案 Ed Staub は、さらに別の改善をもたらしました。3471017 件 / 秒で、以前の最高値よりも 12% 向上しています。

アップデート 2

提案されたすべての解決策とそのクロスミューテーションを集めるために最善を尽くし、それを 小さなベンチマークフレームワークとして github で公開しています。 . 現在、17のアルゴリズムで構成されています。そのうち1つは特別なものです。 Voo1 アルゴリズム ( SOユーザーVooさんより提供 ) は、複雑な反射トリックを使用しているため、非常に高速ですが、JVM 文字列の状態を混乱させるので、別にベンチマークされています。

これをチェックアウトして、あなたのマシン上で結果を決定するために実行することが歓迎されます。以下は、私が得た結果の要約です。これは仕様です。

  • Debian sid
  • Linux 2.6.39-2-amd64 (x86_64)
  • パッケージからインストールされた Java sun-java6-jdk-6.24-1 として、JVMは自分自身を識別します。
    • Java(TM) SE ランタイム環境 (ビルド 1.6.0_24-b07)
    • Java HotSpot(TM) 64 ビット サーバー VM (ビルド 19.1-b02、混合モード)

異なるアルゴリズムでは、異なる入力データのセットが与えられると、最終的に異なる結果を示します。3つのモードでベンチマークを実行しました。

同じ単一の文字列

このモードは StringSource クラスの定数として提供されます。対決は

 Ops / s │ Algorithm(アルゴリズム
──────────┼──────────────────────────────
6 535 947 │ Voo1
──────────┼──────────────────────────────
5 350 454 │ RatchetFreak2EdStaub1GreyCat1
5 249 343 │ EdStaub1
5 002 501 │ EdStaub1GreyCat1
4 859 086 │ ArrayOfCharFromStringCharAt
4 295 532 │ RatchetFreak1
4 045 307 │ ArrayOfCharFromArrayOfChar
2 790 178 │ RatchetFreak2EdStaub1GreyCat2(ラチェットフリーク2エドスタウブ1グレイキャット2
2 583 311 │ RatchetFreak2
1 274 859 │ StringBuilderChar
1 138 174 │ StringBuilderCodePoint
  994 727 │ ArrayOfByteUTF8String
  918 611 │ ArrayOfByteUTF8Const
  756 086 │ MatcherReplace(マッチャーリプレイス
  598 945 │ StringReplaceAll
  460 045 │ ArrayOfByteWindows1251


チャート式で。 <イグ

<サブ (出典 灰猫.ru )

複数の文字列、100%の文字列に制御文字が含まれる

ソース文字列プロバイダは、(0..127)文字セットを使用してランダムな文字列を大量に事前生成していたため、ほぼすべての文字列に少なくとも1つの制御文字が含まれていました。アルゴリズムは、このあらかじめ生成された配列からラウンドロビン方式で文字列を受け取った。

 Ops / s │ アルゴリズム

2 123 142 │ Voo1

1 782 214 │ EdStaub1
1 776 199 │ EdStaub1GreyCat1
1 694 628 │ ArrayOfCharFromStringCharAt
1 481 481 │ ArrayOfCharFromArrayOfChar
1 460 067 │ RatchetFreak2EdStaub1GreyCat1
1 438 435 │ RatchetFreak2EdStaub1GreyCat2
1 366 494 │ RatchetFreak2
1 349 710 │ RatchetFreak1
  893 176 │ ArrayOfByteUTF8String
  817 127 │ ArrayOfByteUTF8Const
  778 089 │ StringBuilderChar
  734 754 │ StringBuilderCodePoint
  377 829 │ ArrayOfByteWindows1251
  224 140 │ MatcherReplace
  211 104 │ StringReplaceAll

チャート式で。

<サブ (出典 灰猫.ru )

複数の文字列、1%の文字列に制御文字が含まれる

残りの99%は[32..127]の文字セットを使って生成されているため、制御文字を含むことができません。この合成負荷は、私のところでこのアルゴリズムを実際に適用した場合に最も近いものです。

 Ops / s │ アルゴリズム

3 711 952 │ Voo1

2 851 440 │ EdStaub1GreyCat1
2 455 796 │ EdStaub1
2 426 007 │ ArrayOfCharFromStringCharAt
2 347 969 │ RatchetFreak2EdStaub1GreyCat2
2 242 152 │ RatchetFreak1
2 171 553 │ ArrayOfCharFromArrayOfChar
1 922 707 │ RatchetFreak2EdStaub1GreyCat1
1 857 010 │ RatchetFreak2
1 023 751 │ ArrayOfByteUTF8String
  939 055 │ StringBuilderChar
  907 194 │ ArrayOfByteUTF8Const
  841 963 │ StringBuilderCodePoint
  606 465 │ MatcherReplace
  501 555 │ StringReplaceAll
  381 185 │ ArrayOfByteWindows1251

チャート式で。

<サブ (出典 灰猫.ru )

誰がベストアンサーなのか、私にはとても判断がつきませんが、実戦的なベストアンサーはエド・ストウブが出した/触発されたものなので、彼の答えをマークするのが公平でしょう。参加された皆さん、ありがとうございました。皆さんのご意見はとても参考になり、貴重なものでした。あなたのマシンでテストスイートを実行し、さらに良い解決策を提案するのは自由です(JNIによる解決策、誰かいませんか?)

参考文献

どのように解決するのですか?

もし、このメソッドをスレッド間で共有されないクラスに埋め込むことが合理的であれば、バッファを再利用することができます。

char [] oldChars = new char[5];

String stripControlChars(String s)
{
    final int inputLen = s.length();
    if ( oldChars.length < inputLen )
    {
        oldChars = new char[inputLen];
    }
    s.getChars(0, inputLen, oldChars, 0);

など

これは、現在のベストケースを理解すると、20%ほどの大きな勝算があります。

もし、大きな文字列の可能性があり、メモリリークが懸念される場合は、弱い参照を使用することができます。