一、對象序列化
將堆內存中的對象存入硬盤,保留對象中的數據,稱之爲對象的持久化(或序列化)。
常用到的兩個類: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("關流失敗");
}
}
}
}
特點:
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類
1、RandomAccessFile此類的實例支持對隨機訪問文件的讀取和寫入,自身具備讀寫方法。
2、該類不算是IO體系中的子類,而是直接繼承Object,但是它是IO包成員,因爲它具備讀寫功能,內部封裝了一個數組,且通過getFilePointer方法獲取指針位置,來對數組的元素進行操作,同時可通過seek方法改變指針的位置。
3、可以完成讀寫的原理:內部封裝了字節輸入流和輸出流。
4、構造函數:RandomAccessFile(File file,String mode)
注意:1、如果模式爲只讀r,則不會創建文件,會去讀一個已存在的文件,若文件不存在,則會出現異常。
2、如果模式爲讀寫rw,且該對象的構造函數要操作的文件不存在,會自動創建;如果存在,則不會覆蓋。
特有方法:
1、seek(long pos)://調整對象中指針。來進行指定位置的數據讀取和寫入。數據要有規律。如果設置的指針位置已有數據,寫入時將會將其修改。用seek可以表示隨機讀寫訪問。
2、int 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、操作基本數據類型的流對象:DataInputStream和DataOutputStream
2、這兩個讀寫對象,可用於操作基本數據類型的流對象,包含讀寫各種基本數據類型的方法。
String readUTF();//對應writeUTF,讀取以UTF-8修改版編碼寫入的字符串
writeUTF(String str);//以與機器無關方式使用UTF-8修改版編碼將一個字符串寫入基礎輸出流。
五、操作數組與字符串的流
1、ByteArrayInputStream:在構造函數的時候,需要接受數據源,而且數據源是一個字節數據。
2、ByteArrayOutputStream:在構造函數的時候,不用定義數據目的,因爲該對象中已經在內部封裝了可變長度的字節數組,這就是數據的目的地。
ByteArrayOutputStream中特有方法:
writeTo(OutputStream out);//將此 byte 數組輸出流的全部內容寫入到指定的輸出流參數中
int size();//當前緩衝區的大小
String toString();//使用平臺默認的字符集,通過解碼字節將緩衝區內容轉換爲字符串
注意:1、這個對象並沒有調用底層資源,所以不用關閉流資源,即使關閉後,仍可調用。
2、內部包含緩衝區,相當於以內存作爲流操作源和目的,不會產生任何IO異常。
3、因爲writeTo(OutputStream out)這個方法用到了字節輸出流,需要拋IO異常,也是字節數組流中唯一需要拋異常的方法。
對應的字符數組和字符串
字符數組流:CharArrayReader和CharArrayWriter
字符串流: StringReader和StringWriter
//使用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、轉換流:InuputStreamReader和OutputStreamWriter
2、打印流:PrintStream和PrintWriter,只有輸出流
常見的編碼表:
1)ASCII:美國標準信息交換碼錶。用一個字節的7位表示
2)IOS8859-1:拉丁碼錶;歐洲碼錶。用一個字節的8位表示
3)GB2312:中國的中文編碼表()早期
4)GBK:中國的中文編碼表升級,融合了更多的中文文字字符。打頭的是兩個高位爲1的兩個字節編碼。爲負數
5)Unicode:國際標準碼,融合了多種文字。所有文字都用兩個字節來表示,Java語言使用的就是unicode。
6)UTF-8:最多用三個字節表示一個字符的編碼表,根據字符所佔內存空間不同,分別用一個、兩個、三個字節來編碼。
注意: 1、如果編碼失敗,解碼就沒意義了。
2、如果編碼成功,解碼出來的是亂碼,則需對亂碼通過再次編碼(用解錯碼的編碼表),然後再通過正確的編碼表解碼。針對於IOS8859-1是通用的。
3、如果用的是GBK編碼,UTF-8解碼,此時通過再次編碼後解碼的方式,就不能成功了,因爲UTF-8也支持中文,在UTF-8解的時候,會將對應的字節數改變,所以不會成功。