java-IO編程

IO基礎

io流

IO流是一種順序讀寫數據的模式:java

  • 單向流動
  • 以byte爲最小單位(字節流)

若是字符不是單子節表示的ASCLL碼,Java還提供一下解決方案:linux

  • java還提供了reader、writer表示字符流
  • 字符流傳輸的最小單位是char
  • 字符流輸出byte取決於編碼方式

reader、writer本質上是一個能自動編解碼的InputStream/OutputStream: 使用reader雖然讀入的數據源是子節,可是咱們讀入的數據都是char類型的字符 windows

使用InputStream雖然讀入的數據和源同樣都是byte子節,可是咱們能夠根據編碼將二進制byte數組轉換成字符串達到和reader同樣的效果
那麼如何選擇reader和InputStream?這取決於數據源,若是數據源不是文本就只能使用InputStream,若是是文本使用reader更加方便。

同步io與異步io

同步io:數組

  • 讀寫io時代碼等待數據返回後才繼續執行後續代碼
  • 代碼編寫簡單,cpu執行效率低

異步io:緩存

  • 讀寫io時僅發出請求,而後馬上執行後續代碼
  • 代碼編寫複雜,cpu執行效率高

JDK提供的java.io是同步io,java.nio是異步io 安全

File對象

java.io.File表示文件系統的一個文件或者目錄 建立方法:能夠是相對路徑或者絕對路徑bash

// windows
File f=new File("c:\\win\\note.exe");
// linux
File f=new File("/usr/bin/javac");
複製代碼

獲取路徑/絕對路徑/規範路徑:getPath() / getAbsolutePath() / getCanonicalPath() 判斷文件或目錄:異步

  • isFile():是不是文件
  • isDirectory():是不是目錄

須要注意的是構造一個File對象,即便咱們傳入的文件或目錄不存在也不會報錯,由於沒有對磁盤進行任何操做,只有在調用某些方法的時候若是文件或目錄不存在纔會報錯。
文件操做:ide

  • canRead():是否容許讀取該文件
  • canWrite():是否容許寫入該文件
  • canExecute():是否容許運行該文件
  • length():獲取文件大小
  • createNewFile():建立一個新文件
  • static createTempFile():建立一個臨時文件
  • delete():刪除該文件
  • deleteOnExit():在JVM退出時刪除該文件

目錄操做:函數

  • String[] list():列出目錄下的文件和子目錄名
  • File[] listFiles():列出目錄下的文件和子目錄名
  • File[] listFiles(FileFilter filter)
  • File[] listFiles(FilenameFilter filter)
  • mkdir():建立該目錄
  • mkdirs():建立該目錄,並在必要時將不存在的父目錄也建立出來
  • delete():刪除該目錄

Input和Output

InputStream

java.io.InputStream是全部輸入流的超類

  • int read()從輸入流中讀取數據的下一個字節,返回0到255範圍內的int字節值。若是由於已經到達流末尾而沒有可用的字節,則返回-1。在輸入數據可用、檢測到流末尾或者拋出異常前,此方法一直阻塞。
  • int read(byte[])讀取若干字節並填充到byte[]數組,返回讀取的字節數
  • read()方法是阻塞(blocking)的

完整的讀取InputStream全部子節

改造後的代碼,使用緩存加快讀取的速度:

package com.feiyangedu.sample;
import java.io.*;
public class Main {
	public static void main(String[] args) throws IOException {
		try (InputStream input = new FileInputStream("readme.txt")) {
			int n;
			byte[] buffer = new byte[1000];
			while ((n = input.read(buffer)) != -1) {
				System.out.println(n);
			}
		}
	}
}
複製代碼

上面代碼存在一個問題,若是在讀取過程當中發生io錯誤,InputStream就沒法正確的關閉資源得不到釋放。改造後的代碼以下:

或者(JDK1.7之後的寫法)
利用緩衝區一次讀取多個子節,提升效率:
ByteArrayInputStream能夠在內存中模擬一個InputStream:
他的做用是將一個數組變成一個InputStream,經常用於測試

OutputStream

OutputStream是全部輸出流的超類:

  • write(int b)寫入一個字節
  • write(byte[])寫入byte[]數組的全部字節
  • close():關閉輸出流,使用try(resource)能夠保證OutputStream正確關閉
  • flush()方法將緩衝器內容輸出,一般是自動調用不須要手動調用

