在Java程序中,對於數據的輸入/輸出以”流”(Stream)方式進行
java.io 包定義了多個流類型:
1) 按數據流方向分爲 – 輸入流和輸出流
2) 按數據單位分爲 – 字節流和字符流
3) 按功能分爲 – 節點流和處理流
所有流類型均位於java.io包內,分別繼承以下四種抽象流類型:
字節流 字符流
輸入流 InputStream Reader
輸出流 OutputStream Writer
注:輸入輸出流,都是站在【程序】的角度上來說,從文件讀數據這叫輸入流,往文件寫數據叫輸出流
一、字節 I/O 操作(InputStream和OutputStream)相關類圖
1) InputStream是一個抽象類,核心方法是read()、read(byte b[])、read(byte b[], int off, int len),從不同的數據源讀取數據。
這些數據源有:
① 字節數組。
② String對象。
③ 文件。
④ “管道”,一端輸入,另一端輸出。
⑤ 一個由其他種類的流組成的序列。
⑥ 其他數據源,如Internet等。
每一種數據源都對應相應的InputStream子類:
ByteArrayInputStream:緩衝區,處理字節數組,允許將內存的緩衝區當作InuputStream。
StringBufferInputStream:將String轉換成 InputStream,底層使用StringBuffer實現。
FileInputStream:從文件中讀取信息。
PipedInputStream:從管道讀數據,最爲多線程中的數據源。
SequenceInputStream:將多個流對象轉換成一個InputStream。
FilterInputStream:“裝飾器”類的基類(這裏是裝飾器模式),爲其它InputStream類提供功能:
DataInputStream:用於讀取基本類型數據。
BufferedInputStream:使用緩衝區,防止每次讀取時都得進行實際寫操作。
LineNumberInputStream:跟蹤輸入流中的行號,getLineNumber()、setLineNumber(int)。
PushbackInputStream:具有能彈出一個字節的緩衝區,可以將讀到的最後一個字符回退。
2) OutputStream是一個抽象類,核心方法是write(int b)、write(byte b[])、write(byte b[], int off, int len),決定輸出的目標:字節數組、文件或管道。
ByteArrayOutputStream:將數據寫入一個byte數組緩衝區。
FileOutputStream:將數據寫入文件。
PipedOutputStream:指定多線程數據的目的地,向與其它線程共用的管道中寫入數據,用於多線程之間任務的通信。
FilterOutputStream:“裝飾器”類,提供特定的輸出流:
DataOutputStream:與DataInputStream搭配使用,用於寫入基本類型數據。
BufferedOutputStream:使用緩衝區,避免每次發送數據時都進行實際的寫操作,可使用 flush() 清空緩衝區。
PrintStream:產生格式化輸出。
二、字符 I/O 操作(Reader和Writer)相關類圖
Reader和Writer操作的是字符,通過 InputStreamReader 和 OutputStreamWriter 進行字節和字符的轉換
三、基於磁盤的I/O操作(File類)
File類既能代表一個特定文件的名稱,又能代表一個目錄下的一組文件的名稱。
【1】文件、目錄的創建和刪除
public class FileOperation {
public static void main(String[] args) {
// 使用 File.separator 系統默認名稱分隔符,Windows和Linux文件路徑分割符不同
String fileName = "C:" + File.separator + "hello.txt";
File file = new File(fileName);
// 創建一個新文件
try {
// 指定的文件不存在且成功創建返回 true;文件已存在返回 false
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 刪除文件
if(file.exists()){
// 成功刪除文件或目錄時返回 true
file.delete();
}
// 創建一個文件夾
String dirName = "C:" + File.separator + "hello";
File dirFile = new File(dirName);
dirFile.mkdir();
dirFile.isDirectory(); // 判斷是否爲目錄
}
}
【2】查看目錄列表
public class FileOperation {
public static void main(String[] args) {
File path = new File("C:");
String[] list;
// 查看目錄所有內容
list = path.list();
// 查看目錄指定內容(以 .txt 結尾的)
final String regStr = ".+.txt";
// 使用一個匿名的目錄過濾器內部類,通過正則表達式驗證過濾,list()可以回調accept()方法
list = path.list(new FilenameFilter() {
private Pattern pattern = Pattern.compile(regStr);
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
for(String dirItem : list){
System.out.println(dirItem);
}
}
}
四、I/O流典型使用方式
儘管可以通過不同的方式組合 I/O 流類,但我們可能也就只用到其中的幾種組合。
【1】緩衝輸入文件(讀取)
public class BufferedInputFile {
public static String read(String fileName) throws Exception{
BufferedReader br = new BufferedReader(new FileReader(fileName));
String str;
StringBuffer sb = new StringBuffer();
while((str = br.readLine()) != null){
sb.append(str + "\n");
}
br.close();
return sb.toString();
}
public static void main(String[] args) throws Exception {
System.out.println(read("src/com/test/BufferedInputFile.java"));
}
}
【2】從內存輸入(讀取)
public class MemoryInput {
public static void main(String[] args) throws Exception {
StringReader in = new StringReader(BufferedInputFile.read("src/com/test/MemoryInput.java"));
int c;
while((c = in.read()) != -1){
System.out.println((char)c);
}
}
}
read()一個字符一個字符的讀取,以int的形式返回下一個字符,需強制轉換爲char才能正常打印
【3】基本文件輸出
/**
* FileWriter向文件寫入數據,使用BufferWriter將其封裝緩衝輸出,爲了格式化裝飾成 PrintWriter
*
*/
public class BasicFileOutput {
static String file = "BasicFileOutput.out";
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new StringReader(BufferedInputFile.read("src/com/test/BasicFileOutput.java")));
// PrintWriter有個構造函數,內部實現了BufferedWriter緩衝
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//可以簡寫爲 PrintWriter out = new PrintWriter(file);
int lineCount = 1;
String s;
while((s = br.readLine()) != null){
out.println(lineCount++ + ": " + s);
}
out.close();
System.out.println(BufferedInputFile.read(file));
}
}
【4】存儲和恢復數據
/**
* 存儲和恢復數據,使用 DataOutputStream寫入數據,並用 DataInputStream 恢復數據
* 這些流可以是任何形式,以文件爲例,對讀寫都進行緩衝處理
*/
public class StoringAndRecoveringData {
public static void main(String[] args) throws Exception {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream("C:" + File.separator + "Data.txt")));
out.writeDouble(3.14159265);
out.writeUTF("That was PI");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(
new FileInputStream("C:" + File.separator + "Data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
in.close();
}
}
【5】讀寫隨機訪問文件
/**
* 讀寫隨機訪問文件 RandomAccessFile,類似於組合使用 DataInputStream和DataOutputStream
* 另外可以使用 seek() 可以在文件中到處移動,並修改文件中的某個值
* double 總是佔 8 個字節,故第五個就是 5*8
*/
public class UsingRandomAccessFile {
static String file = "rtest.dat";
static void display() throws IOException{
RandomAccessFile raf = new RandomAccessFile(file, "r");
for(int i=0; i<7; i++){
System.out.println("Value " + i + ": " + raf.readDouble());
}
System.out.println(raf.readUTF());
raf.close();
}
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile(file, "rw");
for(int i=0; i<7; i++){
raf.writeDouble(i * 1.414);
}
raf.writeUTF("The end of the file");
raf.close();
display();
raf = new RandomAccessFile(file, "rw");
raf.seek(5*8);
raf.writeDouble(47.0001);
raf.close();
display();
}
}
【6】文件讀寫使用工具
JavaSE5 在PrintWriter 中添加了方便的構造器,可以方便的對一個文本文件進行寫入操作,但是其他常見的操作卻需要反覆執行,寫一個工具類用來簡化對文件的讀寫操作。創建一個FileTool對象,它用一個ArrayList來保存文件的若干行,當操縱文件內容時,就可以使用ArrayList的所有功能。
public class FileTool extends ArrayList<String> {
/**
* 讀取文件
* @param fileName 文件名
* @return 返回 String
*/
public static String read(String fileName) {
StringBuffer sb = new StringBuffer();
try{
BufferedReader br = new BufferedReader(new FileReader(new File(fileName).getAbsoluteFile()));
try {
String s;
while((s = br.readLine()) != null){
sb.append(s);
sb.append("\n");
}
} finally {
br.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
/**
* 往文件中寫數據
* @param fileName 文件名
* @param text 要寫入的文本
*/
public static void write(String fileName, String text) {
try {
PrintWriter out = new PrintWriter(new File(fileName).getAbsoluteFile());
try {
out.print(text);
} finally {
out.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 讀取文件,並按照指定分隔符分割
* @param fileName
* @param splitter 分隔符
*/
public FileTool(String fileName, String splitter){
super(Arrays.asList(read(fileName).split(splitter)));
if(get(0).equals("")) remove(0);
}
/**
* 正常讀取文件
* @param fileName
*/
public FileTool(String fileName){
this(fileName, "\n");
}
public void write(String fileName){
try{
PrintWriter out = new PrintWriter(new File(fileName).getAbsoluteFile());
try{
for(String item : this)
out.print(item);
} finally {
out.close();
}
} catch (IOException e){
}
}
// 測試
public static void main(String[] args) {
String file = read("src/com/test/FileTool.java");
System.out.println(file);
write("fileToolTest.txt", file);
FileTool fileTool = new FileTool("fileToolTest.txt");
fileTool.write("fileToolTest.txt");
TreeSet<String> words = new TreeSet<String>(new FileTool("src/com/test/FileTool.java", "\\W+"));
System.out.println(words.headSet("a"));
}
}
【7】讀取二進制文件
public class BinaryFile {
public static byte[] read(File bFile) throws IOException{
BufferedInputStream bi = new BufferedInputStream(new FileInputStream(bFile));
try{
byte[] data = new byte[bi.available()]; // available()用來產生恰當的數組尺寸
bi.read(data); // read()方法填充數組
return data;
} finally {
bi.close();
}
}
public static byte[] read(String bFile) throws IOException{
return read(new File(bFile).getAbsoluteFile());
}
}
【7】標準 I/O
Java提供了System.in、System.out和System.err,System.in是一個沒有被包裝過未經加工的 InputStream
public class Echo {
public static void main(String[] args) throws IOException {
// 將System.in包裝成BufferedReader,使用readLine()一次讀取一行
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
while((str = br.readLine()) != null && str.length() != 0){
System.out.println(str);
}
// 將System.out轉換成PrintWriter
PrintWriter out = new PrintWriter(System.out, true); // 第二個參數 true 開啓自動清空功能
out.println("Hello, world");
// 標準 I/O 重定向 操作的是字節流
// System類提供了一些簡單的靜態方法,允許對標準輸入、輸出和錯誤I/O流進行重定向
// System.setIn(InputStream) System.setOut(PrintStream) System.setErr(PrintStream)
PrintStream console = System.out; // System.out對象的引用
BufferedInputStream in = new BufferedInputStream(new FileInputStream("src/com/test/Echo.java"));
PrintStream _out = new PrintStream(new BufferedOutputStream(new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(_out);
System.setErr(_out);
str = null;
while((str = br.readLine()) != null && str.length() != 0){
System.out.println(str); // 標準輸出將重定向到另一個文件
}
_out.close();
System.setOut(console); // 恢復了System.out對象的引用
}
}
五、Java NIO
JDK1.4 的java.nio.* 包中引入了新的Java I/O類庫,目的在於提高速度。與傳統的 IO 相比主要有以下區別:
① Java IO是面向流的,Java NIO是面向緩衝區(塊)的
② Java IO各種流是阻塞的,當一線程調用read()
或write()
時,該線程被阻塞,直到有數據被讀取或寫入;Java NIO是非阻塞的,一線程從某通道請求讀取數據,若目前沒有數據可用,則不保持線程阻塞,直到有數據可供讀取,該線程可繼續做其他的事情,在寫入的時候也是,不等待它完全寫入,線程也可以同時去做其他的事情
Java NIO 由以下3個核心部分組成:Channel(通道)、Buffer(緩衝區)、Selector(選擇器)。
(1)Channel(通道):Java NIO的通道類似流,但又有些不同:
① 既可以從通道中讀取數據,又可以寫數據到通道。但流的讀寫通常是單向的。
② 通道可以異步地讀寫。
③ 通道中的數據總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入
在Java NIO中最重要的通道的實現:
① FileChannel:從文件中讀寫數據
② DatagramChannel:能通過UDP讀寫網絡中的數據
③ SocketChannel:能通過TCP讀寫網絡中的數據
④ ServerSocketChannel:可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel
(2)Buffer(緩衝區):用於和NIO通道進行交互,數據是從通道讀入緩衝區,從緩衝區寫入到通道中的
使用Buffer讀寫數據一般遵循以下四個步驟:
① 寫入數據到Buffer
② 調用flip()方法
③ 從Buffer中讀取數據
④ 調用clear()方法或者compact()方法
向Buffer寫入數據,當要讀取數據,需要通過flip()方法將Buffer從寫模式切換到讀模式,讀取之前寫入到buffer的所有數據。一旦數據讀取完畢,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:調用clear()或compact()方法。clear()方法會清空整個緩衝區。compact()方法只會清除已經讀過的數據,任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面。
(2.1)Buffer的capacity、position和limit
① capacity:不管Buffer處於讀模式還是寫模式,其返回緩衝區的容量
② position:初始值爲0,最大值爲capacity-1 。寫數據到Buffer時,表示當前位置,沒插入一條數據,向前移動一個單元;讀取數據時,從某個位置讀取數據,當Buffer切換爲讀模式時,position會被重置爲0,讀取一條數據時,會移動到下一個可讀的位置。
③ limit:在寫模式下,Buffer的limit表示你最多能往Buffer裏寫多少數據。 寫模式下,limit等於Buffer的capacity。
當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)
Buffer的分配,要想獲得一個Buffer對象首先要進行分配,如分配48字節capacity的ByteBuffer:ByteBuffer buf = ByteBuffer.allocate(48)
,分配一個可存儲1024個字符的CharBuffer:CharBuffer buf = CharBuffer.allocate(1024);
向Buffer寫數據,兩種方式:
① 從Channel寫到Buffer
int bytesRead = inChannel.read(buf); //read into buffer
② 通過Buffer的put()方法寫到Buffer裏
buf.put(127);
從Buffer中讀數據,兩種方式:
① 從Buffer讀取數據到Channel
int bytesWritten = inChannel.write(buf);
② 使用get()方法從Buffer中讀取數據
byte aByte = buf.get();
rewind()方法
Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)
mark()與reset()方法
通過調用Buffer.mark()方法,可以標記Buffer中的一個特定position。之後可以通過調用Buffer.reset()方法恢復到這個position。
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark.
【1】FileChannel
用於讀取、寫入、映射和操作文件的通道,在現版本中,可從現有的 FileInputStream、FileOutputStream 或 RandomAccessFile 對象獲得文件通道,方法是調用該對象的 getChannel 方法,這會返回一個連接到相同底層文件的文件通道。
public class GetFileChannel {
public static void main(String[] args) throws Exception {
FileChannel fileChannel = new FileOutputStream("data.txt").getChannel();
// 將 byte 數組包裝到緩衝區中,並將數據寫入通道(文件)
fileChannel.write(ByteBuffer.wrap("FileOutputStream--FileChannel Test".getBytes()));
fileChannel.close(); // 關閉此通道
// 向文件末尾增加數據 rw讀寫 r只讀
fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();
// 調用position()方法獲取FileChannel的當前位置,調用position(long pos)方法設置FileChannel的當前位置
// FileChannel實例的size()方法將返回該實例所關聯文件的大小
fileChannel.position(fileChannel.size()); // 設置此通道的文件位置,末尾
fileChannel.write(ByteBuffer.wrap("RandomAccessFile--FileChannel Test".getBytes()));
fileChannel.close();
// FileChannel.force(boolean metaData)方法將通道里尚未寫入磁盤的數據強制寫到磁盤上
// true 表示是否同時將文件元數據(權限信息等)寫到磁盤上
// 讀取文件
fileChannel = new FileInputStream("data.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配一個緩衝對象
fileChannel.read(buffer); // 將字節序列從此通道讀入給定的緩衝區,返回讀取的字節數
buffer.flip(); // 切換到讀模式,讓別人做好讀取字節的準備
while(buffer.hasRemaining()){
System.out.print((char)buffer.get());
}
}
}
上述代碼中,在讀取通道數據並輸出時,使用(char)buffer.get()
每次讀取一個字節,然後強制轉換爲char類型(這種方法顯然比較原始),如果不強制轉換,將會是亂碼,這是因爲ByteBuffer 存儲的是普通的字節,爲了把它們轉換成字符,有兩種方式可以解決:① 在輸入它們的時候對其進行編碼;② 從緩衝器輸出時對它們進行解碼。
從api文檔中可以看到CharBuffer有一個toString()方法可以返回緩衝器包含的所有的字符,而ByteBuffer可以調用asCharBuffer()方法作爲 char 類型緩衝區
public static void main(String[] args) throws Exception {
final int BSIZE = 1024;
FileChannel fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();
fileChannel.write(ByteBuffer.wrap("some text".getBytes()));
fileChannel.close();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
fileChannel = new FileInputStream("data.txt").getChannel();
fileChannel.read(buffer);
buffer.flip();
// 在這裏直接調用asCharBuffer()顯示不行輸出亂碼
System.out.println(buffer.asCharBuffer());
buffer.rewind(); // 將position設回0,可以重讀Buffer中的所有數據
/** 1) 在輸出之前進行解碼 Decoding**/
// 獲取系統編碼,使用java.nio.charset.Charset進行解碼
// decode(ByteBuffer bb) 返回一個CharBuffer對象
String encoding = System.getProperty("file.encoding");
System.out.println("使用 " + encoding +" 解碼輸出:" + Charset.forName(encoding).decode(buffer));
/** 2) 在寫入通道之前進行編碼,這樣輸出纔有意義 **/
fileChannel = new FileOutputStream("data1.txt").getChannel();
// 使用 UTF-8 寫入
fileChannel.write(ByteBuffer.wrap("some text2".getBytes("UTF-16BE")));
fileChannel.close();
// 讀取
fileChannel = new FileInputStream("data1.txt").getChannel();
buffer.clear();
fileChannel.read(buffer);
buffer.flip();
System.out.println(buffer.asCharBuffer());// 正常輸出
/** 3) 在寫入通道使用buffer.asCharBuffer() **/
fileChannel = new FileOutputStream("data3.txt").getChannel();
buffer = ByteBuffer.allocate(24);
buffer.asCharBuffer().put("some text3");
fileChannel.write(buffer);
fileChannel.close();
// 讀取
fileChannel = new FileInputStream("data3.txt").getChannel();
buffer.clear();
fileChannel.read(buffer);
buffer.flip();
System.out.println(buffer.asCharBuffer());// 正常輸出
}
待續… …
Java IO學習總結:
http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html
http://blog.csdn.net/zhangerqing/article/details/8466532
http://ifeve.com/overview/
http://www.importnew.com/17735.html
http://blog.csdn.net/maritimesun/article/details/7973603