Java文字列を1024バイトのチャンクに分割する
質問
Javaで文字列を1024バイトのチャンクに分割する効率的な方法は何ですか?複数のチャンクがある場合、ヘッダー (固定サイズの文字列) を後続のすべてのチャンクで繰り返す必要があります。
解決
文字列とバイトは2つの完全に異なるものであるので、バイトに文字列を分割したいこと詩に絵を分割したいと同じ意味があります。
それはあなたが実際にやってみたいことは何ですか?
の文字列とバイトの間で変換するには、文字列内のすべての文字をエンコードすることができますエンコーディングを指定する必要があります。エンコーディングと文字によっては、それらのいくつかは、複数のバイトにまたがることがあります。
あなたは、1024文字のチャンクに文字列を分割し、バイトとしてそれらをコードするが、各チャンクは1024バイト以上であってもよいことができます。
それとも、バイトに元の文字列をエンコードし、その後1024年のチャンクにそれらを分割するが、その後再度文字列に全体を復号化する前のバイトとしてそれらを追加することを確認する必要がある、またはあなたがで文字化けを得ることができます文字が2バイト以上にまたがるスプリットポイントます。
文字列が非常に長いことができたときに、メモリの使用状況を心配している場合は、、あなたは何回かのように、メモリ内のデータを保つ避けるために、EN /デコードにすると、分割ストリーム(java.ioパッケージ)を使用する必要がありますコピー。理想的には、すべてで1枚の元の文字列を持つ避け、代わりにあなたからそれを得るどこから小さな塊でそれを読むためにストリームを使用する必要があります。
他のヒント
高速な方法とメモリを節約する方法の 2 つの方法があります。ただし、その前に、文字列にどのような文字が含まれているかを知る必要があります。アスキー?ウムラウト (128 ~ 255 の文字) や Unicode (s.getChar() が 256 を超えるものを返す) はありますか。それに応じて、別のエンコーディングを使用する必要があります。バイナリ データがある場合は、データが文字列に保存されるため、「iso-8859-1」を試してください。Unicode を使用している場合は、「utf-8」を試してください。バイナリデータを想定します。
String encoding = "iso-8859-1";
最も早い方法:
ByteArrayInputStream in = new ByteArrayInputStream (string.getBytes(encoding));
文字列は Unicode であるため、すべての文字が必要であることに注意してください。 二 バイト。エンコーディングを指定する必要があります (「プラットフォームのデフォルト」に依存しないでください)。これは後で痛みを引き起こすだけです)。
これで、次を使用して 1024 チャンクで読み取ることができます
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) { ... }
これには、元の文字列の約 3 倍の RAM が必要です。
よりメモリを節約する方法は、StringReader と OutputStreamWriter (ByteArrayOutputStream をラップする) を受け取るコンバーターを作成することです。基礎となるバッファーに 1 つのデータ チャンクが含まれるまで、リーダーからライターにバイトをコピーします。
その場合、データを実際の出力にコピーし (ヘッダーを先頭に追加)、追加のバイト (Unicode からバイトへの変換によって生成された可能性がある) を一時バッファにコピーし、buffer.reset() を呼び出して一時バッファを書き込みます。バッファ。
コードは次のようになります (未テスト)。
StringReader r = new StringReader (string);
ByteArrayOutputStream buffer = new ByteArrayOutputStream (1024*2); // Twice as large as necessary
OutputStreamWriter w = new OutputStreamWriter (buffer, encoding);
char[] cbuf = new char[100];
byte[] tempBuf;
int len;
while ((len = r.read(cbuf, 0, cbuf.length)) > 0) {
w.write(cbuf, 0, len);
w.flush();
if (buffer.size()) >= 1024) {
tempBuf = buffer.toByteArray();
... ready to process one chunk ...
buffer.reset();
if (tempBuf.length > 1024) {
buffer.write(tempBuf, 1024, tempBuf.length - 1024);
}
}
}
... check if some data is left in buffer and process that, too ...
これには数キロバイトの RAM が必要なだけです。
[編集] コメントでは文字列のバイナリデータについて長い議論が行われてきました。まず、バイナリ データを作成してどこかに保存するときに注意していれば、バイナリ データを String に入れるのは完全に安全です。このような String を作成するには、byte[] 配列を取得し、次のようにします。
String safe = new String (array, "iso-8859-1");
Java では、ISO-8859-1 (別名 ISO-Latin1) は 1:1 マッピングです。これは、配列内のバイトがいかなる方法でも解釈されないことを意味します。データに対して substring() などを使用したり、インデックスで検索したり、正規表現を実行したりできるようになりました。たとえば、0 バイトの位置を見つけます。
int pos = safe.indexOf('\u0000');
これは、データのエンコーディングが分からず、コーデックによって問題が発生する前に確認したい場合に特に便利です。
データをどこかに書き込むには、次の逆の操作を行います。
byte[] データ =safe.getBytes("iso-8859-1");
デフォルトの方法は決して使用しないでください new String(array)
または String.getBytes()
! ある日、コードが別のプラットフォームで実行され、壊れてしまいます。
次に、文字列内の文字数が 255 を超える場合の問題です。この方法を使用すると、文字列にそのような文字が含まれることはなくなります。とはいえ、何らかの理由で存在する場合、getBytes() は例外をスローします。ISO-Latin1 ではすべての Unicode 文字を表現する方法がないためです。そのため、コードがサイレントに失敗しないという意味では安全です。
これでは安全性が十分ではないため、バイトと文字列を決して混在させるべきではないと主張する人もいるかもしれません。今の時代、そんな余裕はありません。多くのデータには、明示的なエンコード情報がありません (たとえば、ファイルには、アクセス許可や名前と同じように「エンコード」属性がありません)。XML は、明示的なエンコーディング情報を持つ数少ない形式の 1 つであり、コメントを使用してこの重要な情報を指定する Emacs や jEdit などのエディターがあります。つまり、バイト ストリームを処理するときは、そのストリームがどのエンコーディングで行われているかを常に把握しておく必要があります。現時点では、データがどこから来たかに関係なく、常に機能するコードを書くことは不可能です。
XML を使用する場合でも、ファイルをデコードする前に、ファイルのヘッダーをバイトとして読み取り、エンコードを決定する必要があります。
重要な点は、処理する必要があるデータ ストリームの生成にどのエンコーディングが使用されたかをじっくりと理解することです。それができれば大丈夫ですが、そうでなければ破滅です。この混乱は、ほとんどの人が、同じバイトがエンコーディングに応じて異なる意味を持ち得ること、さらには複数のエンコーディングが存在することさえ認識していないという事実から生じています。また、Sun が「プラットフォームのデフォルトのエンコーディング」という概念を導入していなければ、役に立ったでしょう。
初心者向けの重要なポイント:
- 複数のエンコーディング (文字セット) があります。
- 英語で使用される文字よりも多くの文字があります。いくつかもあります 数字のセット (ASCII、全角、アラビア語、インド語、ベンガル語)。
- 処理しているデータの生成にどのエンコーディングが使用されたかを知っておく必要があります。
- 処理しているデータを書き込むためにどのエンコーディングを使用する必要があるかを知っておく必要があります。
- 次のプログラムが出力 (XML ヘッダー、HTML メタ タグ、特殊なエンコード コメントなど) をデコードできるように、このエンコード情報を指定する正しい方法を知っておく必要があります。
アスキーの時代は終わった。
遅れていることはわかっていますが、自分で解決策を探していたところ、私の答えがベストアンサーとして見つかりました。
private static String chunk_split(String original, int length, String separator) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(original.getBytes());
int n = 0;
byte[] buffer = new byte[length];
String result = "";
while ((n = bis.read(buffer)) > 0) {
for (byte b : buffer) {
result += (char) b;
}
Arrays.fill(buffer, (byte) 0);
result += separator;
}
return result;
}
例:
public static void main(String[] args) throws IOException{
String original = "abcdefghijklmnopqrstuvwxyz";
System.out.println(chunk_split(original,5,"\n"));
}
出力:
abced
fghij
klmno
pqrst
uvwxy
z
私は自分自身のためにこれをしようとしていた、私は1メガバイトによってチャンク巨大な文字列(約10 MB)をする必要があります。 これは、チャンク、最小の時間でデータをすることができます。 (秒未満)。
private static ArrayList<String> chunkLogMessage(String logMessage) throws Exception {
ArrayList<String> messages = new ArrayList<>();
if(logMessage.getBytes().length > CHUNK_SIZE) {
Log.e("chunk_started", System.currentTimeMillis()+"");
byte[] buffer = new byte[CHUNK_SIZE];
int start = 0, end = buffer.length;
long remaining = logMessage.getBytes().length;
ByteArrayInputStream inputStream = new ByteArrayInputStream(logMessage.getBytes());
while ((inputStream.read(buffer, start, end)) != -1){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(buffer, start, end);
messages.add(outputStream.toString("UTF-8"));
remaining = remaining - end;
if(remaining <= end){
end = (int) remaining;
}
}
Log.e("chunk_ended", System.currentTimeMillis()+"");
return messages;
}
messages.add(logMessage);
return messages;
}
Logcatます:
22:08:00.262 3382-3425/com.sample.app E/chunk_started: 1533910080261
22:08:01.228 3382-3425/com.sample.app E/chunk_ended: 1533910081228
22:08:02.468 3382-3425/com.sample.app E/chunk_started: 1533910082468
22:08:03.478 3382-3425/com.sample.app E/chunk_ended: 1533910083478
22:09:19.801 3382-3382/com.sample.app E/chunk_started: 1533910159801
22:09:20.662 3382-3382/com.sample.app E/chunk_ended: 1533910160662
はい、ほとんどすべてではない以上、間違いなく動作します。
それとも、まさにそのこののプロジェクトをチェックアウトすることができ;それだけではチャンクだけではなく、文字列のことですが、また、配列、および、入力ストリームファイルをバイトます。
DataChunker
とStringChunker
これは、2つのクラスを持っています
DataChunker chunker = new DataChunker(8192, blob) {
@Override
public void chunkFound(byte[] foundChunk, int bytesProcessed) {
//process chunk here
}
@Override
public void chunksExhausted(int bytesProcessed) {
//called when all the blocks have been exhausted
}
};
String blob = "Experience is wasted if history does not repeat itself...Gbemiro Jiboye";
final StringBuilder builder = new StringBuilder();
StringChunker chunker = new StringChunker(4, blob) {
@Override
public void chunkFound(String foundChunk, int bytesProcessed) {
builder.append(foundChunk);
System.out.println("Found: "+foundChunk+", bytesProcessed: "+bytesProcessed+" bytes");
}
@Override
public void chunksExhausted(int bytesProcessed) {
System.out.println("Processed all of: "+bytesProcessed+" bytes. Rebuilt string is: "+builder.toString());
}
};
コンストラクタblob
コンストラクタでDatachunker's
バイト配列、File
又はInputStream
のいずれかである