字節流和字符流
InputStream和Reader
InputStream和Reader是所有輸入流的抽象基類,雖然本身並不能創建實例來完成輸入,但他們的方法是所有輸入流都可以使用的方法
InputStream裏包含三個方法:
- int read(): 從輸入流中讀取單個字節,返回所讀取的字節數據(字節數據可直接轉換爲int類型)
- int read(byte[] b):從輸入流中最多讀取b.length個字節的數據,並將其存儲在字節數組b中,返回實際讀取的字節數
- int read(byte[] b, int off, int len):從輸入流中最多讀取len個字節的數據,並將其存儲在數組b中,放入數組b中時,並不是從數組起點開始,而是從off位置開始,返回實際讀取的字節數
Reader裏包含如下三個方法:
- int read():從輸入流中讀取單個字符,返回所讀取的字符數據(字符數據可直接轉換爲int類型)
- int read(char[] cbuf):從輸入流中最多讀取cbuf.length個字符的數據,並將其存儲在字符數組cbuf中,返回實際讀取的字符數
- int read(char[] cbuf, int off, int len):從輸入流中最多讀取len個字符的數據,並將其存儲在字符數組cbuf中,放入數組cbuf中時,並不是從數組起點開始,而是從off位置開始,返回實際讀取的字符數
不難看出二者非常相似,只是處理的數據類型不一樣,雖然本身不能創建實例,但他們分別有一個用於讀取文件的輸入流:FileInputStream和FileReader,這兩個都是節點流直接和指定文件關聯
import java.io.*;
public class FileInputStreamTest
{
public static void main(String[] args) throws IOException
{
// 創建字節輸入流
var fis = new FileInputStream("FileInputStreamTest.java");
// 創建一個長度爲1024的數組
var bbuf = new byte[16];
// 用於保存實際讀取的字節數
var hasRead = 0;
// 使用循環來完成重複取得過程
while ((hasRead = fis.read(bbuf)) > 0)
{
// 取出字節,將字節數組轉換成字符串輸入!
System.out.print(new String(bbuf, 0, hasRead));
}
// 程序裏打開的文件IO資源不屬於內存裏的資源,垃圾回收無法回收該資源,必須顯示關閉IO資源
// 關閉文件輸入流,放在finally塊裏更安全
fis.close();
}
}
執行結果:
import java.io.*;
public class FileInputStreamTest
{
public static void main(String[] args) throws IOException
{
// 創建字節輸入流
var fis = new FileInputStream("FileInputStreamTest.java");
// 創建一個長度爲1024的“竹筒”
var bbuf = new byte[16];
// 用於保存實際?寥〉淖紙謔?
var hasRead = 0;
// 使用循環來重複“取水”過程
while ((hasRead = fis.read(bbuf)) > 0)
{
// 取出“竹?病敝興危ㄗ紙冢紙謔樽懷勺址淙耄?
System.out.print(new String(bbuf, 0, hasRead));
}
// ?乇瘴募淙肓鰨旁趂inally塊裏更安全
fis.close();
}
}
在執行結果中看到亂碼的原因是文件保存的時候採用GBK的編碼方式,每個中文字符佔兩個字節,如果read()方法讀取時候,整好讀了這兩個字節的第一個字節就結束了,那等於讀了半個中文字符,就會導致亂碼,把數組長度放大即可解決這個問題
import java.io.*;
public class FileReaderTest
{
public static void main(String[] args)
{
try (
// 創建字符輸入流
var fr = new FileReader("FileReaderTest.java"))
{
// 創建一個長度爲32的“竹筒”
var cbuf = new char[32];
// 用於保存實際讀取的字符數
var hasRead = 0;
// 使用循環來重複“取水”過程
while ((hasRead = fr.read(cbuf)) > 0)
{
// 取出“竹筒”中水滴(字符),將字符數組轉換成字符串輸入!
System.out.print(new String(cbuf, 0, hasRead));
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
Java7之後改了了所有IO資源類,都實現了AutoCloseable接口,因此都可以通過自動關閉資源的try語句來關閉這些IO流
此外,InputStream和Reader還支持移動記錄指針:
- void mark(int readAheadLimit):在記錄指針當前位置記錄一個標記(mark)
- boolean markSupported():判斷此輸入流是否支持mark()操作,也就是是否支持記錄標記
- boid reset():將此流的記錄指針重新定位到上一次記錄標記(mark)的位置
- long skip(long n):記錄指針向前移動n個字節/字符
OutputStream和Writer
OutputStream和Writer兩個流都提供瞭如下三個方法:
- void write(int c): 將指定的字節/字符輸出到輸出流中,其中c既可以代表字節也可以代表字符
- void write(byte[]/char[] buf):將字節數組/字符數組中的數據輸出到指定的輸出流中
- void write(byte[]/char[] buf, int off, int len):將字節數組/字符數組中從off位置開始長度爲len的字節/字符輸出到輸出流中
字符流以字符作爲操作單位,所以Writer可以用字符串來代替字符數組,即以String對象作爲參數,它還有兩個獨有的方法:
- void write(String str): 將str字符串裏包含的字符輸出到指定輸出流中
- void write(String str, int off, int len):將str字符串裏從off位置開始,長度爲len的字符輸出到指定輸出流中
import java.io.*;
public class FileOutputStreamTest
{
public static void main(String[] args)
{
try (
// 創建字節輸入流
var fis = new FileInputStream("FileOutputStreamTest.java");
// 創建字節輸出流
var fos = new FileOutputStream("newFile.txt"))
{
var bbuf = new byte[32];
var hasRead = 0;
// 循環從輸入流中取出數據
while ((hasRead = fis.read(bbuf)) > 0)
{
// 每讀取一次,即寫入文件輸出流,讀了多少,就寫多少。
fos.write(bbuf, 0, hasRead);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
import java.io.*;
public class FileWriterTest
{
public static void main(String[] args)
{
try (
var fw = new FileWriter("poem.txt"))
{
fw.write("錦瑟 - 李商隱\r\n");
fw.write("錦瑟無端五十弦,一弦一柱思華年。\r\n");
fw.write("莊生曉夢迷蝴蝶,望帝春心託杜鵑。\r\n");
fw.write("滄海月明珠有淚,藍田日暖玉生煙。\r\n");
fw.write("此情可待成追憶,只是當時已惘然。\r\n");
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
使用Java的IO流執行輸出時,不能忘記關閉輸出流,關閉輸出流可以保證流的物理資源被回收的同時還可以將輸出流緩衝區中的數據flush到物理節點裏