幾個檔案讀寫的工具類:文字檔讀寫、二進位檔案讀寫、對象讀寫。其中對象讀寫工具類有錯誤,在試圖進行多個對象讀取時,讀第二個對象就拋出異常,這是為什嗎?此外怎樣把一個存放對象的檔案中所有的對象讀出來?
這個問題已經解決,非常感謝Aguo的文章:自訂ObjectOutputStream,解決追加寫入後,讀取錯誤的問題 。在這篇文章中我找到了答案,同時對作者的原始碼添加了一些註解。解決方案請看文章最後。
1、文字檔讀寫工具類
package mine.util;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;/** * 此工具類用於文字檔的讀寫 * * @author Touch */public class TextFile {// 讀取指定路徑文字檔public static String read(String filePath) {StringBuilder str = new StringBuilder();BufferedReader in = null;try {in = new BufferedReader(new FileReader(filePath));String s;try {while ((s = in.readLine()) != null)str.append(s + '\n');} finally {in.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return str.toString();}// 寫入指定的文字檔,append為true表示追加,false表示重頭開始寫,//text是要寫入的文本字串,text為null時直接返回public static void write(String filePath, boolean append, String text) {if (text == null)return;try {BufferedWriter out = new BufferedWriter(new FileWriter(filePath,append));try {out.write(text);} finally {out.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
package mine.util;public class TestTextFile {public static void main(String[] args) {TextFile.write("file/textfile.txt", false,"這是一個文字檔的讀寫測試\nTouch\n劉海房\n男\n");TextFile.write("file/textfile.txt", true, "武漢工業學院\n軟體工程");System.out.println(TextFile.read("file/textfile.txt"));}}
2、二進位檔案讀寫工具類
package mine.util;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;/** * 此工具類用於二進位檔案的讀寫 * * @author Touch */public class BinaryFile {// 把二進位檔案讀入位元組數組,如果沒有內容,位元組數組為nullpublic static byte[] read(String filePath) {byte[] data = null;try {BufferedInputStream in = new BufferedInputStream(new FileInputStream(filePath));try {data = new byte[in.available()];in.read(data);} finally {in.close();}} catch (IOException e) {e.printStackTrace();}return data;}// 把位元組數組為寫入二進位檔案,數組為null時直接返回public static void write(String filePath, byte[] data) {if (data == null)return;try {BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filePath));try {out.write(data);} finally {out.close();}} catch (IOException e) {e.printStackTrace();}}}
package mine.util;import java.util.Arrays;public class TestBinaryFile {public static void main(String[] args) {BinaryFile.write("file/binaryfile.dat", new byte[] { 1, 2, 3, 4, 5, 6,7, 8, 9, 10, 'a', 'b', 'c' });byte[] data = BinaryFile.read("file/binaryfile.dat");System.out.println(Arrays.toString(data));}}
3、對象讀寫工具類(有問題,在讀取多個對象時有問題,怎樣把一個對象檔案中的所有對象讀出來?)
package mine.util;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;/** * 此類用於對象的讀寫 * * @author Touch */public class ObjectFile {// 把一個對象寫入檔案,isAppend為true表示追加方式寫,false表示重新寫public static void write(String filePath, Object o, boolean isAppend) {if (o == null)return;try {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filePath, isAppend));try {out.writeObject(o);} finally {out.close();}} catch (IOException e) {e.printStackTrace();}}// 把一個對象數組寫入檔案,isAppend為true表示追加方式寫,false表示重新寫public static void write(String filePath, Object[] objects, boolean isAppend) {if (objects == null)return;try {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filePath, isAppend));try {for (Object o : objects)out.writeObject(o);} finally {out.close();}} catch (IOException e) {e.printStackTrace();}}// 讀取對象,返回一個對象public static Object read(String filePath) {Object o = null;try {ObjectInputStream in = new ObjectInputStream(new FileInputStream(filePath));try {o = in.readObject();} finally {in.close();}} catch (Exception e) {e.printStackTrace();}return o;}// 讀取對象,返回一個對象數組,count表示要讀的對象的個數public static Object[] read(String filePath, int count) {Object[] objects = new Object[count];try {ObjectInputStream in = new ObjectInputStream(new FileInputStream(filePath));try {for (int i = 0; i < count; i++) {//第二次調用in.readObject()就拋出異常,這是為什嗎?objects[i] = in.readObject();}} finally {in.close();}} catch (Exception e) {e.printStackTrace();}return objects;}}
package mine.util;import java.io.Serializable;public class TestObjectFile {public static void main(String[] args) {ObjectFile.write("file/object1", new Person(), false);ObjectFile.write("file/object1", new Person(), true);ObjectFile.write("file/object1", new Person[] { new Person("Touch", 1),new Person("Rainbow", 0), new Person() }, true);for (Object o : ObjectFile.read("file/object1", 5))((Person) o).display();}}class Person implements Serializable {private String name = "劉海房";private int sex = 0;Person(String name, int sex) {this.name = name;this.sex = sex;}Person() {}void display() {System.out.println("my name is :" + name);String s = (sex == 0) ? "男" : "女";System.out.println("性別:" + s);}}
4、對象讀寫工具類(解決了3中的問題,能夠寫入及讀取多個對象)
3中到底問題出在哪呢?先來看一段ObjectOutputStream構造方法的原始碼,此原始碼來自jdk1.6版。
public ObjectOutputStream(OutputStream out) throws IOException {verifySubclass();bout = new BlockDataOutputStream(out);handles = new HandleTable(10, (float) 3.00);subs = new ReplaceTable(10, (float) 3.00);enableOverride = false;writeStreamHeader();bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack();} else { debugInfoStack = null; } }
這段代碼中我們只需要關注writeStreamHeader();方法,這個方法在原始碼中的解釋是
/** * The writeStreamHeader method is provided so subclasses can append or * prepend their own header to the stream. It writes the magic number and * version to the stream. * * @throwsIOException if I/O errors occur while writing to the underlying * stream */
也就是說我們開啟(new)一個ObjectOutputStream的時候,這個ObjectOutputStream流中就已經被寫入了一些資訊,這些資訊會寫入到我們的檔案中。在第一次寫入檔案時,這些頭部資訊時需要的,因為ObjectInputStream讀的時候會幫我們過濾掉。但是當我們追加寫入一個檔案時,這些頭部資訊也會寫入檔案中,讀取的時候只會把檔案第一次出現的頭部資訊過濾掉,並不會把檔案中間的頭部資訊也過濾掉,這就是問題的根源所在。
怎麼解決呢?正如lichong_87提到的
一、可以在每次寫入的時候把檔案中所有對象讀出來,然後重新寫入,這種方法效率比較低。
二、如果不是第一次寫入檔案,在寫入時去迴轉部資訊,怎麼去掉呢?頭部資訊是在writeStreamHeader();方法中寫入的,所以我們可以通過繼承ObjectOutputStream來覆蓋這個方法,如果不是第一次寫入檔案,這個方法什麼也不做。
下面是第二種解決方案的原始碼及測試
package mine.util.io;import java.io.File;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.OutputStream;/** * 此類繼承ObjectOutputStream,重寫writeStreamHeader()方法,以實現追加寫入時去迴轉部資訊 */public class MyObjectOutputStream extends ObjectOutputStream {private static File f;// writeStreamHeader()方法是在ObjectOutputStream的構造方法裡調用的// 由於覆蓋後的writeStreamHeader()方法用到了f。如果直接用此構造方法建立// 一個MyObjectOutputStream對象,那麼writeStreamHeader()中的f是null 指標// 因為f還沒有初始化。所以這裡採用單態模式private MyObjectOutputStream(OutputStream out, File f) throws IOException,SecurityException {super(out);}// 返回一個MyObjectOutputStream對象,這裡保證了new MyObjectOutputStream(out, f)// 之前f已經指向一個File對象public static MyObjectOutputStream newInstance(File file, OutputStream out)throws IOException {f = file;// 本方法最重要的地方:構建檔案對象,兩個引用指向同一個檔案對象return new MyObjectOutputStream(out, f);}@Overrideprotected void writeStreamHeader() throws IOException {// 檔案不存在或檔案為空白,此時是第一次寫入檔案,所以要把頭部資訊寫入。if (!f.exists() || (f.exists() && f.length() == 0)) {super.writeStreamHeader();} else {// 不需要做任何事情}}}
package mine.util.io;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;/** * 此類用於對象的讀寫 * * @author Touch */public class ObjectFile {// 把一個對象寫入檔案,isAppend為true表示追加方式寫,false表示重新寫public static void write(String filePath, Object o, boolean isAppend) {if (o == null)return;try {File f = new File(filePath);MyObjectOutputStream out = MyObjectOutputStream.newInstance(f,new FileOutputStream(f, isAppend));try {out.writeObject(o);} finally {out.close();}} catch (IOException e) {e.printStackTrace();}}// 把一個對象數組寫入檔案,isAppend為true表示追加方式寫,false表示重新寫public static void write(String filePath, Object[] objects, boolean isAppend) {if (objects == null)return;try {File f = new File(filePath);MyObjectOutputStream out = MyObjectOutputStream.newInstance(f,new FileOutputStream(f, isAppend));try {for (Object o : objects)out.writeObject(o);} finally {out.close();}} catch (IOException e) {e.printStackTrace();}}// 讀取對象,返回一個對象public static Object read(String filePath) {Object o = null;try {ObjectInputStream in = new ObjectInputStream(new FileInputStream(filePath));try {o = in.readObject();} finally {in.close();}} catch (Exception e) {e.printStackTrace();}return o;}// 讀取對象,返回一個對象數組,count表示要讀的對象的個數public static Object[] read(String filePath, int count) {Object[] objects = new Object[count];try {ObjectInputStream in = new ObjectInputStream(new FileInputStream(filePath));try {for (int i = 0; i < count; i++) {objects[i] = in.readObject();}} finally {in.close();}} catch (Exception e) {e.printStackTrace();}return objects;}}
package mine.util.io;import java.io.Serializable;public class TestObjectFile {public static void main(String[] args) {ObjectFile.write("file/t.dat", new Person(), false);ObjectFile.write("file/t.dat", new Person(), true);ObjectFile.write("file/t.dat", new Person[] { new Person("Touch", 1),new Person("Rainbow", 0), new Person() }, true);for (Object o : ObjectFile.read("file/t.dat", 5))((Person) o).display();}}class Person implements Serializable {private static final long serialVersionUID = 1L;private String name = "劉海房";private int sex = 0;Person(String name, int sex) {this.name = name;this.sex = sex;}Person() {}void display() {System.out.println("my name is :" + name);String s = (sex == 0) ? "男" : "女";System.out.println("性別:" + s);}}
運行結果:
my name is :劉海房
性別:男
my name is :劉海房
性別:男
my name is :Touch
性別:女
my name is :Rainbow
性別:男
my name is :劉海房
性別:男