Java IO流詳解
一、IO流概述
概述:
IO流簡單來說就是Input和Output流,IO流主要是用來處理裝置之間的資料傳輸,Java對於資料的操作都是通過流實現,而java用於操作流的物件都在IO包中。
分類:
按操作資料分為:位元組流和字元流。 如:Reader和InpurStream 按流向分:輸入流和輸出流。如:InputStream和OutputStream
IO流常用的基類:
* InputStream , OutputStream
字元流的抽象基類:
* Reader , Writer 由上面四個類派生的子類名稱都是以其父類名作為子類的字尾: 如:FileReader和FileInputStream
二、字元流
1. 字元流簡介:
* 字元流中的物件融合了編碼表,也就是系統預設的編碼表。我們的系統一般都是GBK編碼。 * 字元流只用來處理文字資料,位元組流用來處理媒體資料。 * 資料最常見的表現方式是檔案,字元流用於操作檔案的子類一般是FileReader和FileWriter。
2.字元流讀寫:
注意事項: * 寫入檔案後必須要用flush()重新整理。 * 用完流後記得要關閉流 * 使用流物件要丟擲IO異常 * 定義檔案路徑時,可以用“/”或者“\\”。 * 在建立一個檔案時,如果目錄下有同名檔案將被覆蓋。 * 在讀取檔案時,必須保證該檔案已存在,否則出異常 示例1:在硬碟上建立一個檔案,並寫入一些文字資料
class FireWriterDemo { public static void main(String[] args) throws IOException { //需要對IO異常進行處理 //建立一個FileWriter物件,該物件一被初始化就必須要明確被操作的檔案。 //而且該檔案會被建立到指定目錄下。如果該目錄有同名檔案,那麼該檔案將被覆蓋。 FileWriter fw = new FileWriter("F:\\1.txt");//目的是明確資料要存放的目的地。 //呼叫write的方法將字串寫到流中 fw.write("hello world!"); //重新整理流物件緩衝中的資料,將資料刷到目的地中 fw.flush(); //關閉流資源,但是關閉之前會重新整理一次內部緩衝中的資料。當我們結束輸入時候,必須close(); fw.write("first_test"); fw.close(); //flush和close的區別:flush重新整理後可以繼續輸入,close重新整理後不能繼續輸入。 } }
示例2:FileReader的reade()方法. 要求:用單個字元和字元陣列進行分別讀取
class FileReaderDemo {
public static void main(String[] args) {
characters();
}
/*****************字元陣列進行讀取*********************/
private static void characters() {
try {
FileReader fr = new FileReader("Demo.txt");
char [] buf = new char[6];
//將Denmo中的檔案讀取到buf陣列中。
int num = 0;
while((num = fr.read(buf))!=-1) {
//String(char[] value , int offest,int count) 分配一個新的String,包含從offest開始的count個字元
sop(new String(buf,0,num));
}
sop('\n');
fr.close();
}
catch (IOException e) {
sop(e.toString());
}
}
/*****************單個字母讀取*************************/
private static void singleReader() {
try {
//建立一個檔案讀取流物件,和指定名稱的檔案關聯。
//要保證檔案已經存在,否則會發生異常:FileNotFoundException
FileReader fr = new FileReader("Demo.txt");
//如何呼叫讀取流物件的read方法?
//read()方法,一次讀取一個字元,並且自動往下讀。如果到達末尾則返回-1
int ch = 0;
while ((ch=fr.read())!=-1) {
sop((char)ch);
}
sop('\n');
fr.close();
/*int ch = fr.read();
sop("ch=" + (char)ch);
int ch2 = fr.read();
sop("ch2=" + (char)ch2);
//使用結束注意關閉流
fr.close(); */
}
catch (IOException e) {
sop(e.toString());
}
}
/**********************Println************************/
private static void sop(Object obj) {
System.out.print(obj);
}
}
示例3:對已有檔案的資料進行續寫
import java.io.*;
class FileWriterDemo3 {
public static void main(String[] args) {
try {
//傳遞一個引數,代表不覆蓋已有的資料。並在已有資料的末尾進行資料續寫
FileWriter fw = new FileWriter("F:\\java_Demo\\day9_24\\demo.txt",true);
fw.write(" is charactor table?");
fw.close();
}
catch (IOException e) {
sop(e.toString());
}
}
/**********************Println************************/
private static void sop(Object obj)
{
System.out.println(obj);
}
}
練習: 將F盤的一個檔案複製到E盤。 思考: 其實就是將F盤下的檔案資料儲存到D盤的一個檔案中。 步驟: 1.在D盤建立一個檔案,儲存F盤中檔案的資料。 2.定義讀取流和F:盤檔案關聯。 3.通過不斷讀寫完成資料儲存。 4.關閉資源。 原始碼:
import java.io.*;
import java.util.Scanner;
class CopyText {
public static void main(String[] args) throws IOException {
sop("請輸入要拷貝的檔案的路徑:");
Scanner in = new Scanner(System.in);
String source = in.next();
sop("請輸入需要拷貝到那個位置的路徑以及生成的檔名:");
String destination = in.next();
in.close();
CopyTextDemo(source,destination);
}
/*****************檔案Copy*********************/
private static void CopyTextDemo(String source,String destination) {
try {
FileWriter fw = new FileWriter(destination);
FileReader fr = new FileReader(source);
char [] buf = new char[1024];
//將Denmo中的檔案讀取到buf陣列中。
int num = 0;
while((num = fr.read(buf))!=-1) {
//String(char[] value , int offest,int count) 分配一個新的String,包含從offest開始的count個字元
fw.write(new String(buf,0,num));
}
fr.close();
fw.close();
}
catch (IOException e) {
sop(e.toString());
}
}
/**********************Println************************/
private static void sop(Object obj) {
System.out.println(obj);
}
}
三、緩衝區
1. 字元流的緩衝區:BufferedReader和BufferedWreiter
緩衝區的出現時為了提高流的操作效率而出現的.
需要被提高效率的流作為引數傳遞給緩衝區的建構函式
在緩衝區中封裝了一個數組,存入資料後一次取出
BufferedReader示例: 讀取流緩衝區提供了一個一次讀一行的方法readline,方便對文字資料的獲取。 readline()只返回回車符前面的字元,不返回回車符。如果是複製的話,必須加入newLine(),寫入回車符 newLine()是java提供的多平臺換行符寫入方法。
class BufferedReaderDemo { public static void main(String[] args) throws IOException {
//建立一個字元讀取流流物件,和檔案關聯
FileReader rw = new FileReader("buf.txt");
//只要將需要被提高效率的流作為引數傳遞給緩衝區的建構函式即可
BufferedReader brw = new BufferedReader(rw);
for(;;) {
String s = brw.readLine();
if(s==null) break;
System.out.println(s);
}
brw.close();//關閉輸入流物件
}
}
BufferedWriter示例:
class BufferedWriterDemo { public static void main(String[] args) throws IOException {
//建立一個字元寫入流物件
FileWriter fw = new FileWriter("buf.txt");
//為了提高字元寫入效率,加入了緩衝技術。
//只要將需要被提高效率的流作為引數傳遞給緩衝區的建構函式即可
BufferedWriter bfw = new BufferedWriter(fw);
//bfw.write("abc\r\nde");
//bfw.newLine(); 這行程式碼等價於bfw.write("\r\n"),相當於一個跨平臺的換行符
//用到緩衝區就必須要重新整理
for(int x = 1; x < 5; x++) {
bfw.write("abc");
bfw.newLine(); //java提供了一個跨平臺的換行符newLine();
bfw.flush();
}
bfw.flush(); //重新整理緩衝區
bfw.close(); //關閉緩衝區,但是必須要先重新整理
//注意,關閉緩衝區就是在關閉緩衝中的流物件
fw.close(); //關閉輸入流物件
}
}
2.裝飾設計模式
裝飾設計模式:::: 要求:自定義一些Reader類,讀取不同的資料(裝飾和繼承的區別) MyReader //專門用於讀取資料的類 |--MyTextReader |--MyBufferTextReader |--MyMediaReader |--MyBufferMediaReader |--MyDataReader |--MyBufferDataReader 如果將他們抽取出來,設計一個MyBufferReader,可以根據傳入的型別進行增強 class MyBufferReader { MyBufferReader (MyTextReader text) {} MyBufferReader (MyMediaReader media) {} MyBufferReader (MyDataReader data) {} } 但是上面的類拓展性很差。找到其引數的共同型別,通過多型的形式,可以提高拓展性 class MyBufferReader extends MyReader{ private MyReader r; //從繼承變為了組成模式 裝飾設計模式 MyBufferReader(MyReader r) {} } 優化後的體系: |--MyTextReader |--MyMediaReader |--MyDataReader |--MyBufferReader //增強上面三個。裝飾模式比繼承靈活, 避免繼承體系的臃腫。降低類與類之間的耦合性 裝飾類只能增強已有的物件,具備的功能是相同的。所以裝飾類和被裝飾類屬於同一個體系 MyBuffereReader類: 自己寫一個MyBuffereReader類,功能與BuffereReader相同
class MyBufferedReader1 extends Reader{ private Reader r; MyBufferedReader1(Reader r){ this.r = r; }
//一次讀一行資料的方法
public String myReaderline() throws IOException {
//定義一個臨時容器,原BufferReader封裝的是字元陣列。
//為了演示方便。定義一個StringBuilder容器。最終要將資料變成字串
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = r.read()) != -1)
{
if(ch == '\r')
continue;
if(ch == '\n') //遇到換行符\n,返回字串
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0) //當最後一行不是以\n結束時候,這裡需要判斷
return sb.toString();
return null;
}
/*
需要覆蓋Reader中的抽象方法close(),read();
*/
public void close()throws IOException {
r.close();
}
public int read(char[] cbuf,int off, int len)throws IOException { //覆蓋read方法
return r.read(cbuf,off,len);
}
public void myClose() throws IOException{
r.close();
}
}
四、位元組流
1.概述:
1、位元組流和字元流的基本操作是相同的,但是要想操作媒體流就需要用到位元組流。
2、位元組流因為操作的是位元組,所以可以用來操作媒體檔案。(媒體檔案也是以位元組儲存的) 3、讀寫位元組流:InputStream 輸入流(讀)和OutputStream 輸出流(寫) 4、位元組流操作可以不用重新整理流操作。 5、InputStream特有方法: int available();//返回檔案中的位元組個數 注:可以利用此方法來指定讀取方式中傳入陣列的長度,從而省去迴圈判斷。但是如果檔案較大,而虛擬機器啟動分配的預設記憶體一般為64M。當檔案過大時,此陣列長度所佔記憶體空間就會溢位。所以,此方法慎用,當檔案不大時,可以使用。 練習: 需求:複製一張圖片F:\java_Demo\day9_28\1.BMP到F:\java_Demo\day9_28\2.bmp
import java.io.*;
class CopyPic {
public static void main(String[] args){
copyBmp();
System.out.println("複製完成");
}
public static void copyBmp() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("F:\\java_Demo\\day9_28\\1.bmp"); //寫入流關聯檔案
fos = new FileOutputStream("F:\\java_Demo\\day9_28\\2.bmp"); //讀取流關聯檔案
byte[] copy = new byte[1024];
int len = 0;
while((len=fis.read(copy))!=-1) {
fos.write(copy,0,len);
}
}
catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("複製檔案異常");
}
finally {
try {
if(fis!=null) fis.close();
}
catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("讀取流");
}
}
}
}
2. 位元組流緩衝區
* 位元組流緩衝區跟字元流緩衝區一樣,也是為了提高效率。 注意事項: 1. read():會將位元組byte()提升為int型值 2. write():會將int型別轉換為byte()型別,保留最後的8位。 練習: 1.複製MP3檔案 1.MP3 --> 2.MP3 2.自己寫一個MyBufferedInputStream緩衝類,提升複製速度 程式碼:
import java.io.*;
//自己的BufferedInputStream
class MyBufferedInputStream {
private InputStream in; //定義一個流物件
private byte [] buf = new byte[1024*4];
private int count = 0,pos = 0;
public MyBufferedInputStream(InputStream in){
this.in = in;
}
public int MyRead() throws IOException{
if(count==0) { //當數組裡的資料為空時候,讀入資料
count = in.read(buf);
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b&255; //提升為int型別,在前面三個位元組補充0。避免1111 1111 1111 1111
}
else if(count > 0) {
byte b = buf[pos];
pos++;
count--;
return b&0xff; //提升為int型別,在前面三個位元組補充0。避免1111 1111 1111 1111
}
return -1;
}
public void myClose() throws IOException{
in.close();
}
}
class BufferedCopyDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
copy();
long end = System.currentTimeMillis();
System.out.println("時間:"+(end-start)+"ms");
start = System.currentTimeMillis();
copy1();
end = System.currentTimeMillis();
System.out.println("時間:"+(end-start)+"ms");
}
public static void copy1() { // 應用自己的緩衝區緩衝資料
MyBufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new MyBufferedInputStream(new FileInputStream("馬旭東-入戲太深.mp3"));//匿名類,傳入一個InputStream流物件
bos = new BufferedOutputStream(new FileOutputStream("3.mp3"));
int buf = 0;
while((buf=bis.MyRead())!=-1) {
bos.write(buf);
}
}
catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("複製失敗");
}
finally {
try {
if(bis!=null) {
bis.myClose();
bos.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
五、流操作規律
1. 鍵盤讀取,控制檯列印。
System.out: 對應的標準輸出裝置:控制檯 //它是PrintStream物件,(PrintStream:列印流。OutputStream的子類)
System.in: 對應的標準輸入裝置:鍵盤 //它是InputStream物件 示例:
/從鍵盤錄入流,列印到控制檯上/ public static void InOutDemo(){ //鍵盤的最常見的寫法 BufferedReader bufr = null; BufferedWriter bufw = null; try {
/*InputStream ips = System.in; //從鍵盤讀入輸入位元組流
InputStreamReader fr = new InputStreamReader(ips); //將位元組流轉成字元流
bufr = new BufferedReader(fr); */ //將字元流加強,提升效率
bufr = new BufferedReader(new InputStreamReader(System.in)); //匿名類。InputSteamReader:讀取位元組並將其解碼為字元
bufw = new BufferedWriter(new OutputStreamWriter(System.out)); //OutputStreamWriter:要寫入流中的字元編碼成位元組
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line)) break;
bufw.write(line.toUpperCase()); //列印
bufw.newLine(); //為了相容,使用newLine()寫入換行符
bufw.flush(); //必須要重新整理。不然不會顯示
}
if(bufw!=null) {
bufr.close();
bufw.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
2. 整行錄入
1.從鍵盤錄入資料,並存儲到檔案中。
2. 我們在鍵盤錄入的是時候,read()方法是一個一個錄入的,能不能整行的錄入呢?這時候我們想到了BufferedReader中ReadLine()方法。
3. 轉換流
為了讓位元組流可以使用字元流中的方法,我們需要轉換流。
1. InputStreamReader:位元組流轉向字元流; a、獲取鍵盤錄入物件。 InputStream in=System.in; b、將位元組流物件轉成字元流物件,使用轉換流。 InputStreamReaderisr=new InputStreamReader(in); c、為了提高效率,將字串進行緩衝區技術高效操作。使用BufferedReader BufferedReaderbr=new BufferedReader(isr); //鍵盤錄入最常見寫法 BufferedReaderin=new BufferedReader(new InputStreamReader(System.in));
2.OutputStreamWriter:字元流通向位元組流
示例:
/==把鍵盤錄入的資料存到一個檔案中/ public static void inToFile() { //鍵盤的最常見的寫法 BufferedReader bufr = null; BufferedWriter bufw = null; try {
/*InputStream ips = System.in; //從鍵盤讀入輸入位元組流
InputStreamReader fr = new InputStreamReader(ips); //將位元組流轉成字元流
bufr = new BufferedReader(fr); */ //將字元流加強,提升效率
bufr = new BufferedReader(new InputStreamReader(System.in)); //匿名類。InputSteamReader:讀取位元組並將其解碼為字元
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("out.txt"))); //OutputStreamWriter:要寫入流中的字元編碼成位元組
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line)) break;
bufw.write(line.toUpperCase()); //列印
bufw.newLine(); //為了相容,使用newLine()寫入換行符
bufw.flush(); //必須要重新整理。不然不會顯示
}
if(bufw!=null) {
bufr.close();
bufw.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
4. 流操作基本規律
為了控制格式我將其寫入了Java程式碼段中,如下:
示例1:文字 ~ 文字
/* 流操作的基本規律。 一、兩個明確:(明確體系)
-
明確源和目的 源:輸入流 InputStream Reader 目的:輸出流 OutputStream Writer
-
操作的資料是否是純文字 是: 字元流 否: 位元組流 二、明確體系後要明確具體使用的物件 通過裝置區分:記憶體,硬碟,鍵盤 目的裝置:記憶體,硬碟,控制檯
示例1:將一個文字檔案中的資料儲存到另一個檔案中: 複製檔案 一、明確體系 源:檔案–>讀取流–>(InputStream和Reader) 是否是文字:是–>Reader
目的:檔案-->寫入流-->(OutputStream Writer)
是否純文字:是-->Writer
二、 明確裝置
源:Reader
裝置:硬碟上一個文字檔案 --> 子類物件為:FileReader
FileReader fr = new FileReader("Goods.txt");
是否提高效率:是-->加入Reader中的緩衝區:BufferedReader
BufferedReader bufr = new BufferedReader(fr);
目的:Writer
裝置:鍵盤上一個文字檔案 --> 子類物件:FileWriter
FileWriter fw = new FileWriter("goods1.txt");
是否提高效率:是-->加入Writer的緩衝區:BufferedWriter
BufferedWriter bufw = new BufferedWriter(fw);
示例2:將一個圖片檔案資料複製到另一個檔案中:複製檔案 一、明確體系 源:檔案–>讀取流–>(InputStream和Reader) 是否是文字:否–>InputStream
目的:檔案-->寫入流-->(OutputStream Writer)
是否純文字:否-->OutputStream
二、 明確裝置
源:InputStream
裝置:硬碟上一個媒體檔案 --> 子類物件為:FileInputStream
FileInputStream fis = new FileInputStream("Goods.txt");
是否提高效率:是-->加入InputStream中的緩衝區:BufferedInputStream
BufferedInputStream bufi = new BufferedInputStream(fis);
目的:OutputStream
裝置:鍵盤上一個媒體檔案 --> 子類物件:FileOutputStream
FileOutputStream fos = new FileOutputStream("goods1.txt");
是否提高效率:是-->加入OutputStream的緩衝區:BufferedOutputStream
BufferedOutputStream bufo = new BufferedOutputStream(fw);
示例3:將鍵盤錄入的資料儲存到一個文字檔案中 一、明確體系 源:鍵盤–>讀取流–>(InputStream和Reader) 是否是文字:是–>Reader
目的:檔案-->寫入流-->(OutputStream Writer)
是否純文字:是-->Writer
二、 明確裝置
源:InputStream
裝置:鍵盤 --> 對用物件為:System.in --> InputStream
為了操作方便,轉成字元流Reader --> 使用Reader中的轉換流:InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
是否提高效率:是-->加入Reader中的緩衝區:BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:Writer
裝置:鍵盤上一個文字檔案 --> 子類物件:FileWriter
FileWriter fw = new FileWriter("goods1.txt");
是否提高效率:是-->加入Writer的緩衝區:BufferedWriter
BufferedWriter bufw = new BufferedWriter(fw);
5.指定編碼表(轉換流可以指定編碼表)
要求:用UTF-8編碼儲存一個文字檔案
import java.io.*; public class IOStreamLaw {
/**
* @param args
*/
public static void main(String[] args) throws IOException {
//鍵盤的最常見寫法
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("goods1.txt"),"UTF-8"));
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line)) break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}