黑馬程序員_JAVA_IO(三)

------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------


一、對象序列化        

       將堆內存中的對象存入硬盤,保留對象中的數據,稱之爲對象的持久化(或序列化)。

常用到的兩個類:ObjectInputStream和ObjectOutputStream。
被操作的對象需要實現Serializable接口(也稱標記接口)
 
關於Serializable接口:
        1、接口Serializable中沒有方法,稱之爲標記接口
        2、序列化運行時使用一個稱爲 serialVersionUID的版本號與每個可序列化類相關聯,用於驗證序列化對象的發送者和接收者是否爲同一對象。如果接收者加載的該對象的類的 serialVersionUID與對應的發送者的類的版本號不同,則反序列化將會導致 InvalidClassException。
             public static final long serialVersionUID = 42L;

注意:1、靜態成員不能被序列化,因爲靜態在方法區,序列化只能對堆內的對象進行序列化。

             2、非靜態成員要不被序列化,可以用關鍵字transient修飾,保證非靜態成員保存在堆內存中,不能存入文件中。

import java.io.*;
class Peason implements Serializable {
	// 自定義serialVersionUID版本號
	public final static long serialVersionUID = 32L;

	private String name;
	transient int age;// 使用transient關鍵字表示該成員不被序列化
	static String country = "CN";// 靜態成員不能序列化

	// 構造函數
	Peason(String name, int age) {
		this.name = name;
		this.age = age;
	}

	// 複寫toString方法
	public String toString() {
		return name + ":" + age + ":" + country;
	}
}

// 序列化測試類
class ObjectStreamDemo {
	public static void main(String[] args) {
		// 指定文件
		File file = new File("obj.txt");
		Peason p = new Peason("zhangsan", 25);

		// 序列化指定對象
		writeObj(p, file);
		// 反序列化
		readObj(file);
	}

	// 將指定對象序列化到指定文件中
	public static void writeObj(Peason p, File file) {
		ObjectOutputStream oos = null;
		try {
			// 創建寫入流對象,關流文件
			oos = new ObjectOutputStream(new FileOutputStream(file));
			oos.writeObject(p);// 寫入對象數據
		} catch (IOException e) {
			throw new RuntimeException("對象寫入失敗");
		} finally {
			try {
				if (oos != null)
					oos.close();
			} catch (IOException e) {
				throw new RuntimeException("關流失敗");
			}
		}
	}

	// 讀取指定文件中的對象,也稱反序列化
	public static void readObj(File file) {
		ObjectInputStream ois = null;
		try {
			// 創建讀取流對象,關聯文件
			ois = new ObjectInputStream(new FileInputStream(file));
			// 讀取文件中的對象
			Peason p = (Peason) ois.readObject();
			System.out.println(p);
		} catch (Exception e) {
			throw new RuntimeException("文件讀取對象失敗");
		} finally {
			try {
				if (ois != null)
					ois.close();
			} catch (IOException e) {
				throw new RuntimeException("關流失敗");
			}
		}
	}
}


二、管道流( PipedInputStream和PipedOutputStream )
特點:
         1、輸入輸出可以直接進行連接,不用再借助數組或集合等容器進行臨時存儲。     
         2、一般結合多線程使用。通常,數據由某個線程寫入PipedOutputStream對象,並由其他線程從連接的 PipedInputStream 讀取。
 
操作步驟:
        1、要先創建一個讀和寫的兩個類,實現Runnable接口,因爲是兩個不同的線程,覆蓋run方法,注意,需要在內部處理異常。
        2、創建兩個管道流,並用connect()方法將兩個流連接
        3、創建讀寫對象,並傳入兩個線程內,並start執行。
import java.io.*;
class PipedStreamDemo {
	public static void main(String[] args) {
		try {
			// 創建管道流對象,並關聯
			PipedInputStream in = new PipedInputStream();
			PipedOutputStream out = new PipedOutputStream();
			in.connect(out);

			// 啓動線程
			new Thread(new Read(in)).start();
			new Thread(new Write(out)).start();
		} catch (IOException e) {
			throw new RuntimeException("管道流關聯失敗");
		}
	}
}