package com.feiyangedu.sample;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Main {
	public static void main(String[] args) throws IOException {
		try (OutputStream output = new FileOutputStream("output.txt")) {
			byte[] b1 = "Hello".getBytes("UTF-8");
			output.write(b1);
			byte[] b2 = "你好".getBytes("UTF-8");
			output.write(b2);
		}
	}
}
複製代碼

輸入流、輸出流、字節數組緩衝區的綜合實例

InputStream輸入流、OutputStream輸出流、ByteArrayInputStream輸入緩衝區、ByteArrayOutStream輸出緩衝區的協調使用。

輸入流與輸出流能夠比喻成水管的兩端,當水流經過入口(InputStream)流入經過出口(OutputStream)流出完成文件或者字符串從一個點到另外一個點的移動或者轉換,在這個過程當中彷佛不須要緩衝區的介入。那麼何時會用到緩衝區呢?當你想在輸入流和輸出流中間有不少操做,或者將輸入流「複製」一份去作另外一個操做的時候就要用到緩衝區了。 好比咱們上傳圖片,爲了更加安全,經過文件的前四個字符串來判斷文件的類型: (寫一個實例)

// 建立輸出流緩衝區
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = file.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
// 將緩衝區數據寫入到數組中
baos.flush();
// 關閉文件
baos.close();

// 建立輸出流緩衝區,將輸入流緩衝區的數據寫入到輸入流緩衝區中,這樣就實現了一次輸入屢次輸出的效果
InputStream getType = new ByteArrayInputStream(baos.toByteArray()); // 拿去檢測文件類型
InputStream fileImg = new ByteArrayInputStream(baos.toByteArray()); // 若是文件類型合法,拿去上傳文件

複製代碼

總結

  • OutputStream是全部輸出流的超類
  • FileOutputStream實現了文件流輸出
  • ByteArrayOutStream在內存中模擬一個字節流輸出
  • 使用try(resource)能夠保證OutputStream正確關閉

Filter模式

已FileInputStream()爲例,他從文件中讀取數據,是最終數據源。

在FileInputStream上增長功能的常規作法

若是咱們要給FileInputStream添加緩衝功能: BufferedFileInputStream extends FileInputStream派生一個BufferedFileInputStream 若是給FileInputStream添加計算簽名的功能: DigestFileInputStream extends FileInputStream派生一個DigestFileInputStream ..... 若是要添加更多的功能就須要更多的子類去擴展,這樣的作的弊端是形成子類爆炸

JDK的Filter模式

JDK爲了解決上面的問題把InputStream分爲兩類:

  • 直接提供數據的的InputStream:(數據真正的來源)
    • FileInputStream
    • ByteArrayInputStream
    • ServletInputStream
    • ...
  • 提供額外附加功能的InputStream:
    • BufferedInputStream
    • DigestInputStream
    • CipherInputStream
    • ...

如何使用

組合:將一個對象複製給另外一個對象的變量,使其持有另外一個的對象的特性和方法 當咱們使用InputStream的時候咱們要根據實際狀況組合使用:

上圖顯示FileInputStream提供數據後通過兩次包裝後,具備了更多的功能,可是不管通過多少次包裝他依然能夠向上轉型爲InputStream而後進行操做。

Filter模式又稱Decorator模式,經過少許的類實現了各類功能的組合。

InputStream繼承樹

FilterInputStream 的做用是用來「封裝其它的輸入流,併爲它們提供額外的功能」。它的經常使用的子類有BufferedInputStream和DataInputStream。 一邊是提供數據的實現類,一邊是提供附加功能的實現類。經過兩邊實現類的組合實現更多的功能。

我的理解:提供數據的實現類不能進行同類包裝,而功能類能夠同類包裝。

實例

package com.feiyangedu.sample;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		try (InputStream input = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt.gz")))) {
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int n;
			while ((n = input.read(buffer)) != -1) {
				output.write(buffer, 0, n);
			}
			byte[] data = output.toByteArray();
			String text = new String(data, "UTF-8");
			System.out.println(text);
		}
	}

}
複製代碼

編寫自定義工具類計算讀取的字節數

