IO
IO流是輸入和輸出的接觸。
分類
1 輸入流惨远,輸出流
輸入流:只能讀數(shù)據(jù)(**--->內(nèi)存),不能寫數(shù)據(jù)
輸出流:只能寫數(shù)據(jù)(內(nèi)存 ---> 磁盤),不能讀數(shù)據(jù)
劃分原則:程序運(yùn)行時(shí)所在的內(nèi)存
2 字節(jié)流舷丹,字符流
字節(jié)流和字符流的操作基本一致,唯一的區(qū)別是操作的數(shù)據(jù)單元不同蜓肆。
字節(jié)流——8位字節(jié)
字符流——16位字符
3 節(jié)點(diǎn)流颜凯,處理流
節(jié)點(diǎn)流:低級(jí)流
處理流:高級(jí)流,包裝流
4 InputStream 和 Reader
InputStream 和 Reader 是所有輸入流的抽象基類症杏,本身不能常見實(shí)例來執(zhí)行輸入,但他們是所有輸入流的模板
- InputStream
int read() 從輸入流中獲取單個(gè)字節(jié)瑞信,返回所取得的字節(jié)數(shù)
int read(byte[] b) 從輸入流中最多取讀b.length個(gè)字節(jié)數(shù)據(jù)厉颤,并將其存儲(chǔ)在字節(jié)數(shù)組b中,返回實(shí)際取讀的字節(jié)數(shù)
int read(byte[] b,int off,int len) 從輸入流中最多取讀len個(gè)字節(jié)的數(shù)據(jù)凡简,并將其存儲(chǔ)在字節(jié)數(shù)組b中逼友,
放入字節(jié)數(shù)組b中時(shí)精肃,并不是從數(shù)組起點(diǎn)開始,而是從off位置開始帜乞,返回實(shí)際取讀的字節(jié)數(shù) - Reader
int read(): 取讀單個(gè)字符
int read(char[] cbuf) 從輸入流中最多取讀cbuf.length個(gè)字符司抱,并將其存儲(chǔ)在字符數(shù)組cbuf中,返回實(shí)際讀取的字符數(shù)
int read(char[] cbuf,int off,int len) 從輸入流中最多取讀len個(gè)字符的數(shù)據(jù)黎烈,并將其存儲(chǔ)在字符數(shù)組b中习柠,
放入字符數(shù)組b中時(shí),并不是從數(shù)組起點(diǎn)開始照棋,而是從off位置開始资溃,返回實(shí)際取讀的字符數(shù)
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
byte[] bbuf = new byte[1024];
int hasRead = 0;
while ((hasRead = fis.read(bbuf)) > 0) {
System.out.println(new String(bbuf, 0, hasRead));
}
fis.close();
}
}
public class FileReaderTest {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("FileReaderTest.java");
char[] bbuf = new char[32];
int hasRead = 0;
while ((hasRead = fr.read(bbuf)) > 0) {
System.out.print(new String(bbuf, 0, hasRead));
}
fr.close();
}
}
5 OutputStream 和 Writer
- void write(int c) 將制定的字節(jié)、字符數(shù)出道輸出流中
void write(byte[]/char[] buf) 將字節(jié)烈炭、字符數(shù)組輸出到指定的輸出流中
void write(byte[]/char[] buf,int off,int len) 將字節(jié)溶锭、字符數(shù)組從off位置開始井辆,長度為len的字節(jié)
惜纸、字符輸出到輸出流中。
Writer中還可以使用字符串代替字符數(shù)組
- void write(String s) 將str中包含的字符輸出到指定的輸出流中
- void write(String str,int off,int len) 將str字符串中從off位置開始卡辰,長度為len的字符輸出到指定輸出流中
public class FileOutPutStreamTest {
public static void main(String[] args) {
try (
FileInputStream fis =
new FileInputStream("FileOutPutStreamTest.java");
FileOutputStream fos = new FileOutputStream("newFile.txt")) {
byte[] bbuf = new byte[32];
int hasRead = 0;
while ((hasRead = fis.read(bbuf)) > 0) {
fos.write(bbuf, 0, hasRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
```java
public class FileWriterTest {
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("pome.txt")) {
fw.write("錦瑟-李商隱\r\n");
fw.write("錦瑟無端五十弦霹疫,一弦一柱思華年\r\n");
fw.write("莊生曉夢迷蝴蝶拱绑,望帝春心托杜鵑\r\n");
fw.write("滄海月明珠有淚,藍(lán)田日暖玉生煙\r\n");
fw.write("此情可待成追憶更米,只是當(dāng)時(shí)已惘然\r\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
輸出:
錦瑟-李商隱
錦瑟無端五十弦欺栗,一弦一柱思華年
莊生曉夢迷蝴蝶,望帝春心托杜鵑
滄海月明珠有淚征峦,藍(lán)田日暖玉生煙
此情可待成追憶迟几,只是當(dāng)時(shí)已惘然
6 處理流
處理流可以隱藏底層設(shè)備上節(jié)點(diǎn)流的差異,對(duì)外提供更加方便的輸入栏笆,輸出方法
識(shí)別處理流:只要文件流的構(gòu)造器參數(shù)不是一個(gè)物理節(jié)點(diǎn)而是一個(gè)已存在的流类腮,那么這個(gè)流一定是處理流。
所有的子節(jié)點(diǎn)都是直接以物理IO最為構(gòu)造器參數(shù)的蛉加。
public class PrintStreamTest {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos)) {
ps.println("普通字符串");
ps.println(new PrintStreamTest());
} catch (IOException e) {
e.printStackTrace();
}
}
}
在使用處理劉包裝了底層節(jié)點(diǎn)流后蚜枢,關(guān)閉資源時(shí),可以之關(guān)閉最上層的處理流即可
分類 | 字節(jié)輸入流 | 字節(jié)輸出流 | 字符輸入流 | 字符輸出流 |
---|---|---|---|---|
抽象基類 | InputStream | OutputStream | Reader | Writer |
訪問文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
訪問數(shù)組 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
訪問管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
訪問字符串 | StringReader | StringWriter | ||
緩沖流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
轉(zhuǎn)換流 | InputStreamReader | OutputStreamWriter | ||
對(duì)象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基類 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回輸入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
序列化
序列化的目標(biāo)是將對(duì)象保存到磁盤中针饥,或允許在網(wǎng)絡(luò)中傳輸對(duì)象厂抽。
對(duì)象序列化機(jī)制允許把內(nèi)容中java對(duì)象轉(zhuǎn)換成平臺(tái)無關(guān)的二進(jìn)制流,從而允許把這種二進(jìn)制流持久的保留在磁盤上
通過網(wǎng)絡(luò)將這種二進(jìn)制流傳輸?shù)搅硪粋€(gè)網(wǎng)絡(luò)節(jié)點(diǎn)丁眼。其他程序一旦獲得了這個(gè)二進(jìn)制流(無論是從磁盤還是網(wǎng)絡(luò))筷凤,
都可以講這種二進(jìn)制流恢復(fù)到原來的java文件。
序列化機(jī)制是的對(duì)象可以脫離程序的運(yùn)行而獨(dú)立存在。
對(duì)象的序列化(Serialize) 指將一個(gè)Java 對(duì)象寫入IO流中藐守,與此對(duì)應(yīng)的是挪丢,對(duì)象的反序列化(Deserialize)
則指從IO流中恢復(fù)該java對(duì)象。
序列化對(duì)象
- 創(chuàng)建一個(gè)ObjectOutputStream卢厂,這個(gè)輸出是一個(gè)處理流乾蓬,所以必須建立才其他節(jié)點(diǎn)流的基礎(chǔ)上
ObjectOutputStream oos = new OjectOutputStream(new FileOutputStream("XXX"));
- 調(diào)用ObjectOuputStream對(duì)象的writeObject()方法即可輸出徐硫化對(duì)象
oos.writeObject(***);
樣例
public class WriteObject {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.txt"))) {
Person person = new Person("孫悟空", 5000);
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
}
}
輸出文件中的內(nèi)容
?? ?sr ?com.huawei.kemuer.Person?#??JR`Z? ?I ?ageL ?namet ?Ljava/lang/String;xp ??t 孫悟空
反序列化步驟
- 創(chuàng)建一個(gè)ObjectInputStream 輸入流,處理流
ObjectInputStram ois = new ObjectInputStream(new FileInputStream(***));
- 調(diào)用ObjectInputStream對(duì)象的readObject()方法取讀流中的對(duì)象慎恒,該方法返回一個(gè)Object類型的java對(duì)象任内,可以強(qiáng)轉(zhuǎn)
該對(duì)象的真實(shí)類型
*** p = (***)ois.readObject();
public class ReadObject {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));) {
Person p = (Person) ois.readObject();
System.out.println("名字:" + p.getName() + " 年齡:" + p.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出:
名字:孫悟空 年齡:5000
當(dāng)反序列化取讀java對(duì)象,無需通過構(gòu)造器來初始化對(duì)象
對(duì)象引用序列化
如果某個(gè)類沒有序列化巧号,那么擁有這個(gè)類作為成員變臉的類也是不能序列化的
public class WriteTeacher {
public static void main(String[] args) {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Teacher.txt"))) {
Person p = new Person("孫悟空", 5000);
Teacher t1 = new Teacher("唐僧", p);
Teacher t2 = new Teacher("菩提老祖", p);
out.writeObject(p);
out.writeObject(t1);
out.writeObject(t2);
out.writeObject(t1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
多次序列化同一個(gè)對(duì)象時(shí)族奢,只有第一次序列化時(shí),才會(huì)把該java對(duì)象轉(zhuǎn)換成字節(jié)序序列并輸出丹鸿。
當(dāng)序列化一個(gè)可變對(duì)象時(shí)越走,只有第一次使用writeObject()方法輸出是才會(huì)將該對(duì)象轉(zhuǎn)換成字節(jié)序列并輸出
當(dāng)程序再次調(diào)用wirteObject()時(shí),程序只是輸出前面的序列化編號(hào)靠欢,及時(shí)后面該對(duì)象的實(shí)例變量值已經(jīng)被改變
改變的實(shí)例變量值也不會(huì)被輸出廊敌。
public class SerializeMutable {
public static void main(String[] args) {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("mutable.txt"));
ObjectInputStream in = new ObjectInputStream(new FileInputStream("mutable.txt"))) {
Person p = new Person("孫悟空", 5000);
out.writeObject(p);
p.setName("豬八戒");
out.writeObject(p);
Person p1 = (Person) in.readObject();
Person p2 = (Person) in.readObject();
System.out.println(p1 == p2);
System.out.println(p2.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出:
有參數(shù)構(gòu)造器
true
孫悟空
自定義序列化
敏感信息不可以序列化
在敏感信息前加transient關(guān)鍵字,保證字段不被序列化
使用transient關(guān)鍵字修飾變量雖然簡單门怪、方便骡澈,但被transient修飾的實(shí)例變量江北完全隔離在序列化機(jī)制之外
這樣導(dǎo)致在反序列化恢復(fù)java對(duì)象時(shí),無法取得該實(shí)例變量值掷空。
NIO
為了解決傳統(tǒng)IO效率不高的問題而出現(xiàn)的新模式的IO
新IO的主要目的也是用于輸入/輸出肋殴,但新IO使用了不同的方式來處理輸入/輸出,新IO采用內(nèi)存映射文件的方式來處理
輸入/輸出坦弟。新IO將文件或文件的一段區(qū)域映射到內(nèi)存中护锤,這樣就可以像訪問內(nèi)存一樣來訪問文件了(模擬操作系統(tǒng)的虛擬內(nèi)存)
,通過這種方式來進(jìn)行輸入/輸出比傳統(tǒng)的輸入/輸出要快的多酿傍。
java中與新IO有關(guān)的包
- java.nio: 主要包含各種與buffer相關(guān)的類
- java.nio.channels:主要包含于Channel和Selector相關(guān)的類
- java.nio.charset: 主要包含與字符集相關(guān)的類
- java.nio.channels.spi:主要包含與Channel相關(guān)服務(wù)提供者接口編程
- java.nio.charset.spi:主要包含與字符集相關(guān)的服務(wù)提供者編程接口烙懦。
Channel(通道) 和 Buffer(緩沖)是新IO中兩個(gè)核心對(duì)象,Channel是對(duì)傳統(tǒng)輸入/輸出系統(tǒng)的模擬赤炒,在新IO
系統(tǒng)中所有的數(shù)據(jù)都需要通過通道傳輸氯析。Channel與傳統(tǒng)的InputStream和OutputStream最大的區(qū)別就是他提供了一個(gè)
map()方法,通過該map()方法可以直接將“一塊數(shù)據(jù)”映射到內(nèi)存中莺褒。如果說傳統(tǒng)的輸入/輸出系統(tǒng)是面向流的處理掩缓,則新IO
則是面向塊的處理
Buffer
Buffer可以理解成一個(gè)容器,他本質(zhì)是一個(gè)數(shù)組遵岩,發(fā)送到channel中的所有對(duì)形象都必須放到Buffer中你辣,而Channel中讀取的
數(shù)據(jù)也要先放到Buffer中,此處的Buffer有點(diǎn)類似于竹筒。
Buffer是個(gè)抽象類
最常用的子類是ByteBuffer绢记。
Buffer類沒有構(gòu)造器,通過下面的方法來獲取一個(gè)Buffer對(duì)象
- static XXXBuffer allocate(int capacity):創(chuàng)建一個(gè)容量為capacity的XxxBuffer對(duì)象
在Buffer中有三個(gè)比較重要的概念:
容量(Capacity):緩沖區(qū)的容量(capacity)表示改Buffer的最大數(shù)據(jù)容量正卧,最多可以存儲(chǔ)多少數(shù)據(jù)蠢熄。
不能為負(fù)值,創(chuàng)建以后不能改變界限(limit):第一個(gè)不應(yīng)該被讀出或者寫入緩存區(qū)位置索引炉旷,也就是說位于limit后的數(shù)據(jù)及不可以被讀签孔,也不可以被寫
位置(position):用于志明下一個(gè)可以被讀出的或者寫入的緩存區(qū)位置索引(類似于IO流中的記錄指針)
0 <= mark <= position <= limit <= capacity
Buffer的主要作用是裝入數(shù)據(jù)然后輸出數(shù)據(jù)
最開始數(shù)據(jù)的limit為capacity,position為0窘行,裝入數(shù)據(jù)后饥追,position向后移動(dòng)一些位置
- flip():從Buffer中取出數(shù)據(jù)做好準(zhǔn)備
limit設(shè)置為position所在的位置,并將position設(shè)置為0 - clear():再次向Buffer中裝入數(shù)據(jù)
position設(shè)置為0罐盔,將limit設(shè)置為capacity
public class BufferTest {
public static void main(String[] args) {
// 創(chuàng)建Buffer
CharBuffer buffer = CharBuffer.allocate(8);
System.out.println("capacity: " + buffer.capacity());
System.out.println("limit: " + buffer.limit());
System.out.println("position: " + buffer.position());
buffer.put('a');
buffer.put('b');
buffer.put('c');
System.out.println("加入三個(gè)元素后但绕,position: " + buffer.position());
buffer.flip();
System.out.println("執(zhí)行flit()后,limit: " + buffer.limit());
System.out.println("position: " + buffer.position());
System.out.println("第一個(gè)元素(position=0):" + buffer.get());
System.out.println("取出一個(gè)元素后惶看,position: " + buffer.position());
buffer.clear();
System.out.println("執(zhí)行clear()后捏顺,limit: " + buffer.limit());
System.out.println("執(zhí)行clear()后,position: " + buffer.position());
System.out.println("執(zhí)行clear()后纬黎,緩存區(qū)內(nèi)容并沒有被清除幅骄,第三個(gè)元素為" + buffer.get(2));
System.out.println("執(zhí)行絕對(duì)取讀后,position:" + buffer.position());
}
}
輸出結(jié)果
capacity: 8
limit: 8
position: 0
加入三個(gè)元素后本今,position: 3
執(zhí)行flit()后拆座,limit: 3
position: 0
第一個(gè)元素(position=0):a
取出一個(gè)元素后,position: 1
執(zhí)行clear()后冠息,limit: 8
執(zhí)行clear()后挪凑,position: 0
執(zhí)行clear()后,緩存區(qū)內(nèi)容并沒有被清除铐达,第三個(gè)元素為c
執(zhí)行絕對(duì)取讀后岖赋,position:0
Channel
channel類似于傳統(tǒng)流對(duì)象,但與傳統(tǒng)流對(duì)形象有兩個(gè)主要的區(qū)別瓮孙。
- Channel可以直接將制定文件的部分或全部直接映射成Buffer
- 程序補(bǔ)鞥呢直接訪問Channel中的數(shù)據(jù)唐断,包括取讀,寫入都不行杭抠,Channel只能與Buffer進(jìn)行交互脸甘。
要從Channel中讀取數(shù)據(jù),先用Buffer從Channel中取出一些數(shù)據(jù)偏灿,然后讓程序從Buffer中取出這些數(shù)據(jù)丹诀;
如果要將程序中的數(shù)據(jù)寫入Channel,一樣先讓程序?qū)?shù)據(jù)放入buffer中,在將Buffer里面的數(shù)據(jù)寫入channel
Pipe.SinkChannel Pipe.SourceChannel適用于支持線程之間通信的管道Channel
ServerSocketChannel SocketChannel適用于支持TCP網(wǎng)絡(luò)通信的Channel
DatagramChannel適用于支持UDP網(wǎng)絡(luò)通信的Channel
所有的Channel不通過構(gòu)造器來直接創(chuàng)建铆遭,而是通過傳統(tǒng)的節(jié)點(diǎn)InputStream硝桩,
OutPutStream的getChannel()方法
。
Channel中最常用的方法:
map(): 用于將Channel對(duì)應(yīng)的部分或全部數(shù)據(jù)英社稱ByteBuffer
read() 或 write()都有一定的重載形式枚荣。用于從Buffer中讀取或?qū)懭霐?shù)據(jù)碗脊。
public class FileChannelTest {
public static void main(String[] args) {
File f = new File("FileChannelTest.java");
try (FileChannel inChannel = new FileInputStream(f).getChannel();
FileChannel outChannel = new FileOutputStream("a.txt").getChannel();) {
// 將FileChannel里面的全部數(shù)據(jù)英社稱ByteBuffer
MappedByteBuffer byteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
// 使用GBK的字符集來創(chuàng)建解碼器
Charset charSet = Charset.forName("GBK");
// 直接將Buffer中的數(shù)據(jù)全部輸出
outChannel.write(byteBuffer);
// 再次調(diào)用buffer的clear方法,復(fù)原limit和position的位置
byteBuffer.clear();
// 創(chuàng)建解碼器
CharsetDecoder decoder = charSet.newDecoder();
// 使用解碼器將byteBuffer轉(zhuǎn)換成charbuffer
CharBuffer chBuffer = decoder.decode(byteBuffer);
System.out.println(chBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}