IO是java中很重要的一塊,包含了對文件,文件目錄,二進制流文件等各種資源的操作,同時IO操作複雜多變,用得好會提高性能,用的不好導致效率低下,所以詳細瞭解並掌握IO相關知識是很有必要的。
IO主要分爲字節流和字符流,然後每一種又分爲輸入流和輸出流,涉及到的類很多,下面一一分析。
字節流—-InputStream和OutputStream
先上類圖,InputStream這個大家庭的:
(1)文件流:FileInputStream
public static void testStream(){
try {
byte [] bytes = new byte [12];
FileInputStream fis = new FileInputStream(new File("D:\\javaFile\\test/hah.txt"));
while(fis.read(bytes)!=-1){
System.out.println("Each:"+new String(bytes));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
說明:FileInputStream是沒有緩衝功能的,但是上面的程序裏fis將讀出的字節放到了字節數組bytes中實現了緩存功能。
while(fis.read()!=-1) //這樣就沒有緩存功能,一個一個字節讀取
(2)對象流:ObjectInputStream
public static void testStream(){
try {
//爲了讀取,我們先寫一個文件,注意這個文件是二進制的,所以打開看是亂碼
FileOutputStream fos = new FileOutputStream("D:\\javaFile\\test\\student.txt");
ObjectOutputStream oop = new ObjectOutputStream(fos);
oop.writeObject(new Student(001 , "wangliang" , true , 22));
oop.writeObject(new Student(002 , "lisi" , false , 22));
//開始讀取
FileInputStream fis = new FileInputStream("D:\\javaFile\\test\\student.txt");
ObjectInputStream ons = new ObjectInputStream(fis);
//一次只能讀取一個Object對象出來
Student stu = (Student) ons.readObject();
System.out.println("age:"+stu.stuAge);
System.out.println("No:"+stu.stuId);
System.out.println("name:"+stu.stuName);
System.out.println("sex:"+(stu.stuSex==true?"男":"女"));
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
輸出結果如下:
age:22
No:1
name:wangliang
sex:男
從結果可以看出,我們寫入了兩個對象,但是隻讀出了一個,那麼應該怎樣讀出所有的對象呢?方法是循環讀取,用EOFException作爲邊界結束,像這樣:
try{
while(true){
Student stu = (Student) ons.readObject();
System.out.println("age:"+stu.stuAge);
System.out.println("No:"+stu.stuId);
System.out.println("name:"+stu.stuName);
System.out.println("sex:"+(stu.stuSex==true?"男":"女"));
}
}catch(EOFException e){}
如果你覺得上面的方式不那麼優雅的話,還可以這樣:
//如果插入是可控的,那麼可以在結尾插入一個結束標記符,一般爲null.但是如果還要追加數據的話就比較麻煩了。
oop.writeObject(null);
//然後在讀取的時候讀到這個結束符就判定結束了
while(true){
Student stu = (Student) ons.readObject();
if(stu == null) break;
System.out.println("age:"+stu.stuAge);
System.out.println("No:"+stu.stuId);
System.out.println("name:"+stu.stuName);
System.out.println("sex:"+(stu.stuSex==true?"男":"女"));
}
(3)字節數組流:ByteArrayInputStream
ByteArrayInputStream的兩個構造函數都需要接受一個byte數組,不同於其他流接受文件或者流。
//ByteArrayInputStream
byte[] byteArray = {'a','b',8,'c','d','e',-128,127,0,-1,2,'i'};
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
int tem;
while((tem = bais.read())!=-1){
//在讀取到8後,跳過後面的c,d,e三個字節
if(tem==8)
bais.skip(3);
System.out.println("byte:"+tem);
}
這個類我不知道存在的意義是什麼?在知乎上找到一個回答是說將byte數組轉換爲InputStream,從而可以使用一些InputStream的API,但是我個人覺得直接操作byte數組更方便啊。歡迎留言討論。
(4)字符流:StringBufferInputStream
這個用作將一個字符串轉換爲流對象,現在早已被標記out了,可以用 StringReader或者ByteArrayInputStream代替。
(5)順序合併流:SequenceInputStream
這個流的作用呢,就是將幾個流按照順序一個一個去讀取出來,可以保證順序。
//第一種寫法
FileInputStream fis = new FileInputStream("D:\\javaFile\\test\\hah.txt");
FileInputStream fis1 = new FileInputStream("D:\\javaFile\\test\\hah1.txt");
SequenceInputStream sis = new SequenceInputStream(fis , fis1);
byte[] temp = new byte [1024];
while(sis.read(temp)!=-1){
System.out.println("Sequence:"+new String(temp));
}
//第二種寫法
Enumeration en;
Vector<FileInputStream> vectors = new Vector<FileInputStream>();
vectors.addElement(fis1);
vectors.addElement(fis);
en = vectors.elements();
SequenceInputStream sis2 = new SequenceInputStream(en);
byte[] temp2 = new byte [1024];
while(sis2.read(temp2)!=-1){
System.out.println("Sequence2:"+new String(temp2));
}
(6)管道流:PipedInputStream和PipedOutputStream
這兩個必須配套使用,管道流用於跨線程數據交互。首先將PipedInputStream和PipedOutputStream通過connect方法綁定,然後在A線程中通過PipedOutputStream寫數據,此時,寫入的數據會發送到與之關聯的PipedInputStream中,在線程B中就可以讀取數據了。
//管道流測試
//創建輸入輸出流
final PipedOutputStream pos = new PipedOutputStream();
final PipedInputStream pis = new PipedInputStream();
try {
//關聯起來
pos.connect(pis);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//線程A發數據
new Thread(){
@Override
public void run(){
try {
pos.write("hello,i come from thread A!".getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
//線程B接受數據
new Thread(){
@Override
public void run(){
byte [] temp = new byte[27];
try {
pis.read(temp);
System.out.println("來自線程A的消息:"+new String(temp));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
(7)回退流:PushbackInputStream
在讀取下一個字節(或者下一個字節數組)前用unread推回流的前端,注意這個會影響流的mark和reset方法,使用者兩個方法前一定要先判斷。
//PushbackInputStream測試
try {
FileInputStream fis = new FileInputStream("D:\\javaFile\\test\\hah.txt");
//最多可以插入512個字節,是總共的字節數,不是一次的
PushbackInputStream pbis = new PushbackInputStream(fis ,30 );
byte [] temp3 = new byte[5];
while(pbis.read(temp3)!=-1){
if(new String(temp3).equals("hello")){
pbis.unread("i am contents".getBytes());
//continue;
}
System.out.println("PushbackInputStream:"+new String(temp3));
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
輸出結果是:hello,i am contents
(8)緩存流:BufferedInputStream
關於這個流需要好好說道說道;首先,看到這個流,再聯想到上面講FileInputStream時使用緩存時的場景,你可能會疑問有什麼區別呢?FileInputStream用read(byte [])也可以緩存啊。
第一:BufferedInputStream的read()方法會使用默認的byte[]來緩存,雖然還是一個字節一個字節的讀,但是是從緩存的byte[]裏面讀取了,而FileInputStream一個字節一個字節的直接從數據源讀取,IO操作增加了很多。
第二:BufferedInputStream存在的意義是封裝了mark和reset方法,如果有mark和reset的操作,用這個流比較好。
mark和reset
mark()是標記當前位置,reset可以回到標記的位置。光說不練假把式,上代碼:
//mark,reset測試
byte[] samplebyte = {'a','b','c','d','e','f','h','i','j','k'};
//將byte[]讀入ByteArrayInputStream
ByteArrayInputStream bis = new ByteArrayInputStream(samplebyte);
//用BufferedInputStream封裝ByteArrayInputStream
BufferedInputStream buffer = new BufferedInputStream(bis);
//開始讀取
try {
int curbyte;
//標記只回退一次
boolean haveMarked = false;
while((curbyte=buffer.read())!=-1){
//在讀取到c的時候mark,參數是限制最多字節數
if(curbyte == 'c' && !haveMarked){
if(buffer.markSupported()){
buffer.mark(5);
haveMarked = true;
}
}
System.out.print(curbyte+",");
}
//在讀取到h的時候回到標記的位置
if(buffer.markSupported())
buffer.reset();
int curbyte2 ;
while((curbyte2=buffer.read())!=-1){
System.out.print(curbyte2+":");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
結果可以看到,讀取完了以後,從我們mark的位置開始從新讀取(這裏的從新讀取不是又進行一次IO操作,而是用之前緩存的那個byte[],這樣就提高了效率):
97,98,99,100,101,102,104,105,106,107,100:101:102:104:105:106:107:
關於mark(int readlimit)的參數是表明讀取多少字節標記才失效,但是實際中是取readlimit和BufferedInputStream類的緩衝區(默認緩存大小byte[1024*8])大小兩者中的最大值,而並非完全由readlimit確定。
字符流
顧名思義了,字符流呢是以字符爲操作單位了。
還是先上類圖:
大部分類的用法和字節流的用法差不多,就不一一贅述了,只有一個LineNumberReader多了一個按行讀取的方法。
//LineNumberReader
try {
Reader fileReader2 = new FileReader("D:\\javaFile\\test\\hah.txt");
LineNumberReader lineNumberReader = new LineNumberReader(fileReader2);
String line;
while((line=lineNumberReader.readLine())!=null){
System.out.println("LineNumberReader:"+line);
}
} catch (IOException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
//CharArrayReader和BufferedReader
char[] char1 = {'a' ,1,'我',234,1234};
char[] tempchar;
BufferedReader bufferReader = new BufferedReader(new CharArrayReader(char1));
int temp;
try {
while((temp = bufferReader.read())!=-1){
System.out.println("char:"+temp);
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//FileReader
try {
FileInputStream fileInputStream = new FileInputStream("D:\\javaFile\\test\\hah.txt");
int bytecount = 0;
while(fileInputStream.read()!=-1){
bytecount++;
}
System.out.println("字節數:"+bytecount);
Reader fileReader = new FileReader("D:\\javaFile\\test\\hah.txt");
int count = 0;
while(fileReader.read()!=-1){
count++;
}
System.out.println("字符數:"+count);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}