不能不懂的 IO 處理流
我們?cè)谡莆樟?File 類、字節(jié)流苛谷、字符流赊锚,學(xué)會(huì)了 IO 操作的套路之后,IO 操作基本上就能處理日常工作中80%的常用問題了酱床。
今天再給大家介紹一下處理流羊赵,學(xué)會(huì)處理流之后,日常工作中的文件操作就都可以應(yīng)對(duì)了扇谣,掌握了下面的處理流昧捷,你將如虎添翼。
我們知道從 IO 流的功能來劃分罐寨,IO 流分為:節(jié)點(diǎn)流和處理流靡挥。其中,節(jié)點(diǎn)流是用來包裝數(shù)據(jù)源(File)的鸯绿,它直接和數(shù)據(jù)源連接跋破,表示從一個(gè)節(jié)點(diǎn)讀取數(shù)據(jù)或者把數(shù)據(jù)寫入到一個(gè)節(jié)點(diǎn);處理流是用來包裝節(jié)點(diǎn)流的瓶蝴,它是對(duì)一個(gè)已經(jīng)存在的節(jié)點(diǎn)流進(jìn)行連接毒返,處理流通過增加緩存的方式來提高輸入輸出操作的性能。
處理流按照功能劃分舷手,可以分為:緩沖流拧簸、轉(zhuǎn)換流、數(shù)據(jù)處理流聚霜、對(duì)象處理流狡恬。緩沖流是為了提高處理性能的,轉(zhuǎn)換流是字節(jié)流轉(zhuǎn)換為字符流用于處理亂碼的(解碼與編碼的字符集問題)蝎宇,數(shù)據(jù)處理流就是對(duì) 8個(gè)基本類型和字符串類型數(shù)據(jù)的直接處理弟劲,對(duì)象數(shù)據(jù)處理流就是經(jīng)常說的序列化與反序列化操作。
一姥芥、緩沖流
1兔乞、認(rèn)識(shí)字節(jié)緩沖流
字節(jié)緩沖流就是用緩沖流包裹字節(jié)流,也就是說在創(chuàng)建緩沖流對(duì)象的時(shí)候,需要傳入一個(gè)字節(jié)流的對(duì)象庸追,同時(shí)會(huì)創(chuàng)建一個(gè)默認(rèn) 8KB 的字節(jié)數(shù)組的緩沖區(qū)霍骄,通過這個(gè)緩沖區(qū)進(jìn)行讀寫操作,以減少 IO 的次數(shù)淡溯,從而提高字節(jié)流的處理性能读整。
字節(jié)緩沖流分為:字節(jié)輸入緩沖流 BufferedInputStream 和字節(jié)輸出緩沖流 BufferedOutputStream,以下代碼是字符緩沖流的源碼:
// 字節(jié)輸入緩沖流的構(gòu)造方法
private static int DEFAULT_BUFFER_SIZE = 8192; // 默認(rèn)8KB
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // 建立緩沖區(qū)
}
// 節(jié)輸出緩沖流的構(gòu)造方法
public BufferedOutputStream(OutputStream out) {
this(out, 8192); // 默認(rèn)8KB
}
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // 建立緩沖區(qū)
}
2咱娶、用字節(jié)緩沖流實(shí)現(xiàn)文件拷貝功能
@Test
public void testCopy() throws IOException {
// 1米间、使用File類與文件建立聯(lián)系
File src = new File("D:/file/image/tomcat.png");
File dest = new File("D:/file/image/tomcat2.jpg");
// 2、選擇對(duì)應(yīng)的輸入流或者輸出流
InputStream is = new BufferedInputStream(new FileInputStream(src)); // 用緩沖流包裹節(jié)點(diǎn)流
OutputStream os = new BufferedOutputStream(new FileOutputStream(dest)); // 用緩沖流包裹節(jié)點(diǎn)流
// 3膘侮、進(jìn)行讀寫操作
byte[] b = new byte[1024];
int len = 0;
while ((len = is.read(b)) != -1) {
os.write(b, 0, len);
}
os.flush();
// 4屈糊、關(guān)閉資源
os.close();
is.close();
}
運(yùn)行結(jié)果:
3、認(rèn)識(shí)字符緩存流
字符緩沖流就是用緩沖流包裹字符流琼了,也就是說在創(chuàng)建緩沖流對(duì)象的時(shí)候逻锐,需要傳入一個(gè)字符流的對(duì)象,同時(shí)會(huì)創(chuàng)建一個(gè)默認(rèn) 8KB 的字符數(shù)組的緩沖區(qū)雕薪,通過這個(gè)緩沖區(qū)進(jìn)行讀寫操作昧诱,以減少 IO 的次數(shù),從而提高字符流的處理性能蹦哼。
字符緩沖流分為:字符輸入緩沖流 BufferedReader 和字符輸出緩沖流 BufferedWriter鳄哭,以下代碼是字符緩沖流的源碼:
// 字符輸入緩沖流的構(gòu)造方法
private static int defaultCharBufferSize = 8192; // 默認(rèn)8KB
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz]; // 建立緩沖區(qū)
nextChar = nChars = 0;
}
// 字符輸出緩沖流的構(gòu)造方法
private static int defaultCharBufferSize = 8192; // 默認(rèn)8KB
public BufferedWriter(Writer out) {
this(out, defaultCharBufferSize);
}
public BufferedWriter(Writer out, int sz) {
super(out);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.out = out;
cb = new char[sz]; // 建立緩沖區(qū)
nChars = sz;
nextChar = 0;
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
4、用字符緩沖流實(shí)現(xiàn)文件拷貝功能
/**
* 字符緩沖流只能處理純文本的copy
*/
@Test
public void testCopy1() throws IOException {
// 1纲熏、使用File類與文件建立聯(lián)系
File src = new File("D:/file/txt/output_char.txt");
File dest = new File("D:/file/image/output_char_coppy.txt");
// 2妆丘、選擇對(duì)應(yīng)的輸入流或者輸出流
// 想使用新增的readLine方法(不能發(fā)生多態(tài))
BufferedReader reader = new BufferedReader(new FileReader(src));// 用緩沖流包裹節(jié)點(diǎn)流
BufferedWriter writer = new BufferedWriter(new FileWriter(dest, true));// 用緩沖流包裹節(jié)點(diǎn)流
// 3、進(jìn)行讀或?qū)懖僮? String line = null;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 類似于writer.append("\r\n");
}
writer.flush(); // 強(qiáng)制刷出
// 4局劲、關(guān)閉資源
writer.close();
reader.close();
}
運(yùn)行結(jié)果:
因?yàn)榫彌_流可以提高文件操作的性能勺拣,所以在以后的開發(fā)中,大家盡量要用緩沖流對(duì)節(jié)點(diǎn)流進(jìn)行包裝鱼填,不要直接使用字節(jié)流和字符流去操作文件药有。
通過以上的代碼大家再來體會(huì)一下 IO 流操作的套路,是不是套路在手苹丸,操作不愁胺叨琛!
二赘理、轉(zhuǎn)換流
1宦言、亂碼產(chǎn)生的原因
我們首先來看兩個(gè)概念,什么是編碼商模,什么是解碼奠旺?要區(qū)分這兩個(gè)概念的話蜘澜,也比較好理解:我們從碼的角度出發(fā)來認(rèn)識(shí)它們,碼就是計(jì)算機(jī)能看懂的東西响疚,也就是“二進(jìn)制”鄙信,等同于字節(jié),人類能看懂的語言是“字符或者字符串”忿晕。
如果是人類能看懂的變?yōu)橛?jì)算機(jī)能看懂的就叫編碼装诡,也就是說“字符或者字符串”變?yōu)樽止?jié)就是編碼,反過來杏糙,如果是計(jì)算機(jī)能看懂的變?yōu)槿祟惸芸炊木徒薪獯a慎王,也就是說字節(jié)變?yōu)椤白址蛘咦址本褪墙獯a。
大家可以通過加密和解密來對(duì)比理解宏侍,人看不懂就是加密,人能看到就是解密蜀漆。
亂碼產(chǎn)生的原因有兩個(gè):
1谅河、編碼與解碼的字符集不相同,導(dǎo)致亂碼确丢;
2绷耍、字節(jié)缺少或者長度丟失,導(dǎo)致亂碼鲜侥;
/**
* 亂碼的原因
*/
@Test
public void test() throws UnsupportedEncodingException {
// 默認(rèn)字符集“utf-8”
System.out.println("默認(rèn)字符集:" + System.getProperty("file.encoding"));
String info = "北京歡迎您褂始!"; // 解碼
byte[] data = info.getBytes(); // 編碼:char--->byte,字符或者字符串到字節(jié)
// 編碼與解碼字符集統(tǒng)一描函,都使用工作空間默認(rèn)的字符集
System.out.println(new String(data)); // 解碼:byte--->char崎苗,字節(jié)到字符或者字符串
// 不統(tǒng)一則出現(xiàn)亂碼
System.out.println(new String(data, "GBK"));
// 編碼與解碼的字符集必須相同,否則亂碼
byte[] data2 = "JPM舀寓,你好胆数!".getBytes("GBK");// 編碼
String info2 = new String(data2, "GBK");// 解碼
System.out.println(info2);
// 亂碼的原因之二,字節(jié)缺少互墓,長度丟失
String str = "北京";
byte[] data3 = str.getBytes();
System.out.println(data3.length); // 6
System.out.println(new String(data3, 0, 5)); // 字節(jié)數(shù)不完整導(dǎo)致亂碼
}
運(yùn)行結(jié)果:
默認(rèn)字符集:UTF-8
北京歡迎您必尼!
鍖椾含嬈㈣繋鎮(zhèn)紒
JPM,你好篡撵!
6
北?
2判莉、認(rèn)識(shí)轉(zhuǎn)換流
在 Java IO 中除了字節(jié)流和字符流外,還有一組字節(jié)流轉(zhuǎn)換位字符流的類育谬,用于處理亂碼問題券盅。
字節(jié)輸入流 InputStreamReader:作用是將輸入的字節(jié)流變?yōu)樽址鳌?br> 字節(jié)輸出流 OutputStreamWriter:作用是將輸出的字節(jié)流變?yōu)樽址鳌?/p>
轉(zhuǎn)換流只能是把字節(jié)流轉(zhuǎn)為字符流,從而完成它的使命斑司,那是因?yàn)樽址鞑荒茉O(shè)置字符集渗饮,只能是把字符流變?yōu)樽止?jié)流才能進(jìn)行字符集的設(shè)置但汞,因?yàn)樽止?jié)流才有設(shè)置字符集的方法。
// 輸入流 InputStreamReader 解碼
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
{
super(in);
if (charsetName == null)
throw new NullPointerException("charsetName");
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
//輸出流 OutputStreamWriter 編碼
public OutputStreamWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException
{
super(out);
if (charsetName == null)
throw new NullPointerException("charsetName");
se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
}
3互站、轉(zhuǎn)換流的文件拷貝demo私蕾,仔細(xì)體會(huì)注釋的文字
/**
* 轉(zhuǎn)換流:字節(jié)轉(zhuǎn)為字符<br>
* 1、輸入流 InputStreamReader 解碼<br>
* 2胡桃、輸出流 OutputStreamWriter 編碼<br>
* 仔細(xì)體會(huì)注釋的文字
*/
@Test
public void test2() throws IOException {
String srcPath = "D:/file/txt/output_char.txt";
String destPath = "D:/file/txt/output_char_convert.txt";
// FileReader(字符流)不能解碼踩叭,F(xiàn)ileInputStream(字節(jié)流)才能解碼
// BufferedReader br = new BufferedReader(new FileReader(new File(srcPath)));
// 字符流FileReader要換成字節(jié)流FileInputStream,但是字節(jié)流與字符流不能直接操作翠胰,需要通過轉(zhuǎn)換流InputStreamReader來實(shí)現(xiàn)
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(srcPath)), "UTF-8")); // 指定解碼字符集
// FileWriter(字符流)不能編碼容贝,F(xiàn)ileOutputStream(字節(jié)流)才能編碼
// BufferedWriter writer = new BufferedWriter(new FileWriter(new File(destPath)));
// 字符流FileWriter要換成字節(jié)流FileOutputStream,但是字節(jié)流與字符流不能直接操作之景,需要通過轉(zhuǎn)換流OutputStreamWriter來實(shí)現(xiàn)
BufferedWriter wr = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(new File(destPath)), "UTF-8"));// 指定編碼字符集
// 讀取并寫出
String line = null;
while ((line = br.readLine()) != null) {
wr.write(line);
wr.newLine();
}
wr.flush();
wr.close();
br.close();
}
運(yùn)行結(jié)果:
三、數(shù)據(jù)處理流
在 Java IO 中锻狗,提供了兩個(gè)數(shù)據(jù)(基本數(shù)據(jù)類型+String)操作流 满力,分別是數(shù)據(jù)輸入流 DataInputStream 和數(shù)據(jù)輸出流 DataOutputStream。
下面直接通過一個(gè)例子來演示數(shù)據(jù)處理流的用法:
@Test
public void test() throws IOException {
write("D:/file/txt/data.txt"); // 寫到文件
read("D:/file/txt/data.txt"); // 從文件讀取
}
/**
* 基本數(shù)據(jù)類型+String類型輸出到文件
*/
public static void write(String destPath) throws IOException {
int intNum = 100;
long longNum = 999L;
float floatNum = 3.14f;
double doubleNum = 5.50;
String str = "基本數(shù)據(jù)類型+String類型輸出到文件";
File dest = new File(destPath);
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
// 操作:注意寫出的順序轻纪,讀取要和寫出的順序一致
dos.writeInt(intNum);
dos.writeLong(longNum);
dos.writeFloat(floatNum);
dos.writeDouble(doubleNum);
dos.writeUTF(str);
dos.flush();
dos.close();
}
/**
* 從文件里讀取基本數(shù)據(jù)類型+String類型
*/
public static void read(String srcPath) throws IOException {
File src = new File(srcPath);
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(src)));
int intNum = dis.readInt();
long longNum = dis.readLong();
float floatNum = dis.readFloat();
double doubleNum = dis.readDouble();
String str = dis.readUTF();
dis.close();
// 100---->999---->3.14---->5.5---->基本數(shù)據(jù)類型+String類型輸出到文件
System.out.println(intNum + "---->" + longNum + "---->" + floatNum + "---->" + doubleNum + "---->" + str);
}
運(yùn)行結(jié)果:
100---->999---->3.14---->5.5---->基本數(shù)據(jù)類型+String類型輸出到文件
四油额、對(duì)象處理流
對(duì)象處理流的操作就是我們經(jīng)常說的序列化與反序列化操作。序列化就是把一個(gè)對(duì)象變?yōu)槎M(jìn)制流的一種方法刻帚,通過對(duì)象序列化可以方便地實(shí)現(xiàn)對(duì)象的傳輸和存儲(chǔ)潦嘶,反過來,如果把一個(gè)對(duì)象讀入到程序的過程及時(shí)反序列化崇众。序列化操作需要使用輸出流 ObjectOutputStream 進(jìn)行輸出掂僵,反序列化操作需要使用輸出流 ObjectInputStream 進(jìn)行讀取對(duì)象數(shù)據(jù)。
如果一個(gè)類的對(duì)象想被序列化校摩,這個(gè)類必須實(shí)現(xiàn) Serializable 接口看峻,同時(shí)要注意這個(gè)類對(duì)象的版本兼容問題,一般我們?cè)僖M(jìn)行序列化的類中設(shè)置一個(gè)固定的 serialVersionUID 常量衙吩,這個(gè)值只要不修改互妓,序列化和反序列化操作就不會(huì)發(fā)生版本兼容問題。
為了減少保存對(duì)象的使用空間坤塞,可以把一個(gè)類的某個(gè)屬性設(shè)置為不被序列化冯勉,當(dāng)實(shí)現(xiàn) Serializable 接口實(shí)現(xiàn)序列化的時(shí)候,可以使用 transient 關(guān)鍵字進(jìn)行聲明摹芙。
下面直接通過一個(gè)例子來演示對(duì)象處理流的用法:
/**
* 對(duì)象的序列化以及反序列化操作
*/
@Test
public void test() throws FileNotFoundException, IOException, ClassNotFoundException {
String filePath = "D:/file/txt/object.txt";
serializa(filePath);
Object object = UnSerializa(filePath);
if (object instanceof User) {
object = (User) object;
}
// User [name=JPM, age=18, address=null]灼狰,因?yàn)閍ddress屬性被transient修飾,沒有被序列化浮禾,所以為null
System.out.println(object.toString());
}
/**
* 對(duì)象序列化:對(duì)象變?yōu)槎M(jìn)制流的方法
*/
public static void serializa(String destPath) throws FileNotFoundException, IOException {
File dest = new File(destPath);
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
User user = new User("JPM", 18, "中國交胚,北京份汗!");
oos.writeObject(user);
oos.flush();
oos.close();
}
/**
* 對(duì)象反序列化:使用對(duì)象輸入流讀取對(duì)象數(shù)據(jù)
*/
public static Object UnSerializa(String srcPath) throws FileNotFoundException, IOException, ClassNotFoundException {
Object object = null;
File scr = new File(srcPath);
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(scr)));
object = ois.readObject();
ois.close();
return object;
}
/**
* 序列化與反序列化的對(duì)象,必須實(shí)現(xiàn)Serializable接口
*/
public class User implements Serializable {
private static final long serialVersionUID = -6954786920974801199L;
private String name;
private int age;
// transient修飾的屬性不會(huì)被序列化
private transient String address;
public User() {
super();
}
public User(String name, int age, String address) {
super();
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
運(yùn)行結(jié)果:
User [name=JPM, age=18, address=null]
這是 Java IO 操作的第三篇文章蝴簇,文章有點(diǎn)長杯活,堅(jiān)持看下來的小伙伴們也非常不容易,但是我想說熬词,能堅(jiān)持看完這三篇 IO 文章的同學(xué)旁钧,你一定掌握了 Java IO 處理的套路,面對(duì)未來開發(fā)中涉及到的 IO 操作互拾,一定會(huì)更加從容自如歪今,如果你能把所有的示例代碼手動(dòng)敲一遍,那你的感覺就會(huì)更加美好颜矿,不信你試試寄猩。