import java.io.*;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		try (InputStream input = new CountInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt.gz"))))) {
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int n;
			while ((n = input.read(buffer)) != -1) {
				output.write(buffer, 0, n);
			}
			byte[] data = output.toByteArray();
			String text = new String(data, "UTF-8");
            // 打印內容
			//System.out.println(text);
			//因爲咱們要使用count方法,所以要將InputStream類型向下轉型爲CountInputStream,由於咱們明確的知道繼承關係這裏能夠不判斷是否能夠安全的向下轉型
			System.out.println(InputStream.class.isAssignableFrom(CountInputStream.class));
			System.out.println(((CountInputStream) input).count);
		}
	}
}
class CountInputStream extends FilterInputStream {
	int count=0;
	public CountInputStream(InputStream in) {
		super(in);
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		//經過調用父類的read方法獲取包裝的inputStream的字節長度
		int n = super.read(b, off, len);
		count +=n;
		return n;
	}
}

複製代碼

輸出

true
11356
複製代碼

關於字節(重要)

InputStream的read()與read(byte[] b)

在學習的過程當中發現若是使用read()返回的值表明字節值(0-255),而使用read(byte[] b)則返回的是讀取的字節長度,而字節值儲存在byte[] b類型的b變量中爲何會產生這樣的結果?
首先看JDK的解釋:InputStream的read()的方法從輸入流中讀取數據的下一個字節,返回0到255範圍內的int字節值。
那麼問題來了0到255範圍的字節值是什麼?
簡單解釋:00000000一個字節包含8位二進制值,這8位二進制數相互變化,共有2^8 = 256種數字,0~255 那麼InputStream的read()每次返回的就是0~255的值,這個int類型10進制的值能夠轉換成一個8位的二進制數 字符類型:char,由於java的char使用unicode編碼,因此可直接賦值給int類型查看他的unicode編碼 反過來就是一個int類型(char)int就能夠直接轉換成char字符

ab中國cdefj
複製代碼
import java.io.*;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		InputStream in = null;
		File f = new File("test.txt");
		in = new FileInputStream(f);
		int i = 0;
		while ((i = in.read()) != -1) {
			//String str = new String((char)i);
			System.out.println((char)i);
		}
	}
}
複製代碼

輸出

a
b
ä
¸
­
å
›
½
c
d
e
f
j
複製代碼

上面的程序演示了read()讀取單個字節而後返回的int類型的值,經過(char)i強制轉型爲單個字符而後輸出,因爲中文是佔3個字符的因此中文部分顯示亂碼

下面的代碼演示byte[]讀取數據

import java.io.*;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		InputStream in = null;
		File f = new File("test.txt");
       //當一次讀取3個字節的時候若是湊巧全是中文,或者從第四個字節開始是中文,就不會出現亂碼,若是不是就會出現亂碼
		byte[] b = new byte[3];

		in = new FileInputStream(f);
		int i = 0;
		while ((i = in.read(b)) != -1) {
        //String函數能夠接收一個char數組或者byte數組而後轉換成字符串
			String str = new String(b);
			System.out.println(str);
		}
	}
}
複製代碼

下面是兩種演示數據: 不會出現亂碼,由於第三個字節後是中文,每次都三個字節正好讀取一箇中文字符

abc中國cdefj
複製代碼

顯示效果

abc
中
國
cde
fje
複製代碼

若是沒有前面的c

ab中國cdefj
複製代碼

就會輸出亂碼

ab�
���
��c
def
jef
複製代碼

操做Zip

ZipInputStream繼承自FlaterInputStream實現了ZipConstants接口,雖然ZipInputStream是FlaterInputStream子類可是有些方法是ZipInputStream獨有的,在使用這些方法的時候不能向上轉型,所以使用ZipInputStream的時候直接建立一個他的實例並且無需向上轉型,

ZipInputStream能夠讀取Zip流。 JarInputStream提供了額外讀取jar包內容的能力。 ZipOutputStream能夠寫入Zip流。 配合FileInputStream和FileOutputStream就能夠讀寫Zip文件。

package com.feiyangedu.sample;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		try (ZipInputStream zip = new ZipInputStream(new BufferedInputStream(new FileInputStream("test.jar")))) {
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				if (entry.isDirectory()) {
					System.out.println("D " + entry.getName());
				} else {
					System.out.println("F " + entry.getName() + " " + entry.getSize());
					printFileContent(zip);
				}
			}
		}
	}

	static void printFileContent(ZipInputStream zip) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int n;
		while ((n = zip.read(buffer)) != -1) {
			output.write(buffer, 0, n);
		}
		byte[] data = output.toByteArray();
		System.out.println(" size: " + data.length);
	}

}

