對象的序列化(持久化)
就是爲了保存在內存中的各種對象的狀態,並且可以把保存的對象狀態再讀出來。
雖然你可以用你自己的各種各樣的方法來保存Object States,但是Java給我們提供了一種應該比自己更好的保存對象狀態的機制,那就是序列化。
簡單說:把對象轉換爲字節序列的過程稱爲對象的序列化。
把字節序列恢復爲對象的過程稱爲對象的反序列化。
操作對象
ObjectInputStream與ObjectOutputStream(兩個類要一起使用)
被操作的對象需要實現Serializable (標記接口)以啓用其序列化功能
該接口沒有方法;沒有方法的接口通常稱爲標記接口
writeObject();、readObject();操作對象
示例:通過序列化操作一個Person類
import java.io.*;
class ObjectStreamDemo
{
public static void main(String[] args) throws Exception
{
// writeObj();
readObj();
}
public static void readObj() throws Exception //反序列化
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();//類型強轉,引用了另一個。java文件的Person類,所以會拋異常ClassNotFoundException
System.out.println(p);
ois.close();
}
public static void writeObj() throws IOException //序列化
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("xiaoqiang",22,"America"));
oos.close();
}
}
Person類
被操作的對象
import java.io.*;
class Person implements Serializable //標記接口
{
public static final long serialVersionUID = 42L;//1,自定義UID
private String name;//1,私有化的成員
transient int age;//3,修飾符,不會被序列化
static String country ="cn";//2,靜態成員
Person(String name, int age, String country){
this.name = name;
this.age = age;
}
public String toString(){ //僞代碼,沒寫set,get
return name+" : "+age+" : "+counrty;
}
}
1,將Person的成員私有化後就不能再被序列化,說明序列號是根據成員獲取的
可以設置UID固定標識:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
設置後Person的成員即使被私有化也能被序列化
2,靜態的成員不能被私有化,例如靜態的成員country序列化修改後依然是cn
因爲序列化操作的是堆內存中的對象,而靜態成員隨着類加載,存在於棧中的方法區,所以執行不到
3,如果想讓非靜態成員不被序列化,就在該成員前面加上transient修飾
管道流
PipedInputStream和PipedOutputStream
一般流的輸入輸出沒有太大關聯,而管道流輸入輸出可以直接進行連接,必須相互連接後創建通信管道;通過結合多線程使用,單線程可能會死鎖。
示例:
import java.io.*;
class Read implements Runnable //管道輸入流線程
{
private PipedInputStream in;
Read(PipedInputStream in){ //構造函數
this.in = in;
}
public void run(){ //實現run方法
try{
byte[] buf = new byte[1024];//緩衝數組
System.out.println("阻塞...數據讀取中...");
int len = in.read(buf); //字節輸入流讀取方法
System.out.println("讀到數據,阻塞結束");
String s = new String(buf,0,len);//轉成字符串
System.out.println(s);
in.close();
}
catch (IOException e){
throw new RuntimeException("管道流read讀取失敗!!!");
}
}
}
class Write implements Runnable //管道輸出流線程
{
private PipedOutputStream out;
Write(PipedOutputStream out){ //構造函數
this.out = out;
}
public void run(){ //覆蓋run方法
try{
System.out.println("開始寫入...等待中。。。");
Thread.sleep(3000);
out.write("hello Piped !".getBytes());//字節讀取流write方法,需要將字符轉爲字節
out.close();
}
catch (Exception e){
throw new RuntimeException("管道流write輸出失敗");
}
}
}
class PipedStreamDemo
{
public static void main(String[] args) throws IOException
{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out=new PipedOutputStream(in);
// in.connect(out);//connect()創建通信管道,也可直接將in傳入out構造參數列表
new Thread(new Read(in)).start();
new Thread(new Write(out)).start();
}
}
運行結果:
由運行結果可以看出:管道流輸入輸出兩個線程在執行的過程中,輸入流線程如果沒有讀到輸出流的數據,就會一直處於阻塞狀態,直到輸出流write完畢輸入流纔開始read。
如果是單線程,極有可能會線程死鎖,所以要和多線程配合使用。
RandomAccessFile
隨機讀寫文件該類不算是IO體系的子類,直接繼承自Object;
但RandomAccessFile是IO包中成員,具備文件讀寫功能,能夠讀寫是其內部封裝了字節輸入流和輸出流
能夠隨機是因爲在內部封裝了一個byte[] 數組,通過指針對數組的元素進行操作,同時可以通過getFilePointer獲取指針位置,通過seek改變指針位置。seek相當強大
通過其構造函數可以看出:該類只能操作文件
而且還有操作文件的模式:只讀r,讀寫rw 等
如果模式爲r,不會創建文件,而是讀取一個已存在的文件,若該文件不存在會拋出異常
如果模式爲rw,操作的文件不存在,會自動創建,如果存在則不會覆蓋,而是直接在該文件上操作
示例:
import java.io.*;
class RandomAccessFileDemo
{
public static void main(String[] args) throws IOException
{
// writeFile(1);
// readFile(0);
writeFile_2(1);
}
public static void readFile(int x) throws IOException //隨機獲取
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","r");//"r"只讀權限
// raf.seek(8*x); //調整對象中指針,一個人的信息爲8個字節,可以通過8的倍數來決定取哪個人的資料
raf.skipBytes(8*x);//跳過指定的字節數,只能往後跳
byte[] buf = new byte[4];
raf.read(buf); //一次讀取四個字節,因爲設置的名字是由2個字符(4個字節)組成
String name = new String(buf);//將該字節數組轉換成字符串
int age = raf.readInt();//直接一次讀取四個字節,並轉成int型
sop("name: "+name);
sop("age: "+age);
raf.close();
}
public static void writeFile_2(int x)throws IOException //隨機寫入
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
raf.seek(8*x); //調整指針,直接將信息插入到第五個信息欄,也可對指定位置信息修改(覆蓋原有信息)
raf.write("王五".getBytes());
raf.writeInt(110);
raf.close();
}
public static void writeFile() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");//"rw"權限,讀寫
raf.write("小強".getBytes());//將字符轉成字節
raf.writeInt(98); //write只寫int型的最低八位(一個字節),該類提供了writeInt()方法,寫入4個字節
raf.write("小明".getBytes());
raf.writeInt(99);
raf.close();
}
public static void sop(Object obj){
System.out.println(obj);
}
}
RandomAccessFile類具備強大的隨機讀寫功能,可以對數據進行分段錄入,如果引入多線程技術,可以大大提高效率,實際應用:P2P多線程下載
該類要做重點掌握!
一、專門操作基本數據類型的流對象
DataInputStream與DataOutputStream
import java.io.*;
class DataStreamDemo
{
public static void main(String[] args) throws IOException
{
// writeData();
// readData();
// writeUTFDemo();
readUTFDemo();
}
public static void readUTFDemo() throws IOException{
DataInputStream dis = new DataInputStream(new FileInputStream("utfData.txt"));
String s = dis.readUTF();
sop(s);
dis.close();
}
public static void writeUTFDemo() throws IOException{ //以 UTF-8 修改版格式寫入此 String 的基本數據。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfData.txt"));
dos.writeUTF("小強");
dos.close();
}
public static void readData() throws IOException{ //讀取基本數據類型
DataInputStream dis = new DataInputStream(new FileInputStream("Data.txt"));
int n = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
sop("n= "+n);
sop("b= "+b);
sop("d= "+d);
dis.close();
}
public static void writeData() throws IOException{ //寫入基本數據類型
DataOutputStream dos = new DataOutputStream(new FileOutputStream("Data.txt"));
dos.writeInt(22); //4個字節
dos.writeBoolean(true); //1個字節
dos.writeDouble(3.1415926535);//8個字節
dos.close();
}
public static void sop(Object obj){
System.out.println(obj);
}
}
二、用於操作字節數組的流對象
用流的讀寫思想操作數組ByteArrayInputStream:在構造時需要接受數據源,數據源是一個字節數組
ByteArrayOutputStream:在構造時不用定義數據目的,因爲該對象內部封裝了可變長度的字節數組,該數組就是數據目的地。
因爲這兩個流對象都操作數組,並沒有操作系統資源,所以不用close()關閉
流操作規律
源: 鍵盤System.in、硬盤FileStream、內存ArrayStream
目的: 控制檯System.our、硬盤FileStream、內存ArrayStream
writeTo(OutputStream out);
示例:
import java.io.*;
class ByteArrayStream
{
public static void main(String[] args) //操作的是數組,一般不用拋異常
{
ByteArrayInputStream bais = new ByteArrayInputStream("ABCDEFG".getBytes());//數據源
ByteArrayOutputStream baos =new ByteArrayOutputStream(); //數據目的
int by = 0;
while ((by=bais.read()) !=-1){
baos.write(by);
}
System.out.println(baos.size()); //返回緩衝區大小length長度
System.out.println(baos.toString()); //將緩衝區字節
// baos.writeTo(new FileOutputStream("a.txt")); //該方法調用了底層資源,會拋異常
}
三、操作字符數組
CharArrayReader與CharArrayWrite
四、操作字符串
StringReader 與 StringWriter
這兩個類與ByteArrayStream類似