流的基本概念
??在java.io包中File類是唯一一個(gè)與文件本身有關(guān)的程序處理類,但是File只能操作文件本身,卻不能操作文件內(nèi)容。在實(shí)際的開(kāi)發(fā)中,IO操作的核心意義在于:輸入與輸出操作。而對(duì)于程序,輸入和輸出可能來(lái)自于不同的環(huán)境,例如:通過(guò)電腦連接服務(wù)器進(jìn)行瀏覽時(shí),實(shí)際上客戶端發(fā)出了一個(gè)信息,而服務(wù)端接收到此信息后進(jìn)行回應(yīng)處理。
??對(duì)于服務(wù)端或者是客戶端實(shí)質(zhì)上傳遞的就是一種數(shù)據(jù)流的處理形式,而所謂的數(shù)據(jù)流指的就是字節(jié)數(shù)據(jù)。而對(duì)于這種流的處理形式在java.io包中提供有兩類支持:
- 字節(jié)處理流:OutputStream()(輸入字節(jié)流)、InputStream()(輸入字節(jié)流);
- 字符處理流:Writer(輸出字符流)、Reader(輸入字符流);
??所有的流操作都應(yīng)采用如下統(tǒng)一的步驟進(jìn)行,下面以文件處理的流程為例:
- 如果進(jìn)行文件讀寫操作,則一定要通過(guò)File類找到一個(gè)文件路徑;
- 通過(guò)字節(jié)流或字符流的子類為父類對(duì)象進(jìn)行實(shí)例化;
- 利用字節(jié)流或字符流中的方法實(shí)現(xiàn)數(shù)據(jù)的輸入與輸出操作;
- 流的操作屬于資源操作,資源操作必須進(jìn)行關(guān)閉處理;
字節(jié)輸出流:OutputStream
??子節(jié)的數(shù)據(jù)以byte類型為主實(shí)現(xiàn)的操作,在進(jìn)行字節(jié)內(nèi)容輸出的時(shí)候,可以使用OutputStream類完成,這個(gè)類的基本定義如下:
public abstract class OutputStream extends Object implements Closeable, Flushable{}
public interface Closeable extends AutoCloseable{
void close?() throws IOException;
}
public interface Flushable{
void flush?() throws IOException;
}
??可以發(fā)現(xiàn)這個(gè)類實(shí)現(xiàn)了兩個(gè)接口,于是基本的對(duì)應(yīng)關(guān)系如下:
??OutputStream類定義的是一個(gè)公共的輸出操作標(biāo)準(zhǔn),在這個(gè)操作標(biāo)準(zhǔn)中定義有三個(gè)內(nèi)容輸出的方法:
- 輸出單個(gè)字節(jié)數(shù)據(jù):public abstract void write?(int b) throws IOException;
- 輸出一組字節(jié)數(shù)據(jù):public void write?(byte[] b) throws IOException;
-
輸出部分字節(jié)數(shù)據(jù):public void write?(byte[] b, int off, int len) throws IOException;
??需要注意的是OutputStream是一個(gè)抽象類,而抽象類如果想要獲得實(shí)例化對(duì)象就需要通過(guò)子類實(shí)例的向上轉(zhuǎn)型完成。如果想要進(jìn)行文件內(nèi)容處理操作,可以使用子類FileOutputStream。
FileOutputStream的構(gòu)造方法:
- 【覆蓋】構(gòu)造方法:public FileOutputStream?(File file) throws FileNotFoundException;
- 【追加】構(gòu)造方法:public FileOutputStream?(File file, boolean append) throws FileNotFoundException
范例:使用FileOutputStream類實(shí)現(xiàn)內(nèi)容輸出
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
//判斷父目錄是否存在,不存在則創(chuàng)建目錄
if(file.getParentFile().exists()==false){
file.getParentFile().mkdirs();
}
OutputStream output=new FileOutputStream(file);
String str="www.baidu.com";//輸出內(nèi)容
output.write(str.getBytes());//將字符串轉(zhuǎn)為字節(jié)數(shù)組
output.close();//關(guān)閉資源
}
}
??本程序是采用了標(biāo)準(zhǔn)的形式實(shí)現(xiàn)了輸出的操作處理,并且在整體的處理中,只是創(chuàng)建了文件的父目錄,并沒(méi)有創(chuàng)建文件,而在執(zhí)行后發(fā)現(xiàn)在內(nèi)容寫入前,文件如果不存在會(huì)自動(dòng)創(chuàng)建。另外要注意的是,由于OutputStream的子類也屬于AutoCloseable的接口子類,所以對(duì)于close()方法也可以簡(jiǎn)化使用。是否使用自動(dòng)的關(guān)閉取決于項(xiàng)目的整體結(jié)構(gòu),另外還要注意的是,整個(gè)程序中最終是輸出了一組的字節(jié)數(shù)據(jù),但是千萬(wàn)別忘記,OutputStream類中定義的輸出方法有三個(gè)。
范例:自動(dòng)關(guān)閉處理
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
//判斷父目錄是否存在,不存在則創(chuàng)建目錄
if(file.getParentFile().exists()==false){
file.getParentFile().mkdirs();
}
try (OutputStream output=new FileOutputStream(file,true)){
String str="www.baidu.com\r\n";//輸出內(nèi)容
output.write(str.getBytes());//將字符串轉(zhuǎn)為字節(jié)數(shù)組
}catch (IOException e){
e.printStackTrace();
}
}
}
字節(jié)輸入流:InputStream
??與OutputStream對(duì)應(yīng)的一個(gè)流就是字節(jié)輸入流,InputStream類主要實(shí)現(xiàn)的就是字節(jié)數(shù)據(jù)讀取,該類定義如下:
public abstract class InputStream extends Object implements Closeable{}
??在InputStream類中定義有如下幾個(gè)核心方法:
- 讀取單個(gè)字節(jié)數(shù)據(jù):
??public abstract int read?() throws IOException、如果讀取到底,返回-1; - 讀取一組字節(jié)數(shù)據(jù):
??public int read?(byte[] b) throws IOException、返回的讀取的個(gè)數(shù),如果讀到底,返回-1; - 讀取一組字節(jié)數(shù)據(jù)的部分內(nèi)容:
??public int read?(byte[] b, int off, int len) throws IOException;
??InputStream類屬于一個(gè)抽象類,這時(shí)應(yīng)該依靠它的子類來(lái)進(jìn)行對(duì)象實(shí)例化,如果要從文件讀取可以使用FileInputStream子類。
FileInputStream的構(gòu)造方法:
- 構(gòu)造方法:public FileInputStream?(File file) throws FileNotFoundException;
范例:使用FileInputStream讀取文件數(shù)據(jù)
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
InputStream input=new FileInputStream(file);
byte[] data=new byte[1024];//開(kāi)辟一個(gè)緩沖區(qū)讀取數(shù)據(jù)
int len=input.read(data);//讀取數(shù)據(jù),數(shù)據(jù)全部保存在字節(jié)數(shù)組中,并返回讀取個(gè)數(shù)
System.out.println("【"+new String(data,0,len)+"】");
input.close();
}
}
??對(duì)于字節(jié)輸入流中最為棘手的問(wèn)題就在于:使用read()方法讀取的時(shí)候只能以字節(jié)數(shù)組為主進(jìn)行接收。
??從JDK1.9開(kāi)始,InputStream類中增加了一個(gè)新的方法:public byte[] readAllBytes?() throws IOException;
不過(guò)現(xiàn)在并不建議使用該方法在開(kāi)發(fā)中使用,如果要讀取的內(nèi)容量非常大,程序可能直接卡死。
范例:使用readAllBytes()讀取文件數(shù)據(jù)
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
InputStream input=new FileInputStream(file);
byte[] data=input.readAllBytes();//讀取全部數(shù)據(jù)
System.out.println("【"+new String(data)+"】");
input.close();
}
}
字符輸出流:Writer
??使用OutputStream字節(jié)輸出流進(jìn)行數(shù)據(jù)輸出時(shí)使用的都是字節(jié)類型的數(shù)據(jù),但很多情況下字符串的輸出更為方便,因此java.io包在JDK1.1時(shí)又推出了字符輸出流:Writer,這個(gè)類的定義如下:
public abstract class Writer extends Object implements Appendable, Closeable, Flushable{}
??在Writer類中提供了幾個(gè)輸出的操作方法:
- 輸出字符數(shù)組:public void write?(char[] cbuf) throws IOException;
- 輸出字符串:public void write?(String str) throws IOException;
- 追加內(nèi)容:public Writer append?(CharSequence csq) throws IOException;
范例:使用Writer類的子類FileWriter進(jìn)行文件輸出
import java.io.File;
import java.io.FileWriter;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();//父目錄一定要存在
}
// Writer fileWriter=new FileWriter(file, true);//內(nèi)容追加
Writer fileWriter = new FileWriter(file);//內(nèi)容覆蓋
String str = "www.baid.com\r\n";
fileWriter.write(str);
fileWriter.append("中國(guó)人民萬(wàn)歲!");
fileWriter.close();
}
}
??使用Writer輸出的最大優(yōu)勢(shì)在于可以直接利用字符串完成。Writer是字符流,字符處理的優(yōu)勢(shì)在于中文數(shù)據(jù)處理。
字符輸入流:Reader
??Reader是實(shí)現(xiàn)字符輸入流的一種類型,其本身屬于一個(gè)抽象類,這個(gè)類的定義如下:
public abstract class Reader extends Object implements Readable, Closeable{}
public interface Readable{}
??Reader類中并沒(méi)有像Writer類一樣提供有整個(gè)字符串的輸入處理操作,因此只能夠利用字符數(shù)組來(lái)實(shí)現(xiàn)接收:
- 讀取單個(gè)字符:public int read?() throws IOException;
- 讀取一組數(shù)據(jù):public int read?(char[] cbuf) throws IOException;
- 讀取數(shù)組部分?jǐn)?shù)據(jù):public abstract int read?(char[] cbuf, int off, int len) throws IOException;
范例:實(shí)現(xiàn)數(shù)據(jù)讀取
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
if(file.exists()){
Reader reader= new FileReader(file);
char[] data=new char[1024];
int len=reader.read(data);
System.out.println("讀取內(nèi)容:"+new String(data,0,len));
reader.close();
}
}
}
??字符流讀取的時(shí)候只能夠按照數(shù)組的形式來(lái)實(shí)現(xiàn)處理操作。
字節(jié)流與字符流的區(qū)別
??通過(guò)以上一系列的分析,已經(jīng)了解了字節(jié)流和字符流的基本操作,但是對(duì)于這兩類流依然是存在區(qū)別的,重點(diǎn)來(lái)分析一下輸出的操作。在使用OutputStream和Writer輸出時(shí),最后都使用了close()方法進(jìn)行了關(guān)閉處理。
??在使用OutputStream進(jìn)行輸出時(shí),如果沒(méi)有使用close()方法關(guān)閉輸出流會(huì)發(fā)現(xiàn)內(nèi)容依然可以正常的輸出;在使用Writer進(jìn)行輸出時(shí),如果沒(méi)有使用close()方法關(guān)閉輸出,內(nèi)容將無(wú)法進(jìn)行輸出,因?yàn)閃riter使用到了緩沖區(qū),當(dāng)使用close()方法時(shí)實(shí)際上會(huì)強(qiáng)制刷新緩沖區(qū),將內(nèi)容進(jìn)行輸出,如果沒(méi)有關(guān)閉,那么將無(wú)法進(jìn)行輸出操作,如果此時(shí)想要在不關(guān)閉的情況下將緩沖區(qū)的內(nèi)容進(jìn)行輸出,可以使用flush()方法強(qiáng)制刷新緩沖區(qū),寫入內(nèi)容。而字節(jié)流在進(jìn)行輸出處理時(shí)并不會(huì)使用到緩沖區(qū),使用緩沖區(qū)的字符流更加適合于中文數(shù)據(jù)處理,所以在日后的開(kāi)發(fā)中,如果涉及包含中文信息的輸出,一般都會(huì)使用字符流進(jìn)行處理,但是從另一個(gè)角度上,字節(jié)流和字符流的基本處理形式是相似的,由于IO大多情況下都是進(jìn)行數(shù)據(jù)的傳輸使用(二進(jìn)制),所以字節(jié)流也是非常重要的。
范例:使用Writer并強(qiáng)制刷新緩沖區(qū)
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();//父目錄一定要存在
}
// Writer writer=new FileWriter(file, true);//內(nèi)容追加
Writer writer = new FileWriter(file);//內(nèi)容覆蓋
String str = "www.baid.com\r\n";
writer.write(str);
writer.flush();//強(qiáng)制刷新緩沖區(qū),寫入內(nèi)容
writer.append("中國(guó)人民萬(wàn)歲!");
writer.close();
}
}
轉(zhuǎn)換流 InputStreamReader與OutputStreamWriter
??所謂的轉(zhuǎn)換流指的是可以是實(shí)現(xiàn)字節(jié)流和字符流操作的功能轉(zhuǎn)換,例如:進(jìn)行輸出時(shí)OutputStream需要將內(nèi)容變?yōu)樽止?jié)數(shù)組后才能輸出,而Writer可以直接輸出字符串,這一點(diǎn)是便利的,因此就有人提出需求所需要提供一種轉(zhuǎn)換的機(jī)制,來(lái)實(shí)現(xiàn)不同流類型的轉(zhuǎn)換操作,為此java.io包中提供了兩個(gè)類:InputStreamReader、OutputStreamWriter;
InputStreamReader:
??public class InputStreamReader extends Reader{}
??構(gòu)造方法:public InputStreamReader?(InputStream in);
OutputStreamWriter:
??public class OutputStreamWriter extends Writer{}
??構(gòu)造方法:public OutputStreamWriter?(OutputStream out);
??通過(guò)類的繼承結(jié)構(gòu)與構(gòu)造方法可以發(fā)現(xiàn),所謂的轉(zhuǎn)換就處理就是將接收到的字符流對(duì)象通過(guò)向上轉(zhuǎn)型變成字符流對(duì)象
范例:觀察轉(zhuǎn)換
import java.io.*;
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String dirPath = "/Users/fzc/Documents/mydir/mldn.txt";
File file = new File(dirPath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();//父目錄一定要存在
}
OutputStream outputStream=new FileOutputStream(file);
Writer writer=new OutputStreamWriter(outputStream);
writer.write("www.baidu.com\r\n");
writer.close();
}
}
??觀察一下FileWriter類和FileReader類的繼承關(guān)系
FileWriter:
??public class FileWriter extends OutputStreamWriter{}
??構(gòu)造方法:public InputStreamReader?(InputStream in);
FileReader:
??public class FileReader extends InputStreamReader{}
??構(gòu)造方法:public OutputStreamWriter?(OutputStream out);
??實(shí)際上所謂的緩存都是指的是程序中間的一道處理緩沖區(qū)。
文件拷貝
??在操作系統(tǒng)中,這個(gè)命令的主要功能是可以實(shí)現(xiàn)文件的拷貝處理,現(xiàn)在要求模擬這個(gè)命令,指定源文件路徑和拷貝的目標(biāo)路徑實(shí)現(xiàn)文件的拷貝處理。
需求分析:
- 需要實(shí)現(xiàn)文件的拷貝操作,那么就有可能拷貝各種類型的文件,因此選擇字節(jié)流;
- 在進(jìn)行拷貝時(shí)需要考慮不到大文件的拷貝問(wèn)題;
實(shí)現(xiàn)方案
- 方案一:使用InputStream將全部要拷貝的內(nèi)容直接讀取到程序中,而后一次性輸出到目標(biāo)文件;
??如果拷貝的文件過(guò)大,程序極有可能會(huì)卡死; - 方案二:采用部分拷貝,讀取一部分輸出一部分?jǐn)?shù)據(jù),核心的操作方法:
??- InputStream:public int read?(byte[] b) throws IOException;
??- OutputStream:public void write?(byte[] b, int off, int len) throws IOException;
范例:實(shí)現(xiàn)文件拷貝處理
import java.io.*;
/**
* 定義一個(gè)文件操作的工具類
*/
class FileUtil {
private File srcFile;//源文件
private File desFile;//目標(biāo)文件路徑
public FileUtil(String src, String des) {
this(new File(src), new File(des));
}
public FileUtil(File srcFile, File desFile) {
this.srcFile = srcFile;
this.desFile = desFile;
}
/**
* 文件拷貝
*
* @return 拷貝是否成功
*/
public boolean copy() throws IOException {
if (srcFile.exists()==false) {
System.out.println("拷貝的源文件不存在");
return false;//源文件不存在拷貝失敗
}
if (desFile.getParentFile().exists() == false) {
desFile.getParentFile().mkdirs();//創(chuàng)建父目錄
}
byte[] data = new byte[1024];//開(kāi)辟一個(gè)緩沖區(qū)
InputStream input = null;
OutputStream output = null;
try {
input = new FileInputStream(srcFile);
output = new FileOutputStream(desFile);
int len = 0;
//1、讀取數(shù)據(jù)到數(shù)組之中,隨后返回讀取的個(gè)數(shù)
//2、判斷個(gè)數(shù)==-1,如果不是則進(jìn)行寫入
//
// do {
// len = input.read(data);
// if(len!=-1){
// output.write(data, 0, len);
// }
// } while (len != -1);
//更為簡(jiǎn)便的寫法
while ((len=input.read(data))!=-1){
output.write(data,0,len);
}
return true;
} catch (IOException e) {
throw e;
} finally {
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
}
}
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String src = "/Users/fzc/Documents/mydir/mldn.txt";
String des = "/Users/fzc/Documents/mydir/mldn_copy.txt";
FileUtil fileUtil = new FileUtil(src, des);
System.out.println(fileUtil.copy() ? "文件拷貝成功!" : "文件拷貝失敗!");
}
}
??需要注意的是,上面的范例是屬于文件拷貝最原始的實(shí)現(xiàn),從JDK1.9開(kāi)始,InputStream和Reader類中都追加了數(shù)據(jù)轉(zhuǎn)存的處理操作方法:
- 【JDK1.9】InputStream:
??public long transferTo?(OutputStream out) throws IOException;
- 【JDK10】Reader:
??public long transferTo?(Writer out) throws IOException;
范例:使用轉(zhuǎn)存的方式處理
import java.io.*;
/**
* 定義一個(gè)文件操作的工具類
*/
class FileUtil {
private File srcFile;//源文件
private File desFile;//目標(biāo)文件路徑
public FileUtil(String src, String des) {
this(new File(src), new File(des));
}
public FileUtil(File srcFile, File desFile) {
this.srcFile = srcFile;
this.desFile = desFile;
}
/**
* 文件拷貝
*
* @return 拷貝是否成功
*/
public boolean copy() throws IOException {
if (srcFile.exists()==false) {
System.out.println("拷貝的源文件不存在");
return false;//源文件不存在拷貝失敗
}
if (desFile.getParentFile().exists() == false) {
desFile.getParentFile().mkdirs();//創(chuàng)建父目錄
}
byte[] data = new byte[1024];//開(kāi)辟一個(gè)緩沖區(qū)
InputStream input = null;
OutputStream output = null;
try {
input = new FileInputStream(srcFile);
output = new FileOutputStream(desFile);
//一定要注意JDK版本問(wèn)題
input.transferTo(output);
return true;
} catch (IOException e) {
throw e;
} finally {
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
}
}
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String src = "/Users/fzc/Documents/mydir/mldn.txt";
String des = "/Users/fzc/Documents/mydir/mldn_copy.txt";
FileUtil fileUtil = new FileUtil(src, des);
System.out.println(fileUtil.copy() ? "文件拷貝成功!" : "文件拷貝失敗!");
}
}
范例:實(shí)現(xiàn)文件夾拷貝
import java.io.*;
/**
* 定義一個(gè)文件操作的工具類
*/
class FileUtil {
private File srcFile;//源文件
private File desFile;//目標(biāo)文件路徑
public FileUtil(File srcFile, File desFile) {
this.srcFile = srcFile;
this.desFile = desFile;
}
public boolean copyDir()throws IOException {
this.copyImpl(srcFile);
return true;
}
private void copyImpl(File file)throws IOException{
if(file.isDirectory()){
File[] results=file.listFiles();
if(results!=null){
for (File res:results){
copyImpl(res);
}
}
}else{
String newFilePath=file.getPath().replace(srcFile.getPath()+File.separator,"");
File newFile=new File(desFile,newFilePath);//目標(biāo)路徑
copyFileImpl(file,newFile);
}
}
private boolean copyFileImpl(File srcFile,File desFile)throws IOException{
if (desFile.getParentFile().exists() == false) {
desFile.getParentFile().mkdirs();//創(chuàng)建父目錄
}
byte[] data = new byte[1024];//開(kāi)辟一個(gè)緩沖區(qū)
InputStream input = null;
OutputStream output = null;
try {
input = new FileInputStream(srcFile);
output = new FileOutputStream(desFile);
int len = 0;
//1、讀取數(shù)據(jù)到數(shù)組之中,隨后返回讀取的個(gè)數(shù)
//2、判斷個(gè)數(shù)==-1,如果不是則進(jìn)行寫入
//
// do {
// len = input.read(data);
// if(len!=-1){
// output.write(data, 0, len);
// }
// } while (len != -1);
//更為簡(jiǎn)便的寫法
// while ((len=input.read(data))!=-1){
// output.write(data,0,len);
// }
//一定要注意JDK版本問(wèn)題
input.transferTo(output);
return true;
} catch (IOException e) {
throw e;
} finally {
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
}
/**
* 文件拷貝
*
* @return 拷貝是否成功
*/
public boolean copy() throws IOException {
if (srcFile.exists()==false) {
System.out.println("拷貝的源文件不存在");
return false;//源文件不存在拷貝失敗
}
this.copyFileImpl(srcFile,desFile);
return true;
}
}
public class JavaApiDemo {
public static void main(String[] args) throws Exception {
String src = "/Users/fzc/Documents/mydir";
String des = "/Users/fzc/Documents/mydir_copy";
File srcFile=new File(src),desFile=new File(des);
FileUtil fileUtil = new FileUtil(srcFile, desFile);
if(srcFile.isFile()){
System.out.println(fileUtil.copy() ? "文件拷貝成功!" : "文件拷貝失敗!");
}else{
System.out.println(fileUtil.copyDir() ? "目錄拷貝成功!" : "目錄拷貝失敗!");
}
}
}
??本程序是IO操作的核心代碼,如果理解了本程序,整個(gè)IO處理機(jī)制也就非常容易理解了。