// 讀取線程
class Read implements Runnable {
	private PipedInputStream in;

	Read(PipedInputStream in) {
		this.in = in;
	}

	// 覆蓋run方法
	public void run() {
		try {
			// 用來存儲讀到的字節
			byte[] by = new byte[1024];
			System.out.println("讀取前。。沒有數據阻塞");
			// 讀取流中數據
			int len = in.read(by);
			System.out.println("讀到數據。。阻塞結束");

			// 將字節數組轉換爲字符串打印輸出
			String s = new String(by, 0, len);

			System.out.println(s);
		} catch (IOException e) {
			throw new RuntimeException("讀取數據失敗");
		} finally {
			try {
				if (in != null)
					in.close();
			} catch (IOException e) {
				throw new RuntimeException("流關閉失敗");
			}
		}
	}
}

// 寫線程
class Write implements Runnable {
	private PipedOutputStream out;

	Write(PipedOutputStream out) {
		this.out = out;
	}

	// 覆蓋run方法
	public void run() {
		try {
			System.out.println("開始寫入數據,等待3秒後。");
			Thread.sleep(3000);
			// 寫入數據到管道流中
			out.write("piped shi shem ma?".getBytes());
		} catch (Exception e) {
			throw new RuntimeException("寫入數據失敗");
		} finally {
			try {
				if (out != null)
					out.close();
			} catch (IOException e) {
				throw new RuntimeException("流關閉失敗");
			}
		}
	}
}


三、RandomAccessFile

        1RandomAccessFile此類的實例支持對隨機訪問文件的讀取和寫入,自身具備讀寫方法。

        2、該類不算是IO體系中的子類,而是直接繼承Object,但是它是IO包成員,因爲它具備讀寫功能,內部封裝了一個數組,且通過getFilePointer方法獲取指針位置,來對數組的元素進行操作,同時可通過seek方法改變指針的位置。

        3、可以完成讀寫的原理:內部封裝了字節輸入流和輸出流。

        4、構造函數:RandomAccessFile(File file,String mode)


注意:1、如果模式爲只讀r,則不會創建文件,會去讀一個已存在的文件,若文件不存在,則會出現異常。

    2、如果模式爲讀寫rw,且該對象的構造函數要操作的文件不存在,會自動創建;如果存在,則不會覆蓋。

 

特有方法:

        1seek(long pos)//調整對象中指針。來進行指定位置的數據讀取和寫入。數據要有規律。如果設置的指針位置已有數據,寫入時將會將其修改。用seek可以表示隨機讀寫訪問。

        2int skipBytes(int n):跳過指定字節數,不可往前跳

 RandomAccessFile中也有對基本數據類型進行讀寫的方法。還有readLine方法。

//需求:使用RandomAccessFileDemo進行讀寫操作
import java.io.*;
public class RandomAccessFileDemo {
	public static void main(String[] args) throws IOException {
		// 指定文件
		File file = new File("1.txt");
		// 寫數據
		writeFile(file);
		// 讀數據
		readFile(file);
	}

	// 讀取指定文件中的數據
	public static void readFile(File file) throws IOException {
		// 創建對象
		RandomAccessFile raf = new RandomAccessFile(file, "r");
		// 設置指針位置
		raf.seek(4);

		// 設置跳過的字節數
		// raf.skipBytes(8);

		// 讀取四個字節存入
		byte[] by = new byte[4];
		// 讀數據
		raf.read(by);
		// 將存入數據的字節數組轉換爲字符串
		String str = new String(by);
		raf.close();// 關流
		System.out.println("str=" + str);
	}

	// 將數據寫入指定文件中
	public static void writeFile(File file) throws IOException {
		// 創建對象
		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		raf.write("abcdefghigk".getBytes());
		raf.close();// 關流
	}
}


