java IO 實例分析,javaio實例分析
初學java,一直搞不懂java裡面的io關系,在網上找了很多大多都是給個結構圖草草描述也看的不是很懂。而且沒有結合到java7 的最新技術,所以自己來整理一下,有錯的話請指正,也希望大家提出寶貴意見。
首先看個圖:(如果你也是初學者,我相信你看了真個人都不好了,想想java設計者真是煞費苦心啊!)
這是java io 比較基本的一些處理流,除此之外我們還會提到一些比較深入的基於io的處理類,比如console類,SteamTokenzier,Externalizable接口,Serializable接口等等一些高級用法極其原理。
一、java io的開始:文件
1. 我們主要講的是流,流的本質也是對文件的處理,我們循序漸進一步一步從文件將到流去。
2. java 處理文件的類 File,java提供了十分詳細的文件處理方法,舉了其中幾個例子,其余的可以去
Java代碼
package com.hxw.io;
import java.io.*;
public class FileExample{
public static void main(String[] args) {
createFile();
}
/**
* 文件處理示例
*/
public static void createFile() {
File f=new File("E:/電腦桌面/jar/files/create.txt");
try{
f.createNewFile(); //當且僅當不存在具有此抽象路徑名指定名稱的文件時,不可分地創建一個新的空文件。
System.out.println("該分區大小"+f.getTotalSpace()/(1024*1024*1024)+"G"); //返回由此抽象路徑名表示的文件或目錄的大小。
f.mkdirs(); //創建此抽象路徑名指定的目錄,包括所有必需但不存在的父目錄。
// f.delete(); // 刪除此抽象路徑名表示的文件或目錄
System.out.println("文件名 "+f.getName()); // 返回由此抽象路徑名表示的文件或目錄的名稱。
System.out.println("文件父目錄字符串 "+f.getParent());// 返回此抽象路徑名父目錄的路徑名字符串;如果此路徑名沒有指定父目錄,則返回 null。
}catch (Exception e) {
e.printStackTrace();
}
}
}
二、字節流:
1.字節流有輸入和輸出流,我們首先看輸入流InputStream,我們首先解析一個例子(FileInputStream)。
Java代碼
package com.hxw.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileCount {
/**
* 我們寫一個檢測文件長度的小程序,別看這個程序挺長的,你忽略try catch塊後發現也就那麼幾行而已。
*/
publicstatic void main(String[] args) {
//TODO 自動生成的方法存根
int count=0; //統計文件字節長度
InputStreamstreamReader = null; //文件輸入流
try{
streamReader=newFileInputStream(new File("D:/David/Java/java 高級進階/files/tiger.jpg"));
/*1.new File()裡面的文件地址也可以寫成D:\\David\\Java\\java 高級進階\\files\\tiger.jpg,前一個\是用來對後一個
* 進行轉換的,FileInputStream是有緩沖區的,所以用完之後必須關閉,否則可能導致內存占滿,數據丟失。
*/
while(streamReader.read()!=-1) { //讀取文件字節,並遞增指針到下一個字節
count++;
}
System.out.println("---長度是: "+count+" 字節");
}catch (final IOException e) {
//TODO 自動生成的 catch 塊
e.printStackTrace();
}finally{
try{
streamReader.close();
}catch (IOException e) {
//TODO 自動生成的 catch 塊
e.printStackTrace();
}
}
}
}
我們一步一步來,首先,上面的程序存在問題是,每讀取一個自己我都要去用到FileInputStream,我輸出的結果是“---長度是: 64982
字節”,那麼進行了64982次操作!可能想象如果文件十分龐大,這樣的操作肯定會出大問題,所以引出了緩沖區的概念。可以將
streamReader.read()改成streamReader.read(byte[]b)此方法讀取的字節數目等於字節數組的長度,讀取的數據
被存儲在字節數組中,返回讀取的字節數,InputStream還有其他方法mark,reset,markSupported方法,例如:
markSupported 判斷該輸入流能支持mark
和 reset
方法。
mark用於標記當前位置;在讀取一定數量的數據(小於readlimit的數據)後使用reset可以回到mark標記的位置。
FileInputStream不支持mark/reset操作;BufferedInputStream支持此操作;
mark(readlimit)的含義是在當前位置作一個標記,制定可以重新讀取的最大字節數,也就是說你如果標記後讀取的字節數大於readlimit,你就再也回不到回來的位置了。
通常InputStream的read()返回-1後,說明到達文件尾,不能再讀取。除非使用了mark/reset。
2.FileOutputStream 循序漸進版, OutputStream是所有字節輸出流的父類,子類有
ByteArrayOutputStream,FileOutputStream,ObjectOutputStreanm,這些我們在後面都會一一說
到。先說FileOutputStream
我以一個文件復制程序來說,順便演示一下緩存區的使用。(Java
I/O默認是不緩沖流的,所謂“緩沖”就是先把從流中得到的一塊字節序列暫存在一個被稱為buffer的內部字節數組裡,然後你可以一下子取到這一整塊的
字節數據,沒有緩沖的流只能一個字節一個字節讀,效率孰高孰低一目了然。有兩個特殊的輸入流實現了緩沖功能,一個是我們常用的
BufferedInputStream.)
Java代碼
package com.hxw.io;
import java.io.*;
public class FileCopy {
public static void main(String[] args) {
// TODO自動生成的方法存根
byte[] buffer=new byte[512]; //一次取出的字節數大小,緩沖區大小
int numberRead=0;
FileInputStream input=null;
FileOutputStream out =null;
try {
input=new FileInputStream("D:/David/Java/java 高級進階/files/tiger.jpg");
out=new FileOutputStream("D:/David/Java/java 高級進階/files/tiger2.jpg"); //如果文件不存在會自動創建(目錄不存在會報錯)
while ((numberRead=input.read(buffer))!=-1) { //numberRead的目的在於防止最後一次讀取的字節小於buffer長度,
out.write(buffer, 0, numberRead); //否則會自動被填充0
}
} catch (final IOException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}finally{
try {
input.close();
out.close();
} catch (IOException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}
}
}
}
3.讀寫對象:ObjectInputStream 和ObjectOutputStream
,該流允許讀取或寫入用戶自定義的類,但是要實現這種功能,被讀取和寫入的類必須實現Serializable接口,其實該接口並沒有什麼方法,可能相當
於一個標記而已,但是確實不可缺少的。實例代碼如下:
Java代碼
package com.hxw.io;
import java.io.*;
public class ObjetStream {
/**
* @param args
*/
public static void main(String[] args) {
// TODO自動生成的方法存根
ObjectOutputStream objectwriter=null;
ObjectInputStream objectreader=null;
try {
objectwriter=new ObjectOutputStream(new FileOutputStream("D:/David/Java/java 高級進階/files/student.txt"));
objectwriter.writeObject(new Student("gg", 22));
objectwriter.writeObject(new Student("tt", 18));
objectwriter.writeObject(new Student("rr", 17));
objectreader=new ObjectInputStream(new FileInputStream("D:/David/Java/java 高級進階/files/student.txt"));
for (int i = 0; i < 3; i++) {
System.out.println(objectreader.readObject());
}
} catch (IOException | ClassNotFoundException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}finally{
try {
objectreader.close();
objectwriter.close();
} catch (IOException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}
}
}
}
class Student implements Serializable{
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
運行後系統輸出:
Student [name=gg, age=22]
Student [name=tt, age=18]
Student [name=rr, age=17]
4.有時沒有必要存儲整個對象的信息,而只是要存儲一個對象的成員數據,成員數據的類型假設都是Java的基本數據類型,這樣的需求不必
使用到與Object輸入、輸出相關的流對象,可以使用DataInputStream、DataOutputStream來寫入或讀出數據。下面是一個
例子:(DataInputStream的好處在於在從文件讀出數據時,不用費心地自行判斷讀入字符串時或讀入int類型時何時將停止,使用對應的readUTF()和readInt()方法就可以正確地讀入完整的類型數據。)
Java代碼
package com.hxw;
public class Member {
private String name;
private int age;
public Member() {
}
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
打算將Member類實例的成員數據寫入文件中,並打算在讀入文件數據後,將這些數據還原為Member對象。下面的代碼簡單示范了如何實現這個需求。
Java代碼
package com.hxw;
import java.io.*;
public class DataStreamDemo
{
public static void main(String[]args)
{
Member[] members = {newMember("Justin",90),
newMember("momor",95),
newMember("Bush",88)};
try
{
DataOutputStreamdataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));
for(Member member:members)
{
//寫入UTF字符串
dataOutputStream.writeUTF(member.getName());
//寫入int數據
dataOutputStream.writeInt(member.getAge());
}
//所有數據至目的地
dataOutputStream.flush();
//關閉流
dataOutputStream.close();
DataInputStreamdataInputStream = new DataInputStream(new FileInputStream(args[0]));
//讀出數據並還原為對象
for(inti=0;i<members.length;i++)
{
//讀出UTF字符串
String name =dataInputStream.readUTF();
//讀出int數據
int score =dataInputStream.readInt();
members[i] = newMember(name,score);
}
//關閉流
dataInputStream.close();
//顯示還原後的數據
for(Member member : members)
{
System.out.printf("%s\t%d%n",member.getName(),member.getAge());
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
5.PushbackInputStream類繼承了FilterInputStream類是iputStream類的修飾者。提供可以
將數據插入到輸入流前端的能力(當然也可以做其他操作)。簡而言之PushbackInputStream類的作用就是能夠在讀取緩沖區的時候提前知道下
一個字節是什麼,其實質是讀取到下一個字符後回退的做法,這之間可以進行很多操作,這有點向你把讀取緩沖區的過程當成一個數組的遍歷,遍歷到某個字符的時候可以進行的操作,當然,如果要插入,能夠插入的最大字節數是與推回緩沖區的大小相關的,插入字符肯定不能大於緩沖區吧!下面是一個示例。
Java代碼
package com.hxw.io;
import java.io.ByteArrayInputStream; //導入ByteArrayInputStream的包
import java.io.IOException;
import java.io.PushbackInputStream;
/**
* 回退流操作
* */
public class PushBackInputStreamDemo {
public static void main(String[] args) throws IOException {
String str = "hello,rollenholt";
PushbackInputStream push = null; // 聲明回退流對象
ByteArrayInputStream bat = null; // 聲明字節數組流對象
bat = new ByteArrayInputStream(str.getBytes());
push = new PushbackInputStream(bat); // 創建回退流對象,將拆解的字節數組流傳入
int temp = 0;
while ((temp = push.read()) != -1) { // push.read()逐字節讀取存放在temp中,如果讀取完成返回-1
if (temp == ',') { // 判斷讀取的是否是逗號
push.unread(temp); //回到temp的位置
temp = push.read(); //接著讀取字節
System.out.print("(回退" + (char) temp + ") "); // 輸出回退的字符
} else {
System.out.print((char) temp); // 否則輸出字符
}
}
}
}
6.SequenceInputStream:有些情況下,當我們需要從多個輸入流中向程序讀入數據。此時,可以使用合並流,將多個輸入
流合並成一個SequenceInputStream流對象。SequenceInputStream會將與之相連接的流集組合成一個輸入流並從第一個輸
入流開始讀取,直到到達文件末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾為止。
合並流的作用是將多個源合並合一個源。其可接收枚舉類所封閉的多個字節流對象。
Java代碼
package com.hxw.io;
import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class SequenceInputStreamTest {
/**
* @param args
* SequenceInputStream合並流,將與之相連接的流集組合成一個輸入流並從第一個輸入流開始讀取,
* 直到到達文件末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾為止。
* 合並流的作用是將多個源合並合一個源。可接收枚舉類所封閉的多個字節流對象。
*/
public static void main(String[] args) {
doSequence();
}
private static void doSequence() {
// 創建一個合並流的對象
SequenceInputStream sis = null;
// 創建輸出流。
BufferedOutputStream bos = null;
try {
// 構建流集合。
Vector<InputStream> vector = new Vector<InputStream>();
vector.addElement(new FileInputStream("D:\text1.txt"));
vector.addElement(new FileInputStream("D:\text2.txt"));
vector.addElement(new FileInputStream("D:\text3.txt"));
Enumeration<InputStream> e = vector.elements();
sis = new SequenceInputStream(e);
bos = new BufferedOutputStream(new FileOutputStream("D:\text4.txt"));
// 讀寫數據
byte[] buf = new byte[1024];
int len = 0;
while ((len = sis.read(buf)) != -1) {
bos.write(buf, 0, len);
bos.flush();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
if (sis != null)
sis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.PrintStream 說這個名字可能初學者不熟悉,如果說System.out.print()你肯定熟悉,System.out這個對象就是PrintStream,這個我們不做過多示例
三、字符流(顧名思義,就是操作字符文件的流)
1.java
使用Unicode存儲字符串,在寫入字符流時我們都可以指定寫入的字符串的編碼。前面介紹了不用拋異常的處理字節型數據的流
ByteArrayOutputStream,與之對應的操作字符類的類就是CharArrayReader,CharArrayWriter類,這裡也
會用到緩沖區,不過是字符緩沖區,一般講字符串放入到操作字符的io流一般方法是
CharArrayReaderreader=mew CharArrayReader(str.toCharArray());
一旦會去到CharArrayReader實例就可以使用CharArrayReader訪問字符串的各個元素以執行進一步讀取操作。不做例子
2.我們用FileReader ,PrintWriter來做示范
Java代碼
package com.hxw.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.CharBuffer;
public class Print {
/**
* @param args
*/
public static void main(String[] args) {
// TODO自動生成的方法存根
char[] buffer=new char[512]; //一次取出的字節數大小,緩沖區大小
int numberRead=0;
FileReader reader=null; //讀取字符文件的流
PrintWriter writer=null; //寫字符到控制台的流
try {
reader=new FileReader("D:/David/Java/java 高級進階/files/copy1.txt");
writer=new PrintWriter(System.out); //PrintWriter可以輸出字符到文件,也可以輸出到控制台
while ((numberRead=reader.read(buffer))!=-1) {
writer.write(buffer, 0, numberRead);
}
} catch (IOException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}finally{
try {
reader.close();
} catch (IOException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}
writer.close(); //這個不用拋異常
}
}
}
3.相對我們前面的例子是直接用FileReader打開的文件,我們這次使用鏈接流,一般比較常用的都用鏈接流,所謂鏈接流就是就多次
對流的封裝,這樣能更好的操作個管理數據,(比如我們利用
DataInputStream(BufferedInputStream(FileInputStream))將字節流層層包裝後,我們可以讀取
readByte(),readChar()這樣更加具體的操作,注意,該流屬於字節流對字符進行操作,)字符流用CharArrayReader就可以
了。下面的示例我們將用到j2se
5中的一個可變參數進行一個小度擴展。使用BufferedWriter
和BufferedReader用文件級聯的方式進行寫入,即將多個文件寫入到同一文件中(自帶緩沖區的輸出輸出流BufferedReader和
BufferedWriter,該流最常用的屬readLine()方法了,讀取一行數據,並返回String)。
Java代碼
package com.hxw.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
public class FileConcatenate {
/**
* 包裝類進行文件級聯操作
*/
public static void main(String[] args) {
// TODO自動生成的方法存根
try {
concennateFile(args);
} catch (IOException e) {
// TODO自動生成的 catch 塊
e.printStackTrace();
}
}
public static voidconcennateFile(String...fileName) throws IOException{
String str;
//構建對該文件您的輸入流
BufferedWriter writer=new BufferedWriter(new FileWriter("D:/David/Java/java 高級進階/files/copy2.txt"));
for(String name: fileName){
BufferedReader reader=new BufferedReader(new FileReader(name));
while ((str=reader.readLine())!=null) {
writer.write(str);
writer.newLine();
}
}
}
}
4.Console類,該類提供了用於讀取密碼的方法,可以禁止控制台回顯並返回char數組,對兩個特性對保證安全有作用,平時用的不多,了解就行。
5.StreamTokenizer 類,這個類非常有用,它可以把輸入流解析為標記(token), StreamTokenizer
並非派生自InputStream或者OutputStream,而是歸類於io庫中,因為StreamTokenizer只處理InputStream
對象。
首先給出我的文本文件內容:
'水上漂'
青青草
"i love wyhss"
{3211}
23223 3523
i love wyh ,。
. ,
下面是代碼:
Java代碼
package com.hxw.io;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.StreamTokenizer;
/**
* 使用StreamTokenizer來統計文件中的字符數
* StreamTokenizer 類獲取輸入流並將其分析為“標記”,允許一次讀取一個標記。
* 分析過程由一個表和許多可以設置為各種狀態的標志控制。
* 該流的標記生成器可以識別標識符、數字、引用的字符串和各種注釋樣式。
*
* 默認情況下,StreamTokenizer認為下列內容是Token: 字母、數字、除C和C++注釋符號以外的其他符號。
* 如符號"/"不是Token,注釋後的內容也不是,而"\"是Token。單引號和雙引號以及其中的內容,只能算是一個Token。
* 統計文章字符數的程序,不是簡單的統計Token數就萬事大吉,因為字符數不等於Token。按照Token的規定,
* 引號中的內容就算是10頁也算一個Token。如果希望引號和引號中的內容都算作Token,應該調用下面的代碼:
* st.ordinaryChar('\'');
* st.ordinaryChar('\"');
*/
public class StreamTokenizerExample {
/**
* 統計字符數
* @param fileName 文件名
* @return 字符數
*/
public static void main(String[] args) {
String fileName = "D:/David/Java/java 高級進階/files/copy1.txt";
StreamTokenizerExample.statis(fileName);
}
public static long statis(String fileName) {
FileReader fileReader = null;
try {
fileReader = new FileReader(fileName);
//創建分析給定字符流的標記生成器
StreamTokenizer st = new StreamTokenizer(new BufferedReader(
fileReader));
//ordinaryChar方法指定字符參數在此標記生成器中是“普通”字符。
//下面指定單引號、雙引號和注釋符號是普通字符
st.ordinaryChar('\'');
st.ordinaryChar('\"');
st.ordinaryChar('/');
String s;
int numberSum = 0;
int wordSum = 0;
int symbolSum = 0;
int total = 0;
//nextToken方法讀取下一個Token.
//TT_EOF指示已讀到流末尾的常量。
while (st.nextToken() !=StreamTokenizer.TT_EOF) {
//在調用 nextToken 方法之後,ttype字段將包含剛讀取的標記的類型
switch (st.ttype) {
//TT_EOL指示已讀到行末尾的常量。
case StreamTokenizer.TT_EOL:
break;
//TT_NUMBER指示已讀到一個數字標記的常量
case StreamTokenizer.TT_NUMBER:
//如果當前標記是一個數字,nval字段將包含該數字的值
s = String.valueOf((st.nval));
System.out.println("數字有:"+s);
numberSum ++;
break;
//TT_WORD指示已讀到一個文字標記的常量
case StreamTokenizer.TT_WORD:
//如果當前標記是一個文字標記,sval字段包含一個給出該文字標記的字符的字符串
s = st.sval;
System.out.println("單詞有: "+s);
wordSum ++;
break;
default:
//如果以上3中類型都不是,則為英文的標點符號
s = String.valueOf((char) st.ttype);
System.out.println("標點有: "+s);
symbolSum ++;
}
}
System.out.println("數字有 " + numberSum+"個");
System.out.println("單詞有 " + wordSum+"個");
System.out.println("標點符號有: " + symbolSum+"個");
total = symbolSum + numberSum +wordSum;
System.out.println("Total = " + total);
return total;
} catch (Exception e) {
e.printStackTrace();
return -1;
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e1) {
}
}
}
}
}
運行結果為:
標點有: '
單詞有: 水上漂
標點有: '
單詞有: 青青草
標點有: "
單詞有: i
單詞有: love
單詞有: wyh
單詞有: ss
標點有: "
標點有: {
數字有:3211.0
標點有: }
數字有:23223.0
數字有:35.23
單詞有: i
單詞有: love
單詞有: wyh
單詞有: ,。
數字有:0.0
標點有: ,
數字有 4個
單詞有 10個
標點符號有: 7個
Total= 21
我們從其中可以看到很多東西:
1.一個單獨的小數點“.”是被當做一個數字來對待的,數字的值為0.0;
2.一串漢字只要中間沒有符號(空格回車 分號等等)都是被當做一個單詞的。中文的標點跟中文的漢字一樣處理
3.如果不對引號化成普通字符,一個引號內的內容不論多少都被當做是一個標記。
4.該類能夠識別英文標點
6. java
io裡面還有其他接口類似Serializable接口的子接口Externalizable接口,比Serializable復雜一些,這裡不再介紹。
還有關於java對象版本化的東西感興趣的可以百度。java nio的東西這裡沒有涉及,後續會結合到線程再發一篇文章專門解析這個東西。