概述
按流向分為輸入流和輸出流
按操作資料分為位元組流和字元流
字元流,基于位元組流,但是字元流對象融合了編碼表
文字用字元流,圖檔用位元組流
IO體系的四個基類
位元組流的抽象基類
InputStream OutputStream
字元流的抽象基類
Reader Writer
由這四個類派生出來的子類名稱都是以其父類名作為字尾,比如FileInputStream,FileReader
字首名是流對象的功能
字元流
字元寫入流FileWriter
父類Writer中的構造方法被protected修飾表示隻給子類通路 資料的最常見展現形式是:檔案 專門用于操作檔案的Writer子類對象:FileWriter(繼承OutputStreamWriter類)
//建立一個FileWriter對象,該對象一被建立就必須明确被操作的檔案,而且該檔案會被建立到指定的目錄下,如果該目錄下有同名檔案,會被覆寫:
FileWriter fw = new FileWriter("demo.txt"); //注意會抛出異常:比如檔案路徑不存在
//調用write方法,将字元串寫入到流中
fw.write("abcde");
//重新整理流對象中的緩沖,将其中的資料刷到目的地中
fw.flush();
//關閉流資源,但是關閉之前會重新整理一次内部的緩沖中的資料。因為Java會調用系統中的内容來完成資料的寫入,完成之後必須将資源釋放,是以close
//将資料刷到目的地中。
//和flush差別:flush重新整理後,流可以繼續使用,close重新整理後,會将流關閉。
fw.close();
IO異常
在IO體系中,IO異常時最為常見的 第一句發生異常,下面執行無意義,是以一起try
FileWriter fw = null;//在外面建立引用,在try裡初始化,如果都寫在try裡,關閉流對象會發生編譯錯誤,因為fw引用作用不到try以外
try
{
fw = new FileWriter("demo.txt");
fw.write("abcdefg");
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally //流關閉,必須執行
{
try//close也會發生異常,需要單獨用try處理
{
if(fw!=null)//不判斷會發生空指針異常
fw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
檔案的續寫
/*
示範對已有檔案的資料續寫。
*/
import java.io.*;
class FileWriterDemo3
{
public static void main(String[] args) throws IOException
{
//傳遞一個true參數,代表不覆寫已有的檔案。并在已有檔案的末尾處進行資料續寫。
FileWriter fw = new FileWriter("demo.txt",true);
fw.write("nihao\r\nxiexie");//為了在windows記事本下顯示換行,用到了\r\n,linux系統為\n
fw.close();
}
}
字元讀取流FileReader
Reader的close方法不重新整理,隻是關閉資源
方式一:讀取單個字元
//建立一個檔案讀取流對象,和指定名稱的檔案相關聯。
//要保證該檔案是已經存在的,如果不存在,會發生異常FileNotFoundException
FileReader fr = new FileReader("demo.txt");
//調用讀取流對象的read方法。
//read():一次讀一個字元。而且會自動往下讀。傳回的是作為整數讀取的字元
int ch = 0;
while((ch=fr.read())!=-1) //傳回-1表示檔案已經讀到末尾
{
System.out.println("ch="+(char)ch);
}
fr.close();
方式二:通過字元數組進行讀取
FileReader fr = new FileReader("demo.txt");
//定義一個字元數組。用于存儲讀到字元。
//該read(char[])傳回的是讀取的字元數。
char[] buf = new char[1024]; //通常定義1024的整數倍,這裡定義了一個2k大小的字元數組,因為一個字元是兩個位元組
int len = 0;
while((len=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,len));//讀到多少就打出多少,因為最後一次循環可能不會讀滿整個數組,如果隻打整個數組就會出現多餘的字元
}
fr.close();
總結:方式二好,讀一串存一下,再全部列印,效率高
拷貝文本檔案
//将C槽一個文本檔案複制到D盤。
/*
複制的原理:
其實就是将C槽下的檔案資料存儲到D盤的一個檔案中。
步驟:
1,在D盤建立一個檔案。用于存儲C槽檔案中的資料。
2,定義讀取流和C槽檔案關聯。
3,通過不斷的讀寫完成資料存儲。
4,關閉資源。
*/
import java.io.*;
class CopyText {
public static void main(String[] args) throws IOException {
copy_2();
}
//方式一:從C槽讀一個字元,就往D盤寫一個字元。(未加入IO異常處理,是以需要抛出異常對象)
public static void copy_1()throws IOException {
//建立目的地。
FileWriter fw = new FileWriter("RuntimeDemo_copy.txt");
//與已有檔案關聯。
FileReader fr = new FileReader("RuntimeDemo.java");
int ch = 0;
while((ch=fr.read())!=-1) {
fw.write(ch);
}
fw.close();
fr.close();
}
//方式二:利用緩存數組讀寫(加入了IO異常處理)
public static void copy_2() {
FileWriter fw = null;
FileReader fr = null;
try{
fw = new FileWriter("SystemDemo_copy.txt");
fr = new FileReader("SystemDemo.java");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1){ //使用數組進行讀取
fw.write(buf,0,len);//使用數組進行寫入
}
}
catch (IOException e){
throw new RuntimeException("讀寫失敗");
}
finally{
if(fr!=null)
try{
fr.close();
}catch (IOException e){
throw new RuntimeException("讀取流關閉失敗");
}
if(fw!=null)
try{
fw.close();
}catch (IOException e){
throw new RuntimeException("寫入流關閉失敗");
}
}
}
}
要點:read(buf)使用數組進行讀取,write(buf,0,len)使用數組進行寫入
字元流的緩沖區
緩沖區的出現是為了提高流的操作效率,是以在建立緩沖區對象之前,要有流對象 隻要将需要被提高效率的流對象作為參數傳遞給緩沖區的構造函數即可 緩沖區要結合流才可以使用,在流的基礎上對流的功能進行了增強 原理:緩沖區對象裡面封裝了數組,不需要再自定義數組了
BufferedWriter
字元寫入流緩沖區
該緩沖區對象提供了一個跨平台的換行符 newLine()方法
//建立一個字元寫入流對象。
FileWriter fw = new FileWriter("buf.txt");
//為了提高字元寫入流效率。加入了緩沖技術。
//隻要将需要被提高效率的流對象作為參數傳遞給緩沖區的構造函數即可。
BufferedWriter bufw = new BufferedWriter(fw);
for(int x=1; x<5; x++)
{
bufw.write("abcd"+x);
bufw.newLine();
bufw.flush(); //防止因為斷電導緻資料丢失,是以重新整理一句,寫入一句
}
//記住,隻要用到緩沖區,就要記得重新整理。
//bufw.flush();
//其實關閉緩沖區,就是在關閉緩沖區中的流對象,不用再關閉流對象了
bufw.close();
BufferedReader
字元讀取流緩沖區 該緩沖區提供了一個一次讀一行的方法 readLine(),友善于對文本資料的擷取。 當傳回null時,表示讀到檔案末尾。
readLine方法傳回的時候隻傳回回車符之前的資料内容。并不傳回回車符。
//建立一個讀取流對象和檔案相關聯。
FileReader fr = new FileReader("buf.txt");
//為了提高效率。加入緩沖技術。将字元讀取流對象作為參數傳遞給緩沖對象的構造函數。
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line);//注意這裡的列印加了換行符,因為readLine不傳回回車符
}
bufr.close();
簡寫形式
BufferedReader bufr = null;
BufferedWriter bufw = null;
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt"));
readLine()方法原理
無論是讀一行或者一下讀取多個字元,其原理都是在硬碟上一個一個讀取字元,是以最終還是使用的read方法一次讀一個,隻是先将讀取的資料存到數組中,當讀到換行符時,不存入數組,将數組已存儲的字元全部輸出
MyBufferedReader
/*
明白了BufferedReader類中特有方法readLine的原理後,
可以自定義一個類中包含一個功能和readLine一緻的方法。
來模拟一下BufferedReader
*/
import java.io.*;
class MyBufferedReader extends Reader //需要繼承Reader,因為裝飾類和被裝飾類屬于同一體系
{
private Reader r;//技巧:定義一個 是的在整個類中都有效
MyBufferedReader(Reader r)
{
this.r = r;
}
//可以一次讀一行資料的方法。
public String myReadLine()throws IOException //這裡不用try和catch異常,而隻将異常抛出,因為這裡定義的是供調用者使用的功能,應該讓調用者去處理異常
{
//定義一個臨時容器。原BufferReader封裝的是字元數組。
//為了示範友善。定義一個StringBuilder容器。因為最終還是要将資料變成字元串。
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=r.read())!=-1)
{
if(ch=='\r')//這裡不需要強轉
continue; //讀到該字元,不存入,繼續while的下一循環讀取下一個字元
if(ch=='\n')
return sb.toString();//讀到該字元傳回數組容器中的字元串形式
else
sb.append((char)ch);
}
if(sb.length()!=0) //緩沖區裡面隻要有資料,就把剩下的資料傳回來,避免了因為最後一行末尾沒有換行符而不能将最後一行輸出的問題
return sb.toString();
return null;
}
/*
覆寫Reader類中的抽象方法,因為繼承了該類
*/
public int read(char[] cbuf, int off, int len) throws IOException
{
return r.read(cbuf,off,len) ; //自己覆寫該類麻煩,可以用傳進來的子類來實作
}
public void close()throws IOException//需要覆寫抽象的close方法,因為每個流對象關閉實作不同,直接關閉資源的和調用流對象close方法的兩種
{
r.close();
}
public void myClose()throws IOException //關閉資源
{
r.close();
}
}
LineNumberReader
跟蹤行号的緩沖字元輸入流。有setLineNumber(int num)方法和getLineNumber方法設定開始行号和擷取行号
自定義跟蹤行号的緩沖字元輸入流MyLineNumberReader
import java.io.*;
class MyLineNumberReader extends MyBufferedReader//使用裝飾設計模式,繼承父類的功能,利用關鍵字super引用父類對象
{
private int lineNumber;
MyLineNumberReader(Reader r)
{
super(r);//父類中已經進行了傳遞,直接調用父類的功能,因為<span style="font-family: Arial, Helvetica, sans-serif;">當父類中沒有空參數的構造函數時,子類中必須手動通過super語句來顯示指定要通路的父類中的構造函數</span>
}
public String myReadLine()throws IOException
{
lineNumber++;//每讀取一行,就調用一次myReadLine方法,行号就自增一次。
return super.myReadLine();//父類中已經有了實作,直接調用
}
public void setLineNumber(int lineNumber)
{
this.lineNumber = lineNumber;
}
public int getLineNumber()
{
return lineNumber;
}
}
裝飾設計模式
當想要對已有的對象進行功能增強時,可以定義類,将已有對象傳入,基于已有的功能,并提供加強功能。
那麼自定義的該類稱為裝飾類。
裝飾類通常會通過構造方法接收被裝飾的對象。
并 基于被裝飾的對象的功能,提供更強的功能。是以該裝飾類需要繼承同一個父類,因為隻是功能增強,事物沒有變。
MyBufferedReader類使用了裝飾設計模式 原因:後期進行功能增強時,不建議改源代碼,是以用此設計模式更優化
裝飾和繼承的差別
使用繼承設計: MyReader//專門用于讀取資料的類。
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
|--MyBufferDataReader
未使用多态的裝飾設計:
class MyBufferReader
{
MyBufferReader(MyTextReader text)
{}
MyBufferReader(MyMediaReader media)
{}
}
上面這個類擴充性很差。
找到其參數的共同類型。通過多态的形式。可以提高擴充性:
class MyBufferReader extends MyReader
{
private MyReader r;
MyBufferReader(MyReader r)
{}
}
使用裝飾設計:
MyReader//專門用于讀取資料的類。
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader
以前是通過繼承将每一個子類都具備緩沖功能。
那麼繼承體系會複雜,并不利于擴充。
現在優化思想。單獨描述一下緩沖内容。
将需要被緩沖的對象。傳遞進來。也就是,誰需要被緩沖,誰就作為參數傳遞給緩沖區。
這樣繼承體系就變得很簡單。優化了體系結構。 裝飾模式比繼承要靈活。避免了繼承體系臃腫。
而且降低了類與類之間的關系。
裝飾類因為增強已有對象,具備的功能和已有的是相同的,隻不過提供了更強功能。是以裝飾類和被裝飾類通常是都屬于一個體系中的。 裝飾設計模式将 類與類的繼承關系變成了組合關系
位元組流
想要操作圖檔資料,就要用到位元組流 位元組流不需要重新整理,因為字元流需要先緩沖資料,然後查表解析,是以需要重新整理,而位元組流不需要,隻需要關閉流資源
位元組流下File的讀寫操作
import java.io.*;
class FileStream
{
public static void main(String[] args) throws IOException
{
readFile_3();
}
public static void readFile_3()throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
// int num = fis.available();//available方法傳回檔案中位元組數
byte[] buf = new byte[fis.available()];//定義一個剛剛好的緩沖區。不用在循環了。該方法慎用,當檔案資料過大時,會造成記憶體溢出!!
fis.read(buf);
System.out.println(new String(buf));
fis.close();
}
public static void readFile_2()throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
byte[] buf = new byte[1024]; //為readFile_1和readFile_3之間的折中方式
int len = 0;
while((len=fis.read(buf))!=-1)
{
System.out.println(new String(buf,0,len));
}
fis.close();
}
public static void readFile_1()throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
int ch = 0;
while((ch=fis.read())!=-1)//read方法傳回的是int型,如果是byte型,可能讀到8個1時就結束循環
{
System.out.println((char)ch);
}
fis.close();
}
public static void writeFile()throws IOException
{
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("abcde".getBytes()); //getBytes方法将字元串轉換為位元組數組
fos.close();
}
}
位元組流的緩沖區
BufferedInputStream BufferedOutputStream
/*
示範mp3的複制。通過緩沖區。
BufferedOutputStream
BufferedInputStream
*/
import java.io.*;
class CopyMp3
{
public static void main(String[] args) throws IOException
{
long start = System.currentTimeMillis();
copy_2();
long end = System.currentTimeMillis();
System.out.println((end-start)+"毫秒");//列印複制時間
}
//通過自定義的位元組流緩沖區(見下)完成複制
public static void copy_2()throws IOException
{
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3"));
int by = 0;
//System.out.println("第一個位元組:"+bufis.myRead());
while((by=bufis.myRead())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.myClose();
}
//通過位元組流的緩沖區完成複制。
public static void copy_1()throws IOException
{
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));
int by = 0;
while((by=bufis.read())!=-1)//read方法傳回的是int型,如果是byte型,可能讀到8個1時就結束循環
{
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
自定義位元組流緩沖區
先将硬碟上的資料通過FileInputStream的read方法存儲到位元組流緩沖區中,緩沖區大小有限制,然後通過緩沖區對象的read方法一次一個取出來
import java.io.*;
class MyBufferedInputStream
{
private InputStream in; //注意這裡傳的不是FileInputStream
private byte[] buf = new byte[1024*4];
private int pos = 0,count = 0;
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//一次讀一個位元組,從緩沖區(位元組數組)擷取。
public int myRead()throws IOException
{
//通過in對象讀取硬碟上資料,并存儲buf中。
if(count==0) //count等于0表示buf中無資料,需要重新從硬碟讀取資料
{
count = in.read(buf);
if(count<0)//表示硬碟資料讀取完畢
return -1;
pos = 0;//将指針歸零,傳回數組第一個元素
byte b = buf[pos];
count--;
pos++;
return b&255;//與上255,在原位元組資料的前面補上了24個0,将讀取的byte轉為int
}
else if(count>0)
{
byte b = buf[pos];
count--;
pos++;
return b&0xff;//0xff是255的16進制表示
}
return -1;//這個return語句為了保證有傳回值,無其他意義
}
public void myClose()throws IOException
{
in.close();
}
}
結論:
自定義位元組流緩沖區的讀一個位元組的read方法為什麼傳回值類型不是byte,而是int。
因為有可能會讀到連續8個二進制1的情況,8個二進制1對應的十進制是-1.
那麼就會資料還沒有讀完,就結束的情況。因為我們判斷讀取結束是通過結尾标記-1來确定的。
是以,為了避免這種情況将讀到的位元組進行int類型的提升。
并在保留原位元組資料的情況前面了補了24個0,變成了int類型的數值。
而在寫入資料時,隻寫該int類型資料的最低8位。 寫入的write方法自動做了強轉操作,否則檔案會變大。
讀取轉換流
引子:讀取鍵盤錄入
System.out:對應的是标準輸出裝置,控制台。
System.in:對應的标準輸入裝置:鍵盤。
并且查詢到該in是一個InputStream,out是一個OutputStream的子類
/*
需求:
通過鍵盤錄入資料。
當錄入一行資料後,就将該行資料進行列印。
如果錄入的資料是over,那麼停止錄入。
*/
import java.io.*;
class ReadIn
{
public static void main(String[] args) throws IOException
{
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true)
{
int ch = in.read();//調用read方法一次就會将鍵盤錄入的字元讀取一次!
if(ch=='\r')
continue;
if(ch=='\n')
{
String s = sb.toString();
if("over".equals(s))
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length());//StringBuilder的清空
}
else
sb.append((char)ch);
}
}
}
InputStreamReader和OutputStreamWriter
通過剛才的鍵盤錄入一行資料并列印其大寫,發現其實就是讀一行資料的原理。
也就是readLine方法。
能不能直接使用readLine方法來完成鍵盤錄入的一行資料的讀取呢?
readLine方法是字元流BufferedReader類中的方法。
而鍵盤錄入的read方法是位元組流InputStream的方法。
那麼能不能将位元組流轉成字元流在使用字元流緩沖區的readLine方法呢?
此時需要用到 轉換流 InputStreamReader是 位元組流通向字元流的橋梁 OutputStreamWriter是 字元流通向位元組流的橋梁
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//擷取鍵盤錄入對象。
//InputStream in = System.in;
//将位元組流對象轉成字元流對象,使用轉換流。InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);
//為了提高效率,将字元串進行緩沖區技術高效操作。使用BufferedReader
//BufferedReader bufr = new BufferedReader(isr);
//讀取鍵盤錄入的最常見寫法。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
// OutputStream out = System.out;
// OutputStreamWriter osw = new OutputStreamWriter(out);
// BufferedWriter bufw = new BufferedWriter(osw);
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();//字元輸出流用到了緩沖區,需要重新整理
}
bufr.close();
bufw.close();
}
}
轉換流可以指定編碼表
流操作的規律
三種情形
1,
源:鍵盤錄入。
目的:控制台。
2,需求:想把鍵盤錄入的資料存儲到一個檔案中。
源:鍵盤。
目的:檔案。
3,需求:想要将一個檔案的資料列印在控制台上。
源:檔案。
目的:控制台。
流操作的基本規律(三個明确)
最痛苦的就是流對象有很多,不知道該用哪一個。
通過三個明确來完成。
1,明确源和目的。
源:輸入流。InputStream Reader
目的:輸出流。OutputStream Writer。
2,操作的資料是否是純文字。
是:字元流。
不是:位元組流。
3,當體系明确後,在明确要使用哪個具體的對象。
通過裝置來進行區分:
源裝置:記憶體,硬碟。鍵盤
目的裝置:記憶體,硬碟,控制台。 例:
需求:将一個文本檔案中資料存儲到另一個檔案中。複制檔案。
1,源:因為是源,是以使用讀取流。InputStream Reader
2,是不是操作文本檔案。
是!這時就可以選擇Reader
這樣體系就明确了。
3,接下來明确要使用該體系中的哪個對象。
明确裝置:硬碟。一個檔案。
Reader體系中可以操作檔案的對象是 FileReader
是否需要提高效率:是!加入Reader體系中緩沖區 BufferedReader. FileReader fr = new FileReader("a.txt"); BufferedReader bufr = new BufferedReader(fr);
目的:OutputStream Writer
是否是純文字。
是!Writer。
裝置:硬碟,一個檔案。
Writer體系中可以操作檔案的對象FileWriter。
是否需要提高效率:是!。加入Writer體系中緩沖區 BufferedWriter FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
---------------------------------------
需求:将鍵盤錄入的資料儲存到一個檔案中。
這個需求中有源和目的都存在。
那麼分别分析
源:InputStream Reader
是不是純文字?是!Reader
裝置:鍵盤。對應的對象是System.in.
不是選擇Reader嗎?System.in對應的不是位元組流嗎?
為了操作鍵盤的文本資料友善。轉成字元流按照字元串操作是最友善的。
是以既然明确了Reader,那麼就将System.in轉換成Reader。
用了Reader體系中轉換流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
需要提高效率嗎?需要!BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream Writer
是否是存文本?是!Writer。
裝置:硬碟。一個檔案。使用 FileWriter。
FileWriter fw = new FileWriter("c.txt");
需要提高效率嗎?需要。
BufferedWriter bufw = new BufferedWriter(fw);
**************
擴充一下,想要把錄入的資料按照指定的編碼表(utf-8),将資料存到檔案中。
目的:OutputStream Writer
是否是存文本?是!Writer。
裝置:硬碟。一個檔案。使用 FileWriter。
但是FileWriter是使用的預設編碼表。GBK. 同樣FileReader把碼表寫死了,當想要用指定的碼表讀資料時,就要用到FileReader的父類轉換流InputStreamReader
但是存儲時,需要加入指定編碼表utf-8。而指定的編碼表隻有轉換流可以指定。
是以要使用的對象是OutputStreamWriter。
而該轉換流對象要接收一個位元組輸出流。而且還可以操作的檔案的位元組輸出流。FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效嗎?需要。
BufferedWriter bufw = new BufferedWriter(osw);
是以,記住。轉換流什麼使用。字元和位元組之間的橋梁,通常,涉及到字元編碼轉換時,
需要用到轉換流。
改變标準輸入輸出裝置
通過System類中的兩個方法setIn()和setOut()來改變 static void setIn(InputStream in)重新配置設定“标準”輸入流。 static void setOut(PrintStream out) 重新配置設定“标準”輸出流。
IO流的應用
異常的日志資訊
import java.io.*;
import java.util.*;
import java.text.*;
class ExceptionInfo
{
public static void main(String[] args)throws IOException
{
try
{
int[] arr = new int[2];
System.out.println(arr[3]);
}
catch (Exception e)
{
try
{
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
PrintStream ps = new PrintStream("exeception.log");
ps.println(s);
System.setOut(ps);
}
catch (IOException ex)
{
throw new RuntimeException("日志檔案建立失敗");
}
e.printStackTrace(System.out);
}
}
}
//log4j 網上提供的專門針對建立異常資訊的java包
系統資訊
import java.util.*;
import java.io.*;
class SystemInfo
{
public static void main(String[] args) throws IOException
{
Properties prop = System.getProperties();
//System.out.println(prop);
prop.list(new PrintStream("sysinfo.txt"));
}
}
File類
用于将檔案或檔案夾封裝成對象,友善對檔案或者檔案夾的屬性資訊進行操作 流隻能操作資料 File對象可以作為參數傳遞給流的構造函數
File對象的建立
//将a.txt封裝成file對象。可以将已有的和未出現的檔案或者檔案夾封裝成對象。
File f1 = new File("a.txt"); //此例傳入相對路徑
//将父目錄和檔案名分别傳入
File f2 = new File("c:\\abc","b.txt");
//将父目錄封裝成對象傳入
File d = new File("c:\\abc");
File f3 = new File(d,"c.txt");
sop("f1:"+f1);//列印出相對路徑
sop("f2:"+f2);//列印出絕對路徑
sop("f3:"+f3);
//通過File類的seperator字段擷取系統的目錄分隔符,實作跨平台操作
File f4 = new File("c:"+File.separator+"abc"+File.separator+"zzz"+File.separator+"a.txt");
File對象功能
建立。
boolean createNewFile():在指定位置建立檔案,如果該 檔案已經存在,則不建立,傳回false。會抛出IO異常
和輸出流不一樣, 輸出流對象一建立建立檔案。而且檔案已經存在,會覆寫。是以該方式更合理 static File createTempFile(String prefix, String suffix); 在預設臨時檔案目錄中建立一個空檔案,使用給定字首和字尾生成其名稱 static File createTempFile(String prefix, String suffix, File directory); 在指定目錄中建立一個新檔案,一般将其建立在system32目錄下
boolean mkdir():建立檔案夾/目錄。不能包括不存在的父目錄
//建立單個檔案夾
File dir = new File("abc\\kkk\\a");//目錄abc\\kkk已經存在
sop("mkdir:"+dir.mkdir());
boolean mkdirs():建立多級檔案夾。在File對象封裝時進行設定:new File("abc\\kk\\dd");
//建立多級檔案夾
File dir = new File("abc\\kkk\\a\\a\\dd\\ee\\qq\\aaa");
sop("mkdir:"+dir.mkdirs());
删除。
boolean delete():删除失敗傳回false。如果檔案正在被使用,則删除不了傳回false。
void deleteOnExit();在程式退出時删除指定檔案。即使發生異常,也會删除
判斷。
boolean canExecute(): 檔案是否可執行 boolean canRead(); boolean canWrite(); int compareTo(File pathname);按字母順序比較兩個抽象路徑名。按大小比較需要自定義比較器
boolean exists() :檔案是否存在. 在用流操作檔案時可以先進行判斷,這就是把檔案封裝成對象的好處 在判斷檔案對象是否是檔案或者目錄時,必須要先通過exists方法判斷該檔案對象封裝的内容是否存在,存在即有确定類型
isFile():
isDirectory();
isHidden();
isAbsolute();不論檔案存不存在,都可以判斷該路徑是否為絕對路徑
擷取資訊。
getName():
getPath():
getParent(): 擷取父目錄。隻要沒有明确指定上一層目錄,傳回的是null,錄入abc\\file.txt和c:\\abc\\file.txt都可以
String getAbsolutePath(): 不論檔案是否存在,傳回此抽象路徑名的絕對路徑名字元串 File getAbsoluteFile(); 傳回此抽象路徑名的絕對路徑名形式,把傳回的絕對路徑封裝成了一個字元串,可以和上一個方法互相轉換
long lastModified() ;傳回此抽象路徑名表示的檔案最後一次被修改的時間
long length() ;傳回檔案的大小
boolean renameTo(File dest)重新命名此抽象路徑名表示的檔案,可以在不同盤附間移動
檔案清單
static File [ ] listRoots():傳回可用的檔案系統的根(即機器裡有效的盤符) String [ ] list():傳回指定目前目錄下的檔案或者檔案夾的名稱,包含隐藏檔案,當該方法所屬對象為一個檔案時,傳回的是null。調用list方法的對象必須是封裝了一個目錄,該目錄還必須存在 String [ ] list(FilenameFilter, filter): 傳回滿足指定過濾器的檔案和目錄 java.io.FilenameFilter 為一個接口,該接口内部有一個方法boolean accept(File dir, String name):測試指定檔案是否應該包含在指定清單中。dir:被找到的檔案所在的目錄,name:檔案的名稱
File dir = new File("d:\\java1223\\day18");
String[] arr = dir.list(new FilenameFilter()
{
public boolean accept(File dir,String name)
{
return name.endsWith(".bmp");
}
});
System.out.println("len:"+arr.length);
for(String name : arr)
{
System.out.println(name);
}
File [ ] listFiles(); File [ ] listFiles(FileFilter, filter) File [ ] listFiles(FilenameFilter, filter) 這個方法傳回的都是File對象數組
列出目錄下所有内容
列出指定目錄下的檔案或者檔案夾,包括子目錄的内容
遞歸
遞歸會不斷開辟遞歸函數的空間,如下圖:
遞歸要注意的:
- 限定條件
- 注意遞歸的次數,避免記憶體溢出
層級目錄
列出指定目錄下檔案或者檔案夾,包含子目錄中的内容。也就是列出指定目錄下所有内容。
//該方法傳回該級檔案或檔案夾前面所插入的字元串
public static String getLevel(int level){
StringBuilder sb = new StringBuilder();
sb.append("|--");
for(int i=0;i<level;i++){
sb.insert(0, " ");//從StringBuilder前面插入
}
return sb.toString();
}
private static void listAll(File file, int level) {
System.out.println(getLevel(level)+file.getName());
level++;
File[] files = file.listFiles();
for(File f: files){
if(f.isDirectory()){
listAll(f,level);
}
else
System.out.println(getLevel(level)+f.getName());
}
}
删除帶内容的目錄
删除原理:在windows中,删除目錄是從裡面往外删除的,需要用到遞歸 java的删除不走資源回收筒,系統隐藏的檔案有的删不了,是以在判斷時确定是非隐藏檔案
public static void removeDir(File dir)
{
File[] files = dir.listFiles();
for(int x=0; x<files.length; x++)
{
if(files[x].isDirectory())
removeDir(files[x]);
else
System.out.println(files[x].toString()+":-file-:"+files[x].delete());
}
System.out.println(dir+"::dir::"+dir.delete());
}
建立java檔案清單
将一個指定目錄下的java檔案的絕對路徑,存儲到一個文本檔案中(資料持久化)。
建立一個java檔案清單檔案。
思路:
1,對指定的目錄進行遞歸。
2,擷取遞歸過程所有的java檔案的路徑。
3,将這些路徑存儲到集合中。
4,将集合中的資料寫入到一個檔案中。
import java.io.*;
import java.util.*;
class JavaFileList
{
public static void main(String[] args) throws IOException
{
File dir = new File("d:\\java1223");
List<File> list = new ArrayList<File>();
fileToList(dir,list);
//System.out.println(list.size());
File file = new File(dir,"javalist.txt");
writeToFile(list,file.toString());
}
public static void fileToList(File dir,List<File> list)
{
File[] files = dir.listFiles();
for(File file : files)
{
if(file.isDirectory())
fileToList(file,list);
else
{
if(file.getName().endsWith(".java"))
list.add(file);
}
}
}
public static void writeToFile(List<File> list,String javaListFile)throws IOException
{
BufferedWriter bufw = null;
try
{
bufw = new BufferedWriter(new FileWriter(javaListFile));
for(File f : list)
{
String path = f.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw e;
}
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw e;
}
}
}
}
Properties
Properties是Hashtable的子類,也就是說它具備Map集合的特點,而且它裡面存儲的鍵值對都是字元串 是集合中和IO技術相結合的集合容器 該對象的特點可用于鍵值對形式的配置檔案 在加載資料時,需要資料有固定格式:鍵=值。
設定和擷取元素
Properties prop = new Properties();//建立對象
prop.setProperty("zhangsan","30");
String value = prop.getProperty("lisi");//根據鍵名擷取值
prop.setProperty("zhangsan",89+"");//修改
Set<String> names = prop.stringPropertyNames();//傳回簡明的set集合,該方法1.6才有效
for(String s : names)
{
System.out.println(s+":"+prop.getProperty(s));
}
存取配置檔案
将流中的資料存儲到集合中
//想要将info.txt中鍵值資料存到集合中進行操作。
/*
1,用一個流和info.txt檔案關聯。
2,讀取一行資料,将該行資料用"="進行切割。
3,等号左邊作為鍵,右邊作為值。存入到Properties集合中即可。
*/
public static void method_1()throws IOException
{
BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
String line = null; //用line記錄讀取到的一行字元串資料
Properties prop = new Properties();
while((line=bufr.readLine())!=null)
{
String[] arr = line.split("=");//用等号分割,傳回一個字元串數組
prop.setProperty(arr[0],arr[1]);
}
bufr.close();//注意關閉流
System.out.println(prop);
}
load方法
從輸入流中讀取屬性清單(鍵和元素對) void load(InputStream inStream) 從輸入字元流中讀取屬性清單(鍵和元素對)jdk1.6
void load(Reader reader) 以上代碼為該方法原理
store方法
将Properties表中的屬性清單(鍵和元素對)寫入輸出流 void store(OutputStream out, String comments)//comments為注釋,用“#”标記 将Properties表中的屬性清單(鍵和元素對)寫入輸出字元流 jdk1.6
void store(Writer writer, String comments)
list方法
void list(PrintStream out) void list(PrintWriter out)
将屬性清單輸出到指定輸出流
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");
//将流中的資料加載進集合。
prop.load(fis);
prop.setProperty("wangwu","39");
FileOutputStream fos = new FileOutputStream("info.txt");
//将集合中的資料寫入輸出流
prop.store(fos,"haha");
prop.list(System.out);
fos.close();
fis.close();
練習:限制程式運作次數。當運作次數到達5次時,給出,請您注冊的提示。并不再讓該程式執行。
/*
用于記錄應用程式運作次數。
如果使用次數已到,那麼給出注冊提示。
很容易想到的是:計數器。
可是該計數器定義在程式中,随着程式的運作而在記憶體中存在,并進行自增。
可是随着該應用程式的退出,該計數器也在記憶體中消失了。
下一次在啟動該程式,又重新開始從0計數。
這樣不是我們想要的。
程式即使結束,該計數器的值也存在。
下次程式啟動會先加載該計數器的值并加1後在重新存儲起來。
是以要建立一個配置檔案。用于記錄該軟體的使用次數。
該配置檔案使用鍵值對的形式。
這樣便于閱讀資料,并操作資料。
鍵值對資料是map集合。
資料是以檔案形式存儲,使用io技術。
那麼map+io -->properties.
配置檔案可以實作應用程式資料的共享。
*/
import java.io.*;
import java.util.*;
class RunCount
{
public static void main(String[] args) throws IOException
{
Properties prop = new Properties();
//用File來對象化檔案,那麼檔案不存在則建立
File file = new File("count.ini"); //此處采用相對路徑,因為絕對路徑會因為盤符不存在而出現IO異常
if(!file.exists())
file.createNewFile();
FileInputStream fis = new FileInputStream(file);
prop.load(fis);
int count = 0;
String value = prop.getProperty("time");
if(value!=null)
{
count = Integer.parseInt(value);
if(count>=5)
{
System.out.println("您好,使用次數已到,拿錢!");
return ; //用該語句直接終止程式的運作
}
}
count++;//當value不為空但又小于5時,外層if代碼塊執行完之後就會執行此語句保證count的續增
prop.setProperty("time",count+"");
FileOutputStream fos = new FileOutputStream(file);//輸出流對象不能在前面建立,否則會出現次數無法遞增的問題!
prop.store(fos,"");
fos.close();
fis.close();
}
}
配置檔案有.properties和.xml,properties檔案存儲鍵值對結構簡單,xml格式可以存儲複雜結構的資料 properties的鍵值對格式: name=zhangsan
age=20
xml格式:
<persons>
<person id="001">
<name>zhagnsan</name>
<age>30</age>
<address>bj</address>
</person>
<person>
<name
</person>
</persons>
可以用org.w3c.dom中的Document接口來代表整個HTML或XML文檔,但是此方法很麻煩, 可以采用dom4j
列印流
該流提供了列印方法,可以将各種資料類型的資料都原樣列印。
位元組列印流 PrintStream
構造函數可以接收的參數類型:
1,file對象。File
2,字元串路徑。String
3,位元組輸出流。OutputStream
字元列印流 PrintWriter
更常用 構造函數可以接收的參數類型:
1,file對象。File,還可以傳入String csn建立具有指定檔案和字元集的新PrinterWriter
2,字元串路徑。String
3, 位元組輸出流。OutputStream,還可以傳入boolean autoFlush可以建立帶自動行重新整理的的新PrintWriter
4,字元輸出流,Writer。可帶自動重新整理
//讀取鍵盤錄入的标準語句
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
//PrintWriter out = new PrintWriter(System.out,true);//列印到控制台
//PrintWriter out = new PrintWriter("a.txt");//直接存入檔案,後面不能寫重新整理,因為重新整理隻能針對流而言
PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true);//在檔案裡面自動重新整理,就将檔案封裝進流中
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());
}
//out.flush();
}
out.close();
bufr.close();
序列流
将多個讀取流合并成一個流
構造方法
多個流合并
SequenceInputStream(Enumeration<? extends InputStream> e) 通過記住參數來初始化新建立的 SequenceInputStream,該參數必須是生成運作時類型為 InputStream 對象的 Enumeration 型參數(可通過Vector獲得)。
兩個流合并
SequenceInputStream(InputStream s1, InputStream s2) 通過記住這兩個參數來初始化新建立的 SequenceInputStream(将按順序讀取這兩個參數,先讀取 s1,然後讀取 s2),以提供從此 SequenceInputStream 讀取的位元組。
執行個體:把三個檔案的資料變成一個檔案
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("c:\\1.txt"));
v.add(new FileInputStream("c:\\2.txt"));
v.add(new FileInputStream("c:\\3.txt"));
Enumeration<FileInputStream> en = v.elements();//vector的elements方法傳回此向量的元件的枚舉
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("c:\\4.txt");
byte[] buf = new byte[1024];
int len =0;
while((len=sis.read(buf))!=-1) //read方法傳回讀取的字元的長度
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
切割合并檔案
public static void merge()throws IOException
{
//Vector執行效率低,此處用ArrayList
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for(int x=1; x<=3; x++)
{
al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));
}
//因為是匿名内部類,是以要對通路的局部變量進行final修飾,規定的
final Iterator<FileInputStream> it = al.iterator();
//使用ArrayList的疊代器覆寫Enumneration匿名内部類的兩個方法建立一個Enumneration對象
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public FileInputStream nextElement()
{
return it.next();
}
};
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("c:\\splitfiles\\0.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
public static void splitFile()throws IOException
{
FileInputStream fis = new FileInputStream("c:\\1.bmp");
FileOutputStream fos = null;
byte[] buf = new byte[1024*1024];//當操作較大資料時,為了防止記憶體溢出,可以用每一個流對象操作100M資料
int len = 0;
int count = 1;
while((len=fis.read(buf))!=-1)
{
fos = new FileOutputStream("c:\\splitfiles\\"+(count++)+".part");
fos.write(buf,0,len);
fos.close();
}
fis.close();
}
對象的序列化
把堆記憶體中的對象以位元組形式存儲在硬碟上,也叫對象的序列化或持久化 兩個對象要成對使用完成讀寫
ObjectOutputStream
寫入對象 javac *.java可以一次編譯多個java檔案
writeInt()方法可以把int類型的4個8位全部寫出,而write方法隻能寫最低8位(即一個位元組)
public static void writeObj()throws IOException
{
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi0",399,"kr"));
oos.close();
}
ObjectInputStream
讀取對象
public static void readObj()throws Exception//這裡會抛出ClassNotFoundException和類沒有序列化異常,是以抛出Exception
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
所序列化的類,此例為Person必須實作Serializable接口,實作該接口不需要覆寫方法,沒有方法的接口稱之為“ 标記接口” 序列化運作時使用一個稱為 serialVersionUID的版本号與每個可序列化類相關聯,用于驗證序列化對象的發送者和接收者是否為同一對象。如果接收者加載的該對象的類的 serialVersionUID與對應的發送者的類的版本号不同,則反序列化将會導緻 InvalidClassException。可序列化類可以通過聲明名為"serialVersionUID"的字段(該字段必須是靜态 (static)、最終 (final)的 long 型字段)顯式聲明其自己的serialVersionUID,如: public static final long serialVersionUID = 42L;
import java.io.*;
class Person implements Serializable
{
public static final long serialVersionUID = 42L; //不讓java自動生成uid,通過自定義uid
private String name;
transient int age; //對非靜态的成員也不使其序列化,則加上transient關鍵字,即使它在堆記憶體中
static String country = "cn"; //靜态成員是不能被序列化的,因為靜态成員在方法區裡面,是以不論構造Person時傳入何值,讀取的時候都是cn
Person(String name,int age,String country)
{
this.name = name;
this.age = age;
this.country = country;
}
public String toString()
{
return name+":"+age+":"+country;
}
}
transient關鍵字可以保證非靜态成員變量在堆記憶體中存在而不在文本檔案中存在 一般存儲對象的檔案将其命名為“XXX.object”
可以一次寫多個對象,讀得時候按順序自動讀出
管道流
PipedInputStream和PipedOutputStream 輸入輸出可以直接進行連接配接 通過結合多線程使用。資料由某個線程寫入PipedOutputStream對象,并由其他線程從PipedInputStream對象讀取
import java.io.*;
//讀取線程
class Read implements Runnable
{
private PipedInputStream in;
Read(PipedInputStream in)
{
this.in = in;
}
public void run()
{
try//此處不能抛異常,必須處理異常,因為覆寫了run方法
{
byte[] buf = new byte[1024];
System.out.println("讀取前。。沒有資料阻塞");
int len = in.read(buf);
System.out.println("讀到資料。。阻塞結束");
String s= new String(buf,0,len);
System.out.println(s);
in.close();
}
catch (IOException e)
{
throw new RuntimeException("管道讀取流失敗");
}
}
}
//輸出線程
class Write implements Runnable
{
private PipedOutputStream out;
Write(PipedOutputStream out)
{
this.out = out;
}
public void run()
{
try
{
System.out.println("開始寫入資料,等待6秒後。");
Thread.sleep(6000);
out.write("piped lai la".getBytes());//write方法寫入的一定是位元組型資料
out.close();
}
catch (Exception e)
{
throw new RuntimeException("管道輸出流失敗");
}
}
}
class PipedStreamDemo
{
public static void main(String[] args) throws IOException
{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);//可以使用connect方法來連接配接兩個流對象,也可以使用構造函數傳遞參數連接配接兩個流對象
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
RandomAccessFile
随機通路檔案,自身具備讀寫的方法,結尾處沒有父類名
通過skipBytes(int x),seek(int x)來達到随機通路,前提是保證資料的存儲是有規律的
該類不算是IO體系中子類。
而是直接繼承自Object。
但是它是IO包中成員。因為它具備讀和寫功能。
内部封裝了一個數組(以檔案的資料的一個位元組為機關),而且通過指針對數組的元素進行操作。
可以通過getFilePointer擷取指針位置,
同時可以通過seek改變指針的位置。
其實完成讀寫的原理就是内部封裝了位元組輸入流和輸出流。
通過構造函數可以看出,該類隻能操作檔案。不能操作鍵盤錄入輸出等其他形式
而且操作檔案還有模式:隻讀r,讀寫rw等。
如果模式為隻讀 r。不會建立檔案。會去讀取一個已存在檔案,如果該檔案不存在,則會出現異常。
如果模式rw。操作的檔案不存在,會自動建立。如果存在則不會覆寫。
public static void readFile()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
//調整對象中指針。至第8個位元組。數組的前後都能指定
//raf.seek(8*1);
//跳過指定的位元組數
raf.skipBytes(8);//隻能在數組中往下跳
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();//readInt方法一次讀4個位元組
System.out.println("name="+name);
System.out.println("age="+age);
raf.close();
}
public static void writeFile_2()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
raf.seek(8*0); //将指針定位在數組開頭,會根據寫入資料的大小從開頭覆寫資料
raf.write("周期".getBytes());
raf.writeInt(103);
raf.close();
}
public static void writeFile()throws IOException//該方法寫入會覆寫原有檔案資料
{
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");//rw代表“讀寫”模式
/*raf.write("2".getBytes());
raf.write(258);//此時會取258的最低8位寫出
raf.writeInt(258);//此時會取258的4個位元組寫出 */
raf.write("李四".getBytes());
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
raf.close();
}
可以實作讀取,寫入,修改操作
該類可以實作多線程的下載下傳檔案。每個線程負責檔案的一段資料的下載下傳,保證下載下傳後資料的完整性
為了保證資料的規律性,可以進行定義,比如,姓名16個位元組,年齡4個位元組,空的位元組會拿空補位
操作基本資料類型的流對象
DataInputStream和DataOutputStream
可以用于操作基本資料類型的資料的流對象
import java.io.*;
class DataStreamDemo
{
public static void main(String[] args) throws IOException
{
//writeData();
//readData();
//writeUTFDemo();
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk"); //使用轉換流指定編碼表gbk
//
// osw.write("你好");
// osw.close();
// readUTFDemo();
}
public static void readUTFDemo()throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt"));
String s = dis.readUTF();//讀取時應使用相應的方法,否則讀取錯誤
System.out.println(s);
dis.close();
}
public static void writeUTFDemo()throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdate.txt"));
dos.writeUTF("你好");//該方法使用修改過的UTF表寫入資料
dos.close();
}
public static void readData()throws IOException//可以讀取基本資料類型
{
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
System.out.println("num="+num);
System.out.println("b="+b);
System.out.println("d="+d);
dis.close();
}
public static void writeData()throws IOException//可以寫入基本資料類型
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));//注意傳入輸出流對象
dos.writeInt(234);
dos.writeBoolean(true);
dos.writeDouble(9887.543);
dos.close();
ObjectOutputStream oos = null;
oos.writeObject(new O());
}
記憶體為源和目的的操作
操作位元組數組的流對象
ByteArrayInputStream和ByteArrayOutputStream
用于操作位元組數組的流對象。ByteArrayInputStream :在構造的時候,需要接收資料源,。而且資料源是一個位元組數組。ByteArrayOutputStream: 在構造的時候,不用定義資料目的,因為該對象中已經内部封裝了可變長度的位元組數組。這就是資料目的地。因為這兩個流對象都操作的數組,并沒有使用系統資源。不用抛IO異常是以,不用進行close關閉。
在流操作規律講解時:
源裝置:鍵盤 System.in,硬碟 FileStream,記憶體 ArrayStream。
目的裝置:控制台 System.out,硬碟FileStream,記憶體 ArrayStream。
其實就是用流的讀寫思想來操作數組。
public static void main(String[] args)
{
//資料源。
ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".getBytes());
//資料目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();//内部已封裝了一個數組,不用定義目的
int by = 0;
while((by=bis.read())!=-1)//不用再判斷數組長度,因為都是在記憶體中進行
{
bos.write(by);//将讀到的位元組資料寫入位元組數組中
}
System.out.println(bos.size());//擷取目前緩沖區的大小
System.out.println(bos.toString());//使用平台預設的字元集,通過解碼位元組将緩沖區的内容轉換為字元串
// bos.writeTo(new FileOutputStream("a.txt"));//一次性把數組中的資料寫到一個輸出流中,但此時需要抛IO異常或者捕獲該異常
}
操作字元數組
CharArrayReader和CharArrayWriter
類似位元組數組
操作字元串
StringReader和StringWriter
類似位元組數組
字元編碼
概述
字元流的出現是為了操作字元,更重要的是加入了位元組與字元之間的編碼轉換,通過子類的轉換流來完成:InputStreamReader和OutputStreamWriter,在這兩個對象進行構造的時候可以指定字元集
可以加入編碼表的對象還有:PrintStream和PrintWriter,但是隻能輸出列印,不能讀取
編碼表的由來:計算機隻能識别二進制資料,早期隻能識别電信号,為了友善應用計算機,讓它可以識别各國的文字,就将各國的文字用數字來表示,并一一對應,形成一張表,這就是編碼表
常見的編碼表:
- ASCII:美國标準資訊交換碼,用一個位元組的7位表示
- IOS8859-1:拉丁碼表。歐洲碼表。用一個位元組的8位表示
- GB2312:中國的中文編碼表(早期)
- GBK:中國的中文編碼表更新,融合了更多的中文文字字元。打頭的是兩個高位為1的兩個位元組編碼。為負數
- Unicode:國際标準碼,融合了多種文字。所有國家的文字都用兩個位元組來表示,但是對于一個位元組就能表示的文字,浪費。Java語言使用的就是unicode。
- UTF-8:Unicode Transform Format,最多用三個位元組表示一個字元,根據字元所占記憶體空間不同,分别用一個、兩個、三個位元組來編碼。并在每個位元組的開頭都加了辨別頭,用以區分什麼是UTF-8的編碼
代碼示例:
private static void readText() throws IOException {
InputStreamReader isr =
new InputStreamReader(new FileInputStream("utf.txt"),"GBK");//位元組流到字元流
char[] buf = new char[10];
int len = isr.read(buf);
String str = new String(buf,0,len);
System.out.println(str);
isr.close();
}
private static void writeText() throws IOException {
/*OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("gbk.txt"));//因為系統預設的是GBK碼表,這裡将字元流轉換為位元組 流,是以此句等價于FileWriter
*/
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");//按照UTF-8碼表寫出資料
osw.write("你好");//osw是字元流對象,是以不用将資料轉換為位元組數組
osw.close();
}
查錯表的情況分析:
可以利用“你好”來分析編碼表的錯誤
編碼與解碼
編碼:字元串變成位元組數組 String --> byte[] 預設字元集(GBK):str.getBytes() 指定字元集:str.getBytes(charsetName) 解碼:位元組數組變成字元串 byte[] --> String 預設字元集(GBK):new String(byte[]) 指定字元集:new String(byte[], charsetName)
String str = "你好";
// byte[] arr = str.getBytes();//結果:[-60, -29, -70, -61]
// byte[] arr = str.getBytes("GBK");//結果:[-60, -29, -70, -61]會抛出異常,因為可能傳入的值不識别
byte[] arr = str.getBytes("UTF-8");//結果:[-28, -67, -96, -27, -91, -67]
System.out.println(Arrays.toString(arr));//将位元組數組直接轉換成字元串形式
常用的碼表就兩張:GBK和UTF-8,他們都識别中文
字元集轉換注意事項
GBK與IOS8859-1
如果編碼時傳入了不能識别原文字的碼表(比如對中文編碼傳入歐洲碼表IOS8859-1),編碼錯誤就無法正确解碼了 如果編碼成功但是解碼後出現亂碼,則需要對亂碼再次進行編碼(采用解碼的碼表),然後用編碼的碼表解碼來還原原字元 原理圖:
應用:當伺服器端出現亂碼就采用編一次解一次來解決,因為伺服器的解碼碼表是ISO8859-1
GBK與UTF-8
因為UTF-8和GBK都識别中文,當用GBK編碼再用UTF-8解碼,會顯示未知字元(???),此時再用UTF-8對其編碼時會在其未知字元區域查找位元組碼值,是以無法再次還原正确的位元組碼值,是以此時不能采用再編碼再解碼的方法解決亂碼
String s = "你好";
byte[] b = s.getBytes("GBK");
System.out.println(Arrays.toString(b));
String s1 = new String(b,"UTF-8");
System.out.println(s1);
//對s1進行再次編碼
byte[] b1 = s1.getBytes("UTF-8");
System.out.println(Arrays.toString(b1));
String s2 = new String(b1,"GBK");
System.out.println(s2);
運作結果:
“聯通”的問題
對于“聯通”兩個字,其GBK編碼對應的四個位元組正好符合UTF-8的編碼規則,是以應用程式(比如記事本)在打開時會自動按照UTF-8對其進行解碼,是以會出現亂碼。 解決辦法是避免隻存入符合UTF-8編碼規則的字元,可以加入别的字元來讓應用程式不按UTF-8進行解析,就可以成功顯示“聯通”
綜合練習
/*
有五個學生,每個學生有3門課的成績,
從鍵盤輸入以上資料(包括姓名,三門課成績),
輸入的格式:如:zhagnsan,30,40,60計算出總成績,
并把學生的資訊和計算出的總分數高低順序存放在磁盤檔案"stud.txt"中。
1,描述學生對象。
2,定義一個可操作學生對象的工具類。
思想:
1,通過擷取鍵盤錄入一行資料,并将該行中的資訊取出封裝成學生對象。
2,因為學生有很多,那麼就需要存儲,使用到集合。因為要對學生的總分排序。
是以可以使用TreeSet。
3,将集合的資訊寫入到一個檔案中。
*/
import java.io.*;
import java.util.*;
//定義Student類實作Comparable接口并指定泛型,這樣在實作compareTo方法時就隻能接收該泛型了
class Student implements Comparable<Student>{
private String name;
private int ma,cn,en,sum;
Student(String name, int ma, int cn, int en){
this.name = name;
this.ma = ma;
this.cn = cn;
this.en = en;
sum = ma+cn+en;
}
public String getName(){
return this.name;
}
public int getSum(){
return this.sum;
}
public int compareTo(Student s){
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num==0)
return this.name.compareTo(s.name);
return num;
}
//一般自定義對象都要覆寫Object類的hashCode和equals方法,還有toString方法
public int hashCode(){
return name.hashCode()+sum*35;
}
public boolean equals(Object obj){
if(!(obj instanceof Student))
throw new ClassCastException("類型不比對");
Student s = (Student)obj;
return this.name.equals(s.name)&&this.sum==s.sum;
}
public String toString(){
return "Student:"+name+"["+ma+","+cn+","+en+"]";
}
}
class StudentInfoTool{
//按照預設排序方式,總成績從小到大
public static Set<Student> getStudents() throws IOException{
return getStudents(null);//不需要比較器,傳入null。
}
//按照指定比較器排序方式,總成績從大到小
public static Set<Student> getStudents(Comparator<Student> cmp) throws IOException{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
Set<Student> stus = null;
if(cmp==null)
stus = new TreeSet<Student>();//如果沒有傳入比較器,那麼就建立一個不帶比較器的集合
else
stus = new TreeSet<Student>(cmp);//如果傳入了比較器,就建立一個帶比較器的集合
while(true){
line = bufr.readLine();
if("over".equals(line))
break;
String[] arr = line.split(",");
Student s = new Student(arr[0],Integer.parseInt(arr[1]),
Integer.parseInt(arr[2]),Integer.parseInt(arr[3]));
stus.add(s);
}
bufr.close();
return stus;
}
//将存有學生資訊的集合寫入檔案
public static void write2File(Set<Student> stus) throws IOException{
BufferedWriter bufw = new BufferedWriter(new FileWriter("stuInfo.txt"));
for(Student s: stus){
bufw.write(s.toString()+'\t');
bufw.write(s.getSum()+"");//如果單獨輸出整數隻輸出最後一個八位,會出現亂碼,是以要轉為字元串
bufw.newLine();
bufw.flush();//寫入檔案需要重新整理,因為用到了緩存
}
bufw.close();
}
}
public class StudentInfoDemo {
public static void main(String[] args) throws IOException {
Comparator<Student> cmp = Collections.reverseOrder();//傳回一個逆序自身比較性的比較器
Set<Student> stus = StudentInfoTool.getStudents(cmp);
StudentInfoTool.write2File(stus);
}
}