四、操作基本數據類型的流對象( DataStream )

        1、操作基本數據類型的流對象:DataInputStreamDataOutputStream

        2、這兩個讀寫對象,可用於操作基本數據類型的流對象,包含讀寫各種基本數據類型的方法。

 

String readUTF();//對應writeUTF,讀取以UTF-8修改版編碼寫入的字符串

writeUTF(String str);//以與機器無關方式使用UTF-8修改版編碼將一個字符串寫入基礎輸出流。


五、操作數組與字符串的流

        1ByteArrayInputStream:在構造函數的時候,需要接受數據源,而且數據源是一個字節數據。

        2ByteArrayOutputStream:在構造函數的時候,不用定義數據目的,因爲該對象中已經在內部封裝了可變長度的字節數組,這就是數據的目的地。


ByteArrayOutputStream特有方法

        writeTo(OutputStream out);//將此 byte 數組輸出流的全部內容寫入到指定的輸出流參數中

        int size();//當前緩衝區的大小

        String toString();//使用平臺默認的字符集,通過解碼字節將緩衝區內容轉換爲字符串


 注意:1、這個對象並沒有調用底層資源,所以不用關閉流資源,即使關閉後,仍可調用。 

     2、內部包含緩衝區,相當於以內存作爲流操作源和目的,不會產生任何IO異常。 

     3、因爲writeTo(OutputStream out)這個方法用到了字節輸出流,需要拋IO異常,也是字節數組流中唯一需要拋異常的方法。

對應的字符數組和字符串

        字符數組流:CharArrayReaderCharArrayWriter

        字符串流:   StringReaderStringWriter

//使用IO中操作字節數組的流對象讀寫數據
import java.io.*;
class ByteArrayStreamDemo {
	public static void main(String[] args) {
		// 創建一個byte數組輸入流,數據源
		ByteArrayInputStream bais = new ByteArrayInputStream(
				"ABCDEFG".getBytes());

		// 創建一個新的byte數組輸出流,數據目的
		ByteArrayOutputStream baos = new ByteArrayOutputStream();

		int by = 0;
		while ((by = bais.read()) != -1) {
			// 寫入數據
			baos.write(by);
		}

		// 輸出數據長度
		System.out.println(baos.size());

		// 以字符串輸出數據
		System.out.println(baos.toString());

		try {
			// 調用此方法時,需要拋出異常
			baos.writeTo(new FileOutputStream("writeTo.txt"));
		} catch (IOException e) {
			throw new RuntimeException("數據寫入文件失敗");
		}
	}
}

六、字符編碼

1、字符流的出現爲了方便操作字符。

2、更重要的是加入了編碼的轉換,即轉換流。

3、通過子類轉換流來完成。在兩個對象進行構造的時候,可以加入字符集(即編碼表),可傳入編碼表的有:

        1、轉換流:InuputStreamReaderOutputStreamWriter

        2、打印流:PrintStreamPrintWriter,只有輸出流

常見的編碼表:

        1ASCII:美國標準信息交換碼錶。用一個字節的7位表示

        2IOS8859-1:拉丁碼錶;歐洲碼錶。用一個字節的8位表示

        3GB2312:中國的中文編碼表()早期

        4GBK:中國的中文編碼表升級,融合了更多的中文文字字符。打頭的是兩個高位爲1的兩個字節編碼。爲負數

        5Unicode:國際標準碼,融合了多種文字。所有文字都用兩個字節來表示,Java語言使用的就是unicode

        6UTF-8:最多用三個字節表示一個字符的編碼表,根據字符所佔內存空間不同,分別用一個、兩個、三個字節來編碼。


注意: 1、如果編碼失敗,解碼就沒意義了。

              2、如果編碼成功,解碼出來的是亂碼,則需對亂碼通過再次編碼(用解錯碼的編碼表),然後再通過正確的編碼表解碼。針對於IOS8859-1是通用的。

              3、如果用的是GBK編碼,UTF-8解碼,此時通過再次編碼後解碼的方式,就不能成功了,因爲UTF-8也支持中文,在UTF-8解的時候,會將對應的字節數改變,所以不會成功。

發佈了46 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章