- ベストアンサー
ファイル内の特定バイト数を読み込む方法とエラーハンドリングについて
- ファイル内の1行ごとに指定バイト数だけを読み込む方法を紹介します。
- 取得レコード長が制限値を超えた場合のエラーハンドリングについても解説します。
- ファイル操作に関する知識が少ない方でも理解しやすいように、具体的なループ処理のロジックも提案します。
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
No.5の続きです。 補足にて示して頂いたコードで、 for (c = result.read(); c >= 0 && (char)c != '\r' || c >= 0 && (char)c != '\n'; c = result.read()){ // この部分はとりあえず適当に書いています。 // 実際にはbreakするとループを抜けてしまうので次の行を見れません。 break; } 上記の部分を下記のように修正して頂ければ次の行、あるいはファイルの終端まで読み飛ばす処理になります。 for (c = result.read(); c >= 0 && (char)c != '\r' || c >= 0 && (char)c != '\n'; c = result.read()); read()にて読み込んだ1文字を次々と変数cに上書きするので、結果として読み飛ばしていることになります。 下記記述の方がわかりやすかったかも知れません。 do { c = result.read(); } while(c >= 0 && (char)c != '\r' || c >= 0 && (char)c != '\n'); このループを抜けた時は、改行コードを読み取った直後かファイルの終端であるので、ファイルの終端でない場合は、次に read() した時が次の行の始めの文字を取得することになります。 skip(long n)を使用するには、次の改行コードまでの長さがどのくらいあるかがわかった上で使用しなければならないので、今回のような1文字ずつ読み込む方法には適さないと思います。 また、read(char[] cbuf, int off, int len) も、改行コードを超えて読み込んでしまった場合、超えた分のデータをどう処理するか考えなければならず、処理が複雑になってしまうのでやはり適さないと思います。
その他の回答 (5)
- infeeld
- ベストアンサー率37% (3/8)
行の区切りは改行コードを読み込むまでわかりません。 行ないたい処理は、2048バイト読み込んだかという条件を、改行コードを読み込む間での間に判定する必要があるので、行単位で処理をするのではなく、文字単位で処理をした方が良いと思いました。 BufferedReader br = new BufferedReader( new FileReader("読み込みたいファイル名のフルパス")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int c = -1; do { int readBytes = 0; for (c = br.read(); c >= 0 && c != '\n'; c = br.read()) { readBytes++; if (readBytes > 2048) { System.err.println("2048バイトを超える行を検出しました。"); //ファイルの終わりか改行まで読み飛ばす。 for (c = br.read(); c >= 0 && c != '\n'; c = br.read()); break; } baos.write(c); } System.out.println(baos.toString()); baos.reset(); } while (c >= 0); baos.close(); br.close(); この例ではファイルの終わりか改行コードが出現するまで、1文字ずつ読み込み都度バッファに書き込みます。 連続して読み込んだ文字が2048バイトを超えた時点でエラーメッセージを出力した後に、残りの文字を読み飛ばし、バッファへの書き込みをやめます。 次に、バッファへ書き込んだ内容を文字列として取得し、画面へ出力します。 上記の処理をファイルの終わりまで繰り返します。 出力先が何であるかがわからなかったため、今回はByteArrayOutputStreamを出力先に使用しましたが、OutputStreamやWriterにはwriteメソッドがあるので、他の出力先に応用できると思います。
お礼
ご回答ありがとうございます。 大変参考になります。 ですが、少々変わった点がございますので下記で回答いたします。 まず実際の処理ですが、一旦読込んでおいて、 後から別のメソッドで文字列を取得するという形になりました。 ですので、その際に1行に対して2048バイトを超えていたら、そこから改行までを読込まないようにしたいのです。 で、次の行からまた読み込みたいのです。(全レコードに対して2048バイト未満を読込む。はみ出た分は読込まない。) 簡単にですが、もう少し細かく説明しますと、下記のような感じです。 [ループ] (ファイルの終端まで1バイトずつ読込んでいく) 2048バイト未満で改行コードがあったら(カウンターを0にでも初期化しておいて)、次の行を見に行く。 1行を読込んでいる間に、2048バイトを超えていたらエラー出力し、処理はそのまま続行(はみ出た分は読み捨てる。というか読込まない。) (処理をそのまま続行というのは、ループは抜けずに次の行を見にいくということです) 次の行を指定し、そこから読込めるようにする。 [ループ] あと、改行コードですが実際には「\n」だけでなく「\r」もあるので両方の判定が必要になります。 一応下記で、参考にさせてもらいつつ、それらしいロジックを書いてみました。 疑問点としては、ロジックではなく言葉で言うと、 1行に対して2048バイトを超えていたら、その超えた部分から次の行までを読まずにスキップさせたい ↑このやり方が分かりません。 もしよろしければお知恵を拝借できないでしょうか? skip()というメソッドを使うだろうとは予測しますが、それをどのように使えばいいかが分かりません。 あとは「read()」ではなく、「read(char[] cbuf, int off, int len)」の方を使うだろうと予測します。 InputStreamReader isr; isr = new InputStreamReader(fis); BufferedReader result = new BufferedReader(isr); int c = 0; // 整数としての読み込まれた文字 int cnt = 0; // 行をカウント final int BUFSIZE = 2048; // サイズ while((c = result.read()) != -1){ cnt++; // 改行コードが含まれているか判定 if ((char)c == '\r' || (char)c == '\n') { // 改行コードが含まれているため、カウンターを初期化 cnt = 0; } // 1行の読込み数を判定 if (cnt > BUFSIZE) { // エラーメッセージを出力 System.out.println("2048バイトを超える行を検出しました。"); // ここで次の行まで読み捨てたい // つまり2048バイト未満だけを読込んだままにし、 // 超えた分は読み飛ばし、次の行を見るようにしたい for (c = result.read(); c >= 0 && (char)c != '\r' || c >= 0 && (char)c != '\n'; c = result.read()){ // この部分はとりあえず適当に書いています。 // 実際にはbreakするとループを抜けてしまうので次の行を見れません。 break; } // カウンターの初期化 cnt = 0; } }
- erichgumma
- ベストアンサー率48% (13/27)
ByteBufferじゃ駄目なんですか? ... import java.nio.*; ... private static final int BSIZE = 1024; ... ByteBuffer buff = ByteBuffer.allocate(BSIZE); ... http://okwave.jp/qa/q5981150.html 改行は portability を考慮した public static final String NEWLINE = System.getProperty("line.separator"); を用いるのが、リファクタリング上、推奨されます。
お礼
ありがとうございます。 ByteBufferなるものがあるのですね。 これもまた詳しくみておきます。(ただ今回はBefferedReaderクラスを使います) System.getProperty("line.separator"); ↑これについては、実際の処理ではバイト単位で読込んでいるのに対し、 これは「\r\n」で取れてくる(String型)なので処理ができなくなってしまいます。
- Tacosan
- ベストアンサー率23% (3656/15482)
えぇと.... コードとして書く前に, ロジックをきちんと考えていますか? たとえば, while の中で最後の方にある int c = cbuf[currpos++]; // cbufに改行を含む if (c == '\n') { startLine = true; } の部分は「どのような動作をさせたい」と思って書いたのか, 説明してみてください.
補足
ご回答ありがとうございます。 もう少し詳しくロジックを書いてみました。(ただ、これも中途半端です) やりたいことは質問に書いたとおりなのですが、 readメソッドを使った事が無く、また使用例も少ない為いまいちわかりにくいです。 どのように動作させたいかというと、 ファイル内の次の文字を読みに行き、 改行があればstartLineをtrueに設定し、 また次の行を見に行く・・・というようなことを書きたかったです。 最初にそのロジックを書いた時言ったように、実現の仕方がよく分からず、中途半端になってしまっています。 その書き方がよくわからないので、とりあえずのロジックを乗せて、よろしければヒントをもらえないでしょうか?という質問をしました。 単純にしたいのは ・ファイル内の1行を読込み時に2048バイト未満を読み込む。 →2048バイトを超えていたらエラー出力し、そのまま処理を続行する。(2048バイト分は読込み、はみ出た分は読み捨てる) ・読み込んだ一行を取得する。 というだけなのですが・・・。 while (true) { result.read(cbuf, currpos, BUFSIZE); if (startLine) { // 読み込んだのは行の開始 startLine = false; } int c = cbuf[currpos++]; // cbufに改行を含む if (c == '\n') { startLine = true; // ここで次の行を見るようにするロジックを入れる? } //読み込んだ1行が2048バイトを超えていたらエラーを出したい。(処理は続行し、2048バイト分だけ読み込む) //cbufの0番目から改行までを取得(やり方が分からない。ここで記述すべきじゃない?) // EOFならば-1を返す。 if (c == -1) { break; } }
- askaaska
- ベストアンサー率35% (1455/4149)
答えはreadメソッドよ。 readLineメソッドは内部で 改行までを読み込んでいるに過ぎないわ。 つまりロジック的には次のようになるわね。 boolean startLine = true; while(ナニカ) { read(buf, pos, len) if (startLine) { // TODO 読み込んだのは行の開始 startLine = false; } if (bufに改行を含む) { //TODO pos位置を調整 startLine = true; } } 私ならBufferedReaderを継承して作った新しいReaderを用意して readLineSmallメソッドでも作って そこでやらせるわね。
お礼
ご回答ありがとうございます。 だんだんとわかってはきました・・・が、 おっしゃってることはそれなりに理解できたつもりなのですが 実際に組む時、不慣れな為どうやって組むかがいまいちで・・・。 一応下記のように組んではいるのですが、中途半端な組み方になってしまって・・・。 あと、デバッガで動かすとなぜか最初の判定で「startLine」がfalseになってしまっています。 もう少しヒントをいただくことって出来ないでしょうか? よろしかったらお願いいたします。 public static void main(String[] args) throws IOException { File testFile = new File("C:\\work\\test\\file\\readTest.txt"); // ファイルオープン FileInputStream fis = new FileInputStream(testFile); InputStreamReader isr; isr = new InputStreamReader(fis); BufferedReader result = new BufferedReader(isr); boolean startLine = true; final int BUFSIZE = 2048; char cbuf[] = new char[BUFSIZE]; int currpos = 0; int maxpos = 0; //とりあえず書いてみたけど使っていない while (true) { result.read(cbuf, 0, BUFSIZE); if (startLine) { // 読み込んだのは行の開始 startLine = false; } int c = cbuf[currpos++]; // cbufに改行を含む if (c == '\n') { startLine = true; } } result.close(); }
- SaKaKashi
- ベストアンサー率24% (755/3136)
1行の区切りをどうやって認識するのかがわかってないようですけど。 それが出来なければ、1行を読むことは不可能ですね。 >readLineで一度他の変数へ入れてからというのは無しです その理由がわかりませんね。
補足
ご回答ありがとうございます。 >1行の区切りをどうやって認識するのかがわかってないようですけど。 1行のテキストを読み込む場合、1行の終端は、改行「\n」か、復帰「\r」、または復行とそれに続く改行のどれかで認識されます。 >>readLineで一度他の変数へ入れてからというのは無しです >その理由がわかりませんね。 1行が長すぎる場合があり、読み込む時点でOutOfMemoryが発生する可能性があるからです。 その為、1行を読み込む時点で取得サイズに制限をかけたいのです。
お礼
再度のアドバイスありがとうございます。 とても感謝しております。 後々、別のメソッドでこの読込んだBufferedReaderから文字列(レコード)を取得する時に、 2048バイト以降が読込まれていたら困ると思っていたのですが、 for分内での「c = result.read()」は結果的に読込まれていないのですねぇ。 skipの使用も、どう実現するか結構悩みました。見えそうで見えず…。やはり適さないのですね。 read(char[] cbuf, int off, int len)もいざ使用するとなると相当大変そうですね…。 あと、改行の「\r」「\n」の区別ですが for分で「||」を付けると次の行ではなく、ファイルの終端まで行ってしまい、for分を抜けてしまいました。 「|」で試したらなぜか次の行を見てくれました。(下記の記述) for (c = result.read(); c >= 0 && c != '\r' | c >= 0 && c != '\n'; c = result.read()); また、最初にアドバイスをいただいたコードを応用した、 下記のやり方の方が余分な改行を読込まなくてすむのでこちらのやり方の方がよさそうですね。 int c = -1; do { int readBytes = 0; for (c = result.read(); c >= 0 && c != '\r' | c >= 0 && c != '\n'; c = result.read()) { readBytes++; System.out.println("char = " + (char)c); //確認 if (readBytes > 4) { System.err.println("2048バイトを超える行を検出しました。"); // ファイルの終わりか改行まで読み飛ばす。 for (c = result.read(); c >= 0 && c != '\r' | c >= 0 && c != '\n'; c = result.read()); break; } } } while (c >= 0);
補足
あと、 「 c != '\n';」←この判定は 「(char)c != '\n';」としなくてもちゃんと判定してくれるのですね。