複製代碼

classpath資源 (linux與win的路徑有區別)

classpath中能夠包含任意類型的文件。 從classpath讀取文件能夠避免不一樣環境下文件路徑不一致的問題。 讀取classpath資源:

try(InputStream input = getClass().getResourceAsStream("/default.properties")) {
    if (input != null) {
        // Read from classpath
    }
}
複製代碼
  • 把資源存儲在classpath中能夠避免文件路徑依賴
  • class對象的getResourceAsStream()能夠從classpath讀取資源
  • 須要檢查返回的InputStream是否爲null
package com.feiyangedu.sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;

public class Main {

	public static void main(String[] args) throws IOException {
		// 從classpath讀取配置文件:
		try (InputStream input = Main.class.getResourceAsStream("/conf.properties")) {
			if (input != null) {
				System.out.println("Read /conf.properties...");
				Properties props = new Properties();
				props.load(input);
				System.out.println("name=" + props.getProperty("name"));
			}
		}
		// 從classpath讀取txt文件:
		String data = "/com/feiyangedu/sample/data.txt";
		try (InputStream input = Main.class.getResourceAsStream(data)) {
			if (input != null) {
				System.out.println("Read " + data + "...");
				BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
				System.out.println(reader.readLine());
			} else {
				System.out.println("Resource not found: " + data);
			}
		}
	}

}
複製代碼

conf.properties

name=Java IO
url=www.feiyangedu.com
複製代碼

序列化

序列化是指把一個Java對象變成二進制內容(byte[]) Java對象實現序列化必須實現Serializable接口(空接口) 反序列化是指把一個二進制內容(byte[])變成Java對象 使用ObjectOutputStream和ObjectInputStream實現序列化和反序列化 readObject()可能拋出的異常:

  • ClassNotFoundException:沒有找到對應的Class
  • InvalidClassException:Class不匹配
  • 反序列化由JVM直接構造出Java對象,不調用構造方法
  • 可設置serialVersionUID做爲版本號(非必需)

Reader與InputStream

Reader與InputStream的區別:

Reader以字符爲最小單位實現了字符流輸入:

  • int read() 讀取下一個字符並返回字符int值(0-65535)
  • int read(char[]) 讀取若干字符並填充到char[]數組 經常使用Reader類:
  • FileReader:從文件讀取
  • CharArrayReader:從char[]數組讀取

完整讀取文件實例

利用緩衝區讀取文件
FileReader能夠從文件中獲取Reader: 可是要注意這裏使用的是系統默認的編碼
CharArrayReader能夠在內存中模擬一個Reader:

Reader與InputStream的關係

Reader是基於InputStream構造的,任何InputStream均可指定編碼並經過InputStreamReader轉換爲Reader:

Reader reader = new InputStreamReader(input, "UTF-8")
複製代碼

FileReader內部持有一個 FileInputStream

import java.io.*;

public class Main {

	public static void main(String[] args) throws IOException {
		try (Reader reader = new InputStreamReader(new FileInputStream("readme.txt"),"UTF-8")) {
			int n;
			while ((n = reader.read()) != -1) {
				System.out.println((char) n);
			}
		}
	}

}
複製代碼

Writer與OutputStream

Writer與OutputStream的區別

Writer以字符爲最小單位實現了字符流輸出:

  • write(int c) 寫入下一個字符(0-65535)
  • write(char[]) 寫入char[]數組的全部字符
  • write(char[] c,int off,int len) 寫入指定範圍的字符
  • write(String s)寫入string表示的字符

經常使用Writer類:

  • FileWriter:寫入文件
  • CharArrayWriter:寫入char[]數組

向Writer寫入字符:

FileWriter能夠從文件中獲取Writer:
CharArrayWriter能夠在內存中模擬一個Writer:

Writer與OutputStream的關係

Writer是基於OutputStream構造的,任何OutputStream均可指定編碼並經過OutputStreamWriter轉換爲Writer:

Writer writer = new OutputStreamWriter(output, "UTF-8")
複製代碼
相關文章
相關標籤/搜索