1. 程式人生 > 其它 >Java標準I/O流程式設計一覽筆錄

Java標準I/O流程式設計一覽筆錄

Java標準I/O知識體系圖:

1、I/O是什麼?

I/O 是Input/Output(輸入、輸出)的簡稱,輸入流可以理解為向記憶體輸入,輸出流是從記憶體輸出。

2、流

流是一個連續的資料流,可以從流中讀取資料,也可以往流中寫資料。流與資料來源,或資料來源流向的媒介相關聯。

在Java IO流中,流可以是位元組流,也可以是字元流。

3、Java I/O 用途與對應的流一覽

注:粗體為節點流。藍色為轉換流(位元組流轉為字元流)。

4、流的處理

流分節點流和處理流兩種。

節點流:可以從或向一個特定的地方(節點)讀寫資料。如FileInputStream、FileReader。

處理流:是對一個已存在的流的連線和封裝,通過所封裝的流的功能呼叫實現資料讀寫。如BufferedReader.處理流的構造方法總是要帶一個其他的流物件做引數。一個流物件經過其他流的多次包裝,稱為流的連結

5、檔案訪問

(1)讀取檔案

如果你需要在不同端使用讀取檔案,你可以根據你要讀的檔案是二進位制檔案還是文字檔案,或者根據你要處理的資料是準備採取位元組方式還是字元方式,決定使用 FileInputStream 或者 FileReader。兩者支援你從檔案開頭開始到檔案結尾讀取一個位元組或者字元,也可以將讀取的多個位元組或字元,寫入到記憶體的位元組陣列或字元陣列。

單位元組讀取檔案示例:

public static void readFileAsByte() throws IOException {
		String filepath = "file.bin";
		java.io.InputStream is = null;
		try {
			is = new FileInputStream(filepath);
			int data = -1;
			while ((data = is.read()) != -1) {// -1 表示讀取到達檔案結尾
				//操作資料
				System.out.print((byte)data + " ");
			}
		} finally {
			if (is != null) {
				is.close();// 關閉流
			}
		}
	}

位元組陣列讀取檔案示例:

public static void readFileAsByteArray() throws IOException {
	String filepath = "file.bin";
	java.io.InputStream is = null;
	try {
		is = new BufferedInputStream(new FileInputStream(filepath));// 組裝BufferedInputStream流,加入緩衝能力
		byte[] data = new byte[256];
		int len = -1;
		while ((len = is.read(data)) != -1) {// -1 表示讀取到達檔案結尾
			//操作資料
			for (int i = 0; i < len; i++) {
				System.out.print(data[i] + " ");
			}
		}
	} finally {
		if (is != null) {
			is.close();// 關閉流
		}
	}
}

單字元讀取檔案示例:

public static void readFileAsChar() throws IOException {
	String filepath = "file.txt";
	java.io.Reader r = null;
	try {
		r = new FileReader(filepath);
		int data = -1;
		while ((data = r.read()) != -1) {// -1 表示讀取到達檔案結尾
			//操作資料
			System.out.print((char) data);
		}
	} finally {
		if (r != null) {
			r.close();// 關閉流
		}
	}
}

字元陣列讀取檔案示例:

public static void readFileAsCharArray() throws IOException {
	String filepath = "file.txt";
	java.io.Reader r = null;
	try {
		r = new BufferedReader(new FileReader(filepath));// 組裝BufferedReader流,加入緩衝能力
		char[] data = new char[256];
		int len = -1;
		while ((len = r.read(data)) != -1) {// -1 表示讀取到達檔案結尾
			//操作資料
			for (int i = 0; i < len; i++) {
				System.out.print(data[i]);
			}
		}
	} finally {
		if (r != null) {
			r.close();// 關閉流
		}
	}
}

(2)寫入檔案

與讀取檔案類似:

如果你需要在不同端使用寫入檔案,你可以根據你要寫的檔案是二進位制檔案還是文字檔案,或者根據你要處理的資料是準備採取位元組方式還是字元方式,決定使用 FileOutputStream 或者 FileWriter。兩者支援你可以一次寫入一個位元組或者字元到檔案中,也可以直接寫入一個位元組陣列或者字元資料。資料按照寫入的順序儲存在檔案當中。

單位元組寫入檔案示例:

public static void writeFileAsByte() throws IOException {
	String filepath = "file.bin";
	java.io.OutputStream os = null;
	try {
		os = new FileOutputStream(filepath);
		os.write('1');
		os.write('2');
		os.write('3');
		os.write('4');
		os.flush();// 把緩衝區內的資料重新整理到磁碟
		
	} finally {
		if (os != null) {
			os.close();// 關閉流
		}
	}
}

位元組陣列寫入檔案示例:

public static void writeFileAsByteArray() throws IOException {
		String filepath = "file.bin";
		java.io.OutputStream os = null;
		try {
			os = new BufferedOutputStream(new FileOutputStream(filepath));
			// 模擬
			byte[] data = new byte[256];
			new Random().nextBytes(data);
			
			os.write(data);
			os.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (os != null) {
				os.close();// 關閉流
			}
		}
	}

單字元寫入檔案示例:

public static void writeFileAsChar() throws IOException {
	String filepath = "file.txt";
	java.io.Writer w = null;
	try {
		w = new FileWriter(filepath);
		w.write('1');
		w.write('2');
		w.write('3');
		w.write('4');
		w.flush();// 把緩衝區內的資料重新整理到磁碟
		
	} finally {
		if (w != null) {
			w.close();// 關閉流
		}
	}
}

字元陣列寫入檔案示例:

public static void writeFileAsCharArray() throws IOException {
		String filepath = "file.txt";
		java.io.Writer w = null;
		try {
			w = new BufferedWriter(new FileWriter(filepath));// 組裝BufferedWriter流,加入緩衝能力
			// 模擬
			char[] data = new char[256];
			String f = "0123456789abcdefghijklmnopqrstuvwxyz";
			Random rd = new Random();
			for (int i = 0; i < data.length; i++) {
				data[i] = f.charAt(rd.nextInt(f.length()));
			}
			w.write(data);
			w.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (w != null) {
				w.close();// 關閉流
			}
		}
	}

(3)隨機訪問檔案

如果你需要不按特定的存取順序,隨意讀取或者寫入檔案,可以考慮RandomAccessFile。

void seek(long pos)   設定到此檔案開頭測量到的檔案指標偏移量,在該位置發生下一個讀取或寫入操作。

簡單示例:

public static void main(String[] args) throws IOException {
		RandomAccessFile file = null;
		try {
			file = new java.io.RandomAccessFile("file.bin", "rw");
			file.seek(0);
			
			file.writeChar('1');
			file.seek(0);
			System.out.println(file.readChar());
			
			/**
			 * 讀取
			 */
			int data = -1;
			while ((data = file.read()) != -1) {// -1 表示讀取到達檔案結尾
				//操作資料
				System.out.print((byte)data + " ");
			}
			
		} finally {
			if (file != null) {
				file.close();// 關閉流
			}
		}
	}

 6、管道(執行緒記憶體)

管道為同一JVM中執行的執行緒提供基於記憶體的通訊機制。但是你不能利用管道在不同的JVM中的執行緒間通訊。

在概念上,Java的管道不同於Unix/Linux系統中的管道。在Unix/Linux中,執行在不同地址空間的兩個程序可以通過管道通訊。在Java中,通訊的雙方應該是執行在同一程序中的不同執行緒。當然除了管道之外,一個JVM中不同執行緒之間還有許多通訊的方式。實際上,執行緒在大多數情況下會傳遞完整的物件資訊而非原始的位元組資料。但是,如果你需要線上程之間傳遞位元組資料,Java IO的管道是一個不錯的選擇。 

當使用兩個相關聯的管道流時,務必將它們分配給不同的執行緒。read()方法和write()方法呼叫時會導致流阻塞,這意味著如果你嘗試在一個執行緒中同時進行讀和寫,可能會導致執行緒死鎖。

簡單示例:

static class Input implements Runnable {
		private final PipedInputStream inputStream = new PipedInputStream();
		public Input() {
		}
		public PipedInputStream getInputStream() {
			return inputStream;
		}
		@Override
		public void run() {
			try {
				byte[] buf = new byte[1024];
				int len = -1;
				System.out.println("管道讀取準備。");
				StringBuffer result = new StringBuffer();
				while ((len = inputStream.read(buf)) > 0) {
					//System.out.println(new String(buf, 0, len));
					result.append(new String(buf, 0, len));
				}
				System.out.println("管道讀取結果:" + result.toString());
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					if (inputStream != null)
						inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	static class Output implements Runnable {
		private final PipedOutputStream outputStream = new PipedOutputStream();
		public Output() {
		}
		public PipedOutputStream getOutputStream() {
			return outputStream;
		}
		@Override
		public void run() {
			try {
				System.out.println("管道寫出準備。");
				StringBuilder sb = new StringBuilder();
				// 模擬 通過for迴圈寫入2050個位元組
				for (int i = 0; i < 201; i++) {
					sb.append("0123456789");
					if (i > 0 && (i % 10 == 0)) {
						sb.append("rn");
					}
				}
				String str = sb.toString();
				outputStream.write(str.getBytes());
				System.out.println("管道寫出完成。");
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					if (outputStream != null)
						outputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String[] args) throws IOException {
		Input input = new Input();
		Output output = new Output();
		/**
		 * 將“管道輸入流”和“管道輸出流”關聯起來。
		 */
		//input.getInputStream().connect(output.getOutputStream());// 與下面一行等價
		output.getOutputStream().connect(input.getInputStream());
		new Thread(input).start();
		new Thread(output).start();
	}

7、序列化與ObjectInputStream、ObjectOutputStream

使用ObjectInputStream、ObjectOutputStream讀取或寫入物件,首先該物件必須實現Serializable介面,使得能夠序列化和反序列化。

簡單示例:

@SuppressWarnings("unused")
	public static void main(String[] args) throws IOException {
		class A implements java.io.Serializable {
			private static final long serialVersionUID = -9115696482036699559L;
			private int i = 1;
			private float f = 3;
			private String s = "風策信";
			public A() {
				super();
			}
			public A(int i, float f, String s) {
				super();
				this.i = i;
				this.f = f;
				this.s = s;
			}
			@Override
			public String toString() {
				StringBuilder builder = new StringBuilder();
				builder.append("A [i=").append(i).append(", f=").append(f).append(", s=").append(s).append("]");
				return builder.toString();
			}
		}
		class B implements java.io.Serializable {
			private static final long serialVersionUID = 6124575321340728225L;
			private long i = 2;
			private double f = 4;
			private String str = "風策信";
			public B() {
				super();
			}
			public B(long i, double f, String str) {
				super();
				this.i = i;
				this.f = f;
				this.str = str;
			}
			@Override
			public String toString() {
				StringBuilder builder = new StringBuilder();
				builder.append("B [i=").append(i).append(", f=").append(f).append(", str=").append(str).append("]");
				return builder.toString();
			}
		}
		A a = new A(1, 3, "a");
		B b = new B(2, 4, "b");
		//System.out.println(a);
		//System.out.println(b);
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("object.data.bin"));
			oos.writeObject(a);
			oos.writeObject(b);
			oos.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (oos != null)
				oos.close();
		}
		ObjectInputStream ois = null;
		try {
			ois = new ObjectInputStream(new FileInputStream("object.data.bin"));
			A a1 = (A) ois.readObject();
			B b1 = (B) ois.readObject();
			System.out.println(a1);
			System.out.println(b1);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			if (ois != null)
				ois.close();
		}
	}

8、回推流:PushbackInputStream與PushbackReader

PushbackInputStream/PushbackReader 用於解析InputStream/Reader內的資料,允許你讀取位元組/字元後,回推(pushback)到流中,而不破壞流。

PushbackInputStream類具有以下建構函式:

PushbackInputStream(InputStream inputStream)
PushbackInputStream(InputStream inputStream,int numBytes)

第一種形式建立的流物件允許將一個位元組返回到輸入流; 第二種形式建立的流物件具有一個長度為numBytes的回推快取,從而允許將多個位元組回推到輸入流中。

提供了unread()方法,如下所示:

void unread(int b)
void unread(byte[] buffer)
void unread(byte[] buffer,int offset,int numBytes)

第一種形式回推b的低位元組,這會使得後續的read()呼叫會把這個位元組再次讀取出來。第二種形式回推buffer中的位元組。第三種形式回推buffer中從offset開始的numBytes個位元組。當回推快取已滿時,如果試圖回推位元組,就會丟擲IOException異常。

示例:

public static void main(String[] args) throws IOException {
		String filepath = "file.bin";
		java.io.OutputStream os = null;
		try {
			os = new FileOutputStream(filepath);
			os.write('#');
			os.write(new byte[]{'a', 'b', 'c', 'd'});
			os.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (os != null) {
				os.close();// 關閉流
			}
		}
		/**
		 * 回推(pushback)
		 */
		PushbackInputStream pis = null;
		try {
			//pis = new PushbackInputStream(new FileInputStream(filepath));
			pis = new PushbackInputStream(new FileInputStream(filepath), 3);
			int len = -1;
			byte[] bytes = new byte[2];
			while ((len = pis.read(bytes)) != -1) {
				if ('b' == bytes[0]) {
					//pis.unread('U');
					//pis.unread(bytes);
					pis.unread(new byte[]{'1', '2', '3'});
				}
				for (int i = 0; i < len; i++) {
					System.out.print(((char) bytes[i]));
				}
			}
			System.out.println();
		} finally {
			if (pis != null)
				pis.close();
		}
		/**
		 * 會發現PushbackInputStream並沒有改變目標介質的資料,不破壞流
		 */
		try {
			pis = new PushbackInputStream(new FileInputStream(filepath));
			int len = -1;
			byte[] bytes = new byte[2];
			while ((len = pis.read(bytes)) != -1) {
				for (int i = 0; i < len; i++) {
					System.out.print(((char) bytes[i]));
				}
			}
		} finally {
			if (pis != null)
				pis.close();
		}
	}

注:PushbackInputStream物件會使得InputStream物件(用於建立PushbackInputStream物件)的mark()或reset()方法無效。對於準備使用mark()或reset()方法的任何流來說,都應當使用markSupported()方法進行檢查。

9、行數記錄:LineNumberInputStream與LineNumberReader

LineNumberInputStream與LineNumberReader提供跟蹤行號的附加功能。行是以回車符 ('r')、換行符 ('n') 或回車符後面緊跟換行符結尾的位元組序列。在所有這三種情況下,都以單個換行符形式返回行終止字元。 行號以 0 開頭,並在 read 返回換行符時遞增 1。 

使用getLineNumber()可以獲取當前讀取所在行數。

示例:

public static void main(String[] args) throws IOException {
		String filepath = "file.txt";
		java.io.Writer w = null;
		try {
			w = new FileWriter(filepath);
			w.write("百世山河任凋換,一生意氣未改遷。願從劫火投身去,重自寒灰飛赤鸞。rn");
			w.write("滄海桑田新幾度,月明還照舊容顏。琴心劍魄今何在,留見星虹貫九天。 n");
			w.write("冰輪騰轉下西樓,永夜初晗凝碧天。長路尋仙三山外,道心自在紅塵間。 n");
			w.write("何來慧劍破心繭,再把貂裘換酒錢。回望天涯攜手處,踏歌重訪白雲間。n");
			w.write("何以飄零去,何以少團欒,何以別離久,何以不得安? n");
			w.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (w != null) {
				w.close();// 關閉流
			}
		}
		/**
		 * LineNumberReader
		 */
		LineNumberReader lnr = null;
		try {
			lnr = new LineNumberReader(new FileReader(filepath));
			int len = -1;
			char[] chars = new char[2];
			//int lastLineNumber = -1;
			while ((len = lnr.read(chars)) != -1) {
				for (int i = 0; i < len; i++) {
					System.out.print(((char) chars[i]));
				}
				/*int lineNumber = lnr.getLineNumber();
				if (lineNumber != lastLineNumber) {
					System.out.println("---------------行數:" + lineNumber);
					lastLineNumber = lineNumber;
				}*/
			}
			int lineNumber = lnr.getLineNumber();
			System.out.println("行數:" + lineNumber);
			System.out.println();
		} finally {
			if (lnr != null)
				lnr.close();
		}
	}

10、StreamTokenizer的使用

 StreamTokenizer定義了幾種基本的常量用於標識解析過程:TT_EOF(流結尾)、TT_EOL(行結尾)、TT_NUMBER(數字符號, 0 1 2 3 4 5 6 7 8 9 . -都屬於數字語法)、TT_WORD(一個單詞)。 ttype 在呼叫 nextToken 方法之後,此欄位將包含剛讀取的標記的型別。 nval 如果當前標記是一個數字,則此欄位將包含該數字的值。 sval 如果當前標記是一個文字標記,則此欄位包含一個給出該文字標記的字元的字串。

public static void main(String[] args) throws IOException {
	StreamTokenizer tokenizer = new StreamTokenizer(new StringReader("Sven had 7 shining ring..."));
	while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {// 流末尾
		if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
			System.out.println(tokenizer.sval);
		} else if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
			System.out.println(tokenizer.nval);
		} else if (tokenizer.ttype == StreamTokenizer.TT_EOL) {// 行末尾
			System.out.println();
		}
	}
	//System.out.println(tokenizer.lineno());
}

基本方法介紹一下:

 nextToken()  - 從此標記生成器的輸入流中解析下一個標記。

(1)標記註釋

commenChar(int ch) - 指定某個字元為註釋字元,此字元之後直到行結尾都被stream tokenizer忽略。

slashSlashComments(boolean flag) - 如果為true,則/*與*/之間的都被認為是註釋,反之,不是。

slashStartComments(boolean flag) - 如果為true,則//之後到行結尾的所有都被認為是註釋,反之,不是。 

(2)基本語義

eolIsSignificant(boolean flag) - 決定一個行結束符是否被當作一個基本的符號處理,如果是true,則被當作一個基本符號,不當作普通的分隔符,如果是false,則保持原義,即當作普通的分隔符。

lowerCaseMode(boolean flag) - 決定是否讀取一個單詞時是否轉變成小寫。

parseNumbers() - 當stream tokenizer遭遇到一個單詞為雙精度的浮點數時,會把它當作一個數字,而不是一個單詞。

resetSyntax() - 重置語法表使所有的字元都被認為是“ordinary”。

(3)指定字元語義

ordinaryChar(int ch) - 指定字元在這個tokenizer中保持原義,即只會把當前字元認為普通的字元,不會有其他的語義。 ordinaryChars(int low, int hi) - 指定範圍內的字元保持語義,同上

whitespaceChars(int low, int hi) - 字元low與hi之間的所有字元都被當作為空格符,即被認識為tokenzier的分隔符。 wordChars(int low, int hi) - 字元low與hi之間的所有字元都被當作為單詞的要素。一個單詞是由一個單詞要素後面跟著0個或者更多個單詞要素或者數字要素。

11、合併流SequenceInputStream

SequenceInputStream會將與之相連線的流集組合成一個輸入流並從第一個輸入流開始讀取,直到到達檔案末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的末尾為止。 合併流的作用是將多個源合併合一個源。

public static void main(String[] args) throws IOException {
		String filepath1 = "file1.txt";
		String filepath2 = "file2.txt";
		java.io.Writer w = null;
		try {
			w = new FileWriter(filepath1);
			w.write("百世山河任凋換,一生意氣未改遷。願從劫火投身去,重自寒灰飛赤鸞。rn");
			w.write("滄海桑田新幾度,月明還照舊容顏。琴心劍魄今何在,留見星虹貫九天。 n");
			w.write("冰輪騰轉下西樓,永夜初晗凝碧天。長路尋仙三山外,道心自在紅塵間。 n");
			w.write("何來慧劍破心繭,再把貂裘換酒錢。回望天涯攜手處,踏歌重訪白雲間。n");
			w.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (w != null) {
				w.close();// 關閉流
			}
		}
		try {
			w = new FileWriter(filepath2);
			w.write("何以飄零去,何以少團欒,何以別離久,何以不得安? ");
			w.flush();// 把緩衝區內的資料重新整理到磁碟
		} finally {
			if (w != null) {
				w.close();// 關閉流
			}
		}
		java.io.Reader r = null;
		try {
			Vector<InputStream> v = new Vector<InputStream>(2);
			InputStream s1 = new FileInputStream(filepath1);
			InputStream s2 = new FileInputStream(filepath2);
			v.addElement(s1);
			v.addElement(s2);
			r = new BufferedReader(new InputStreamReader(new SequenceInputStream(v.elements())));
			
			char[] data = new char[256];
			int len = -1;
			while ((len = r.read(data)) != -1) {// -1 表示讀取到達檔案結尾
				//操作資料
				for (int i = 0; i < len; i++) {
					System.out.print(data[i]);
				}
			}
		} finally {
			if (r != null) {
				r.close();// 關閉流
			}
		}
	}

更多Demo:https://git.oschina.net/svenaugustus/MyJavaIOLab

本文只針對標準IO的知識總結,其他IO總結姊妹篇(NIO)請參見: + JavaNIO程式設計一覽筆錄: https://my.oschina.net/langxSpirit/blog/899954