前言 :
最近工作中遇到的鳥問題, 一般我們在處理文字檔案時, 會慣用 BufferedReader 類別上的 readLine() 來幫我們一次讀取一行來進行處理. 而當我們在處理檔案同時具有 "\r", "\n", "\r\n" 時, 到底哪個會被 BufferedReader 視為斷行的字元呢? 看看 JDK 上 readLine() 的說明 :
說白了就是都會! 但其實我只希望使用 "\r\n" 當作斷行符號怎麼辦呢? 底下我們將一步步說明並實作一個 Wrapper 將 InputStream 進行封裝, 只對 "\r\n" 做斷行處理.
前置工作 :
在一開始, 我們要來寫一些簡單的代碼幫我們產生測試文件. 這邊的測試文件使用的編碼為 "Big5" :
執行後應該會在執行目錄下產生 "test.txt" 檔案, 用 UltraEdit 打開後使用 Hex mode 觀察 :
可以觀察到我們產生了一個使用不同 "\r" (0x0d) 與 "\n" (0x0a) 組合當作斷行的範例. 底下使用我們慣用的方法來讀取, 看看 Java 的 BufferedReader 如何斷行 :
執行該方法後, 可以發現每一種斷行組合都可以成功斷行 :
只處理 "\r\n" 斷行的類別 JBReader :
但我們的需求是只對 "\r\n" 斷行, 實際上 Windows 使用的斷行符號是 "\r\n" ; Linux 使用的斷行符號是 "\n" ; Mac 使用的斷行符號是 "\r", 這是為什麼 BufferedReader 會將這些組合都視為斷行的原因, 為了跨平台摟. 因此我們要嗎繼承 BufferedReader 並改寫它上面的方法(s), 或是根據自己需求撰寫一個全新的類別. 這邊採用後面的方法. 首先使用了設計模式中的 Decorator Pattern (裝飾模式 - 動態增加類別功能), 我們設計一個類別 JBReader 並於建構子中傳入 InputStream 物件, 並撰寫方法 readLine() 將檔案的 binary 使用定義好的 buffer (固定 buffer size, 避免一次一個 byte 讀出造成過多的 IO read overhead)讀出來, 當遇到 "\r\n" (0x0d0a) 便將之前讀出來的 binary 使用 String 上的建構子 String(byte[] bytes, String charsetName) 建立字串物件並返回.
完整代碼如下 :
- JBReader.java :
接著你便可以如下使用該類別讀出剛剛我們產生的測試檔案 :
讀出結果可以發現, 只有 "\r\n" 會被視為斷行 :
最近工作中遇到的鳥問題, 一般我們在處理文字檔案時, 會慣用 BufferedReader 類別上的 readLine() 來幫我們一次讀取一行來進行處理. 而當我們在處理檔案同時具有 "\r", "\n", "\r\n" 時, 到底哪個會被 BufferedReader 視為斷行的字元呢? 看看 JDK 上 readLine() 的說明 :
說白了就是都會! 但其實我只希望使用 "\r\n" 當作斷行符號怎麼辦呢? 底下我們將一步步說明並實作一個 Wrapper 將 InputStream 進行封裝, 只對 "\r\n" 做斷行處理.
前置工作 :
在一開始, 我們要來寫一些簡單的代碼幫我們產生測試文件. 這邊的測試文件使用的編碼為 "Big5" :
- package test;
- import java.io.File;
- import java.io.FileOutputStream;
- public class GenTestFile {
- public static void main(String[] args) throws Exception{
- String encoding = "big5";
- File outf = new File("test.txt");
- FileOutputStream fos = new FileOutputStream(outf);
- fos.write("Line1 with \\r!\r".getBytes(encoding));
- fos.write("Line2 with \\r\\n!\r\n".getBytes(encoding));
- fos.write("Line3 with \\n!\n".getBytes(encoding));
- fos.write("Line4".getBytes(encoding));
- fos.close();
- }
- }
可以觀察到我們產生了一個使用不同 "\r" (0x0d) 與 "\n" (0x0a) 組合當作斷行的範例. 底下使用我們慣用的方法來讀取, 看看 Java 的 BufferedReader 如何斷行 :
- public static void read() throws Exception
- {
- String encoding = "big5";
- File outf = new File("test.txt");
- BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(outf), encoding));
- String line = null;
- while((line=br.readLine())!=null)
- {
- System.out.printf("\t[Info] Read: %s\n", line);
- }
- br.close();
- }
只處理 "\r\n" 斷行的類別 JBReader :
但我們的需求是只對 "\r\n" 斷行, 實際上 Windows 使用的斷行符號是 "\r\n" ; Linux 使用的斷行符號是 "\n" ; Mac 使用的斷行符號是 "\r", 這是為什麼 BufferedReader 會將這些組合都視為斷行的原因, 為了跨平台摟. 因此我們要嗎繼承 BufferedReader 並改寫它上面的方法(s), 或是根據自己需求撰寫一個全新的類別. 這邊採用後面的方法. 首先使用了設計模式中的 Decorator Pattern (裝飾模式 - 動態增加類別功能), 我們設計一個類別 JBReader 並於建構子中傳入 InputStream 物件, 並撰寫方法 readLine() 將檔案的 binary 使用定義好的 buffer (固定 buffer size, 避免一次一個 byte 讀出造成過多的 IO read overhead)讀出來, 當遇到 "\r\n" (0x0d0a) 便將之前讀出來的 binary 使用 String 上的建構子 String(byte[] bytes, String charsetName) 建立字串物件並返回.
完整代碼如下 :
- JBReader.java :
- package flib.util.io;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.LinkedList;
- import java.util.List;
- public class JBReader {
- public static String ENCODING = "big5";
- public static String OUT_ENCODING = null;
- public static int BUFFER_SIZE = 1024;
- public static boolean REMOVE_END_SPECIAL_CHAR = true; // Check '\r' and '\n' only.
- private InputStream is = null;
- private byte buffer[] = null;
- private byte prevByte = -1;
- private int prevLastFrom=-1, prevLastEnd=-1;
- private List
strBuf = new ArrayList(); - public JBReader(){}
- public JBReader(InputStream is) {
- this.is = is;
- buffer = new byte[BUFFER_SIZE];
- }
- public JBReader(InputStream is, String encoding)
- {
- this(is);
- this.ENCODING = encoding;
- }
- public void assignIS(InputStream is) throws IOException
- {
- assignIS(is, false);
- }
- public void assignIS(InputStream is, boolean reallocBuffer) throws IOException
- {
- close();
- this.is = is;
- if(reallocBuffer || buffer == null) buffer = new byte[BUFFER_SIZE];
- }
- public void close() throws IOException{
- if(is!=null) {
- is.close();
- buffer = null;
- }
- strBuf.clear();
- prevByte = -1;
- prevLastFrom = prevLastEnd = -1;
- }
- public byte[] readLineInBinary() throws IOException
- {
- if(is!=null)
- {
- strBuf.clear();
- // Restore last binary from previous loop
- if(prevLastFrom>0)
- {
- for(int i=prevLastFrom; i
- prevLastFrom=prevLastEnd=-1;
- }
- int rs = -1;
- boolean isNewLine = false;
- // Loop to read binary data
- while((rs = is.read(buffer)) > 0)
- {
- for(int i=0; i
- {
- strBuf.add(buffer[i]);
- if(prevByte == (byte)0x0d && buffer[i] == (byte)0x0a)
- {
- isNewLine = true;
- prevByte = -1;
- if(i+1< rs) {
- prevLastFrom=i+1;
- prevLastEnd=rs;
- }
- break;
- }
- else
- {
- prevByte = buffer[i];
- }
- }
- if(isNewLine) break;
- }
- if(rs<0) return null;
- // Process returned string
- byte binarys[] = new byte[strBuf.size()];
- for(int i=0; i
- return binarys;
- }
- return null;
- }
- protected String processRtnStr() throws IOException
- {
- byte binarys[] = null;
- if(REMOVE_END_SPECIAL_CHAR)
- {
- List
scIdxList = new LinkedList (); - for(int i=0; i
- {
- byte cb = strBuf.get(i);
- if(cb == (byte)0x0d || cb == (byte)0x0a) scIdxList.add(i);
- }
- binarys = new byte[strBuf.size()-scIdxList.size()];
- int j=0;
- for(int i=0; i
- if(!scIdxList.contains(i))
- {
- binarys[j++] = strBuf.get(i);
- }
- else
- {
- scIdxList.remove(new Integer(i));
- }
- }
- else
- {
- binarys = new byte[strBuf.size()];
- for(int i=0; i
- }
- if(OUT_ENCODING==null)
- {
- return new String(binarys, ENCODING);
- }
- else
- {
- return new String(new String(binarys, ENCODING).getBytes(OUT_ENCODING));
- }
- }
- public String readLine() throws IOException
- {
- if(is!=null)
- {
- strBuf.clear();
- // Restore last binary from previous loop
- if(prevLastFrom>0)
- {
- boolean isNewLine = false;
- for(int i=prevLastFrom; i
- {
- strBuf.add(buffer[i]);
- if(prevByte == (byte)0x0d && buffer[i] == (byte)0x0a)
- {
- isNewLine = true;
- prevByte = -1;
- if(i+1< prevLastEnd) {
- prevLastFrom=i+1;
- } else {
- prevLastFrom=prevLastEnd=-1;
- }
- break;
- }
- else
- {
- prevByte = buffer[i];
- }
- }
- if(isNewLine)
- {
- return processRtnStr();
- }
- prevLastFrom=prevLastEnd=-1;
- }
- int rs = -1;
- boolean isNewLine = false;
- // Loop to read binary data
- while((rs = is.read(buffer)) > 0)
- {
- for(int i=0; i
- {
- strBuf.add(buffer[i]);
- if(prevByte == (byte)0x0d && buffer[i] == (byte)0x0a)
- {
- isNewLine = true;
- prevByte = -1;
- if(i+1< rs)
- {
- prevLastFrom=i+1;
- prevLastEnd=rs;
- }
- break;
- }
- else
- {
- prevByte = buffer[i];
- }
- }
- if(isNewLine) break;
- }
- if(rs<0 && strBuf.size()==0)
- {
- //System.out.printf("\t[Test] EOF!");
- return null;
- }
- // Process returned string
- return processRtnStr();
- }
- return null;
- }
- }
- public static void main(String[] args) throws Exception{
- File testf = new File("test.txt");
- FileInputStream fis = new FileInputStream(testf);
- //BufferedReader br = new BufferedReader(new InputStreamReader(fis, ENCODING));
- JBReader br = new JBReader(fis);
- String line = null; int lc = 0;
- while((line=br.readLine())!=null)
- {
- lc++;
- System.out.printf("\t[Info] Read : %s\n", line.trim());
- }
- System.out.printf("\t[Info] Total %d line(s).\n", lc);
- br.close();
- }
This message was edited 19 times. Last update was at 21/08/2012 10:15:45
請問一下 第65.75.99行的i都有錯 要怎麼修改呢?
回覆刪除其實不只耶 就是很多for條件好像都沒有打完@@
回覆刪除Check this:
刪除https://github.com/johnklee/flib/blob/master/src/flib/util/io/JBReader.java
The error is caused by characters '<' and '>' which being used as HTML markup. Thanks for the reminding!