ファイルの各行のバイト単位のサイズが必要なので、読み取られたファイルのパーセンテージを取得できます。でファイルのサイズをすでに取得していますがfile.length()
、各行のサイズを取得するにはどうすればよいですか?
5 に答える
エンコーディングを知る必要があります-そうでなければ、それは無意味な質問です。たとえば、「foo」はUTF-16では6バイトですが、ASCIIでは3バイトです。一度に1行ずつ読んでいると仮定すると(質問があれば)、読み始めたときに指定する必要があるので、使用しているエンコーディングを知っておく必要があります。
String.getBytes(charset)
特定の文字列のエンコードされた表現を取得するために呼び出すことができます。
プラットフォームのデフォルトのエンコーディングを使用するため、単に呼び出すのではありません。String.getBytes()
これはすべて多少手間がかかることに注意してください...バイトを読み取り、テキストにデコードしてから、バイトに再エンコードしています...
final String hello_str = "Hello World";
hello_str.getBytes().length is the "byte size", i.e. the number of bytes
あなたはおそらくファイルを読むために以下について使用します
FileInputStream fis = new FileInputStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
String line;
while ((line = br.readLine()) != null) {
/* process line */
/* report percentage */
}
すでに最初にエンコーディングを指定する必要があります。そうでない場合は、AndroidでUTF-8を入手する必要があります。これはデフォルトですが、変更することができます。しかし、それを行うデバイスはないと思います。
他の回答がすでに述べたことを繰り返すには:文字数は必ずしもバイト数と同じではありません。特にUTFエンコーディングには注意が必要です。現在、249,764個のUnicode文字が割り当てられており、100万文字を超える可能性があります(WP)。UTFは1〜4バイトを使用して、それらすべてをエンコードできるようにします。UTF-32は、常に4バイトを使用するため、最も単純なケースです。UTF-8はそれを動的に実行し、1〜4バイトを使用します。単純なASCII文字は1バイトしか使用しません。(出典:UTF&BOM FAQ)
バイト数を取得するには、たとえばを使用できますline.getBytes("UTF-8").length()
。大きな欠点の1つは、String内部配列のコピーを毎回作成し、その後破棄するため、これは非常に非効率的であるということです。これはAndroidで取り上げられた#1です| パフォーマンスのヒント
また、次の理由により、ファイルから読み取られた実際のバイト数に関して100%正確ではありません。
たとえば、UTF-16テキストファイルは、多くの場合、リトルエンディアンとビッグエンディアンのどちらを解釈する必要があるかを示すために、特別な2バイトのBOM(バイト順マーク)で始まります。
String
これらの2(UTF-8:3、UTF-32:4)バイトは、リーダーから取得したものを見るだけでは報告されません。したがって、ここではすでに数バイト離れています。ファイルのすべての行をUTF-16に変換すると、
String
各行のBOMバイトが含まれます。したがってgetBytes
、各行に2バイトが多すぎます。行末文字は、結果の行の一部ではありません-
String
。さらに悪いことに、回線の終わりを通知するさまざまな方法があります。'\n'
通常、 1文字のみのUnixスタイルまたは'\r''\n'
2文字のWindowsスタイル。はBufferedReader
単にそれらをスキップします。ここで、計算には非常に可変的なバイト数がありません。Unix/UTF-8の場合は1バイトからWindows/UTF-32の場合は8バイト。
Unix / UTF-16を使用している場合、最後の2つの理由は互いに否定し合いますが、それはおそらく一般的なケースではありません。エラーの影響は行の長さにも依存します。各行に4バイトのエラーがあり、合計で10バイトの長さしかない場合、進行状況はかなり間違っています(私の計算が良ければ、進行状況は140%になります)。または、計算で1行あたり-4バイトと+4バイトのどちらを想定しているかに応じて、最後の行の後に60%)
つまり、これまでのところ、何をするかに関係なく、近似値しか得られないということです。
実際のバイトカウントを取得するには、独自の特別なバイトカウントを作成すればおそらく実行できますReader
が、それはかなりの作業になります。
InputStream
別の方法は、基になるストリームから実際に読み取られたバイト数をカウントするカスタムを使用することです。それはそれほど難しいことではなく、エンコーディングを気にしません。
BufferedReader
大きな欠点は、内部バッファーがいっぱいになり、そこから行が読み取られ、ファイルから次のチャンクが読み取られるため、読み取った行に比例して増加しないことです。バッファが十分に大きい場合は、すでに最初の行で100%になっています。しかし、私はあなたのファイルが十分に大きいか、あなたが進捗状況について知りたくないと思います。
これは、たとえば、そのような実装になります。それは動作しますが、それが完璧であることを保証することはできません。mark()
ストリームがとを使用している場合は機能しませんreset()
。ただし、ファイルの読み取りではそれを行うべきではありません。
static class CountingInputStream extends FilterInputStream {
private long bytesRead;
protected CountingInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int result = super.read();
if (result != -1) bytesRead += 1;
return result;
}
@Override
public int read(byte[] b) throws IOException {
int result = super.read(b);
if (result != -1) bytesRead += result;
return result;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
if (result != -1) bytesRead += result;
return result;
}
@Override
public long skip(long n) throws IOException {
long result = super.skip(n);
if (result != -1) bytesRead += result;
return result;
}
public long getBytesRead() {
return bytesRead;
}
}
次のコードを使用する
File file = new File("mytestfile.txt");
int linesRead = 0;
long progress = 0;
long fileLength = file.length();
String line;
CountingInputStream cis = new CountingInputStream(new FileInputStream(file));
BufferedReader br = new BufferedReader(new InputStreamReader(cis, "UTF-8"), 8192);
while ((line = br.readLine()) != null) {
long newProgress = cis.getBytesRead();
if (progress != newProgress) {
progress = newProgress;
int percent = (int) ((progress * 100) / fileLength);
System.out.println(String.format("At line: %4d, bytes: %6d = %3d%%", linesRead, progress, percent));
}
linesRead++;
}
System.out.println("Total lines: " + linesRead);
System.out.println("Total bytes: " + fileLength);
br.close();
私は次のような出力を取得します
At line: 0, bytes: 8192 = 5%
At line: 82, bytes: 16384 = 10%
At line: 178, bytes: 24576 = 15%
....
At line: 1621, bytes: 155648 = 97%
At line: 1687, bytes: 159805 = 100%
Total lines: 1756
Total bytes: 159805
または同じファイルの場合UTF-16でエンコード
At line: 0, bytes: 24576 = 7%
At line: 82, bytes: 40960 = 12%
At line: 178, bytes: 57344 = 17%
.....
At line: 1529, bytes: 303104 = 94%
At line: 1621, bytes: 319488 = 99%
At line: 1687, bytes: 319612 = 100%
Total lines: 1756
Total bytes: 319612
印刷する代わりに、進捗状況を更新できます。
それで、最良のアプローチは何ですか?
- これらの文字に1バイトのみを使用するエンコーディングで単純なASCIIテキストがあることがわかっている場合:使用するだけで
String#length()
(行末に+1または+2を追加するString#length()
こともできます)、高速で単純です。問題はないはずです。 - 単純なアプローチが機能しない国際的なテキストがある場合:
- 各行の処理にかなり時間がかかる小さなファイルの場合:
String#getBytes()
、1行の処理が長いほど、一時配列とそのガベージコレクションの影響が少なくなります。不正確さは許容範囲内である必要があります。進行状況が最後に100%を超えたり100%を下回ったりしても、クラッシュしないように注意してください。 - 上記のアプローチよりも大きなファイルの場合。ファイルが大きいほど良いです。0.001%のステップで進行状況を更新すると、処理が遅くなります。リーダーのバッファーサイズを小さくすると、精度は向上しますが、読み取りパフォーマンスも低下します。
- 各行の処理にかなり時間がかかる小さなファイルの場合:
- 十分な時間がある場合:正確なバイト位置を示す独自のリーダーを作成します。おそらく、Readerはすでに文字を操作しているので、
InputStreamReader
との組み合わせです。Androidの実装が出発点として役立つ場合があります。BufferedReader
ファイルがASCIIファイルの場合は、String.length();を使用できます。それ以外の場合は、より複雑になります。
と呼ばれる文字列変数があると考えてくださいhello_str
final String hello_str = "Hello World";
//Check Character length
hello_str.length() //output will be 11
// Check encoded sizes
final byte[] utf8Bytes = hello_str.getBytes("UTF-8");
utf8Bytes.length //output will be 11
final byte[] utf16Bytes= hello_str.getBytes("UTF-16");
utf16Bytes.length // output will be "24"
final byte[] utf32Bytes = hello_str.getBytes("UTF-32");
utf32Bytes.length // output will be "44"