字節流與字符流
上一節我們學習了文件操作類File,但是File類雖然可以操作文件,但是卻不能操作文件的內容。如果要進行文件內容的操作,就必須依靠流的概念來完成。流在實際中分爲輸入流和輸出流兩種,輸入流和輸出流是一種相對的概念,關鍵是要看參考點。
Java中針對數據流的操作也分爲輸入與輸出兩種方式,並且提供了以下的支持:
字節流:InputStream(輸入字節流)、OutputStream(輸出字節流);
字符流:Reader(輸入字符流)、Writer(輸出字符流)
注意:這四個操作流的類都屬於抽象類,所以在使用這些類時,必須通過子類對象向上轉型來進行抽象類的實例化操作。
流的基本操作形式:
- 通過File類定義一個要操作文件的路徑;
- 通過字節流的子類對象爲父類對象實例化;
- 進行數據的讀(輸入)、寫(輸出)操作;
- 數據流屬於資源操作,資源操作必須關閉。
其中最重要的式第四步,不管何種情況,只要是資源操作(例如:網絡、文件、數據庫的操作都屬於資源操作),就必須關閉連接(幾乎每種類都會提供close()方法)。
字節輸出流:OutputStream
OutputStream類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public void close() throws IOException | 普通 | 關閉字節輸出流 |
2 | public void flush() throws IOException | 普通 | 強制刷新 |
3 | public abstract void write(int b) throws IOException | 普通 | 輸出單個字節 |
4 | public void write(byte[] b) throws IOException | 普通 | 輸出全部字節數組數據 |
5 | public void write(byte[] b,int off,int len) throws IOException | 普通 | 輸出部分字節數組數據 |
關於OutputStream類的組成說明
OutputStream是一個抽象類,該類的定義如下:
public abstract class OutputStream extends Object implement Closeable,Flushable
由上我們可以發現OutputStream類同時實現了Closeable與Flushable兩個父接口,而這兩個父接口的定義如下:
Closeable接口
public interface Closeable extends AutoCloseable{
public void close() throws IOException;
}
Flushable接口
public interface Flushable{
public void flush() throws IOException
}
通過定義我們可以發現Closeable接口聲明繼承了AutoCloseable(自動關閉)父接口,該接口定義如下:
public interface AutoCloseable{
public void close() throws Excption;
}
通過AutoCloseable接口系統會自動幫助我們(即用戶)調用close()方法釋放資源。
例:自動執行close()操作
package Project.Study.OutputStreamClass;
class Net implements AutoCloseable{
@Override
public void close()throws Exception {
System.out.println("資源自動關閉,釋放資源");
}
public void info()throws Exception {//假設有異常拋出
System.out.println("...");
}
}
public class Test1 {
public static void main(String[]args){
try(Net net=new Net()) {
net.info();
}catch (Exception e){
e.printStackTrace();
}
}
}
//結果:
//...
//資源自動關閉,釋放資源
OutputStream類本身是一個抽象類,這樣就需要一個子類。所以,可以使用FileOutputStream子類完成操作。
FileOutputStream類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public FileOutputStream(File file)throws FileNotFoundException | 構造 | 將內容輸出到指定路徑,如果文件已經存在,則使用新的內容覆蓋舊的內容 |
2 | public FileOutputStream(File file,boolean append)throws FileNotFoundException | 構造 | 如果將布爾參數設置爲true,表示追加新的內容到文件中 |
例:文件內容的輸出
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test2 {
public static void main(String[]args)throws Exception{
//1.定義要輸出文件的路徑
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目錄不存在
file.getParentFile().mkdirs(); //創建目錄
}
//2.應使用OutputStream和其子類進行對象的實例化,此時目錄存在,文件還不存在
OutputStream outputStream=new FileOutputStream(file);
//字節流輸出需要使用byte類型,需要將String類對象變爲字節數組
String str="Hello World!!!";
byte[]data=str.getBytes(); //將字符串變爲字節數組
outputStream.write(data); //輸出內容
outputStream.close(); //資源操作的最後一定要進行關閉
}
}
結果:
例:採用單個字節的方式輸出
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test3 {
public static void main(String[]args)throws Exception{
//1.定義要輸出文件的路徑
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目錄不存在
file.getParentFile().mkdirs(); //創建目錄
}
//2.應使用OutputStream和其子類進行對象的實例化,此時目錄存在,文件還不存在
OutputStream outputStream=new FileOutputStream(file);
//字節流輸出需要使用byte類型,需要將String類對象變爲字節數組
String str="Hello World!!!";
byte[]data=str.getBytes(); //將字符串變爲字節數組
for (int x=0;x<data.length;x++){
outputStream.write(data[x]);//內容輸出
}
outputStream.close(); //資源操作的最後一定要進行關閉
}
}
例:輸出部分字節數組內容(設置數組的開始索引和長度)
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test4 {
public static void main(String[]args)throws Exception{
//1.定義要輸出文件的路徑
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目錄不存在
file.getParentFile().mkdirs(); //創建目錄
}
//2.應使用OutputStream和其子類進行對象的實例化,此時目錄存在,文件還不存在
OutputStream outputStream=new FileOutputStream(file);
//字節流輸出需要使用byte類型,需要將String類對象變爲字節數組
String str="Hello World!!!";
byte[]data=str.getBytes(); //將字符串變爲字節數組
outputStream.write(data,0,6);//內容輸出
outputStream.close(); //資源操作的最後一定要進行關閉
}
}
結果:
注意:採用上面這種方法輸出時,要注意數組越界的問題
我們上面三個文件操作都是對文件內容的覆蓋,而如果要實現文件的追加操作可以使用public FileOutputStream(File file,boolean append)的構造方法。
例:文件追加
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test3 {
public static void main(String[]args) throws Exception {
//1.定義要輸出文件的路徑
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目錄不存在
file.getParentFile().mkdirs(); //創建目錄
}
//2.應使用OutputStream和其子類進行對象的實例化,此時目錄存在,文件還不存在
OutputStream outputStream=new FileOutputStream(file,true);//追加模式
//字節流輸出需要使用byte類型,需要將String類對象變爲字節數組
String str="Hello World!!!";
byte[]data=str.getBytes(); //將字符串變爲字節數組
outputStream.write(data,5,9);//內容輸出
outputStream.close(); //資源操作的最後一定要進行關閉
}
}
結果:
字節輸入流:InputStream
如果要進行文件數據的讀取操作,就可以用java.io.InputStream類完成,此類可以完成字節數據的讀取操作。
InputStream類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public void close() throws IOException | 普通 | 關閉字節輸入流 |
2 | public abstract int read() throws IOException | 普通 | 讀取單個字節 |
3 | public int read(byte[] b) throws IOException | 普通 | 將數據讀取到字節數組中,同時返回讀取長度 |
4 | public int read(byte[] b,int off,int len) throws IOException | 普通 | 將數據讀取到部分字節數組中,同時返回讀取的數據長度 |
InputStream類依舊屬於一個抽象類,此類的定義如下:
public abstract class InputStream extends Object implements Closeable
通過定義可以發現InputStream類也實現了Closeable接口(繼承了AutoCloseable接口),所以利用自動關閉的異常處理結構可以實現自動的資源釋放。
關於InputStream類的三個方法的詳細作用:
-
讀取單個字節:public abstract int read() throws IOException;
返回值:返回讀取的字節內容,如果已經沒有內容,則讀取後返回“-1”; -
將讀取的數據保存在字節數組裏(一次讀取多個數據):public int read(byte[] b) throws IOException;
返回值:返回讀取的數據長度,如果已經讀取到結尾,則讀取後返回“-1”; -
將讀取的數據保存在部分字節數組裏:public int read(byte[] b,int off,int len) throws IOException;
返回值:讀取的部分數據的長度,如果已經讀取到結尾,則讀取後返回“-1”
java.io.InputStream是一個抽象類,所以如果要進行文件的讀取,需要使用FileInputStream子類,而這個子類的構造方法如下:
方法 | 類型 | 描述 |
---|---|---|
public FileInputStream (File file) throws FileNotFoundException | 普通 | 設置要讀取文件數據的路徑 |
例:數據讀取操作
package Project.Study.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Test1 {
public static void main(String[]args) throws Exception {
File file=new File("d:"+File.separator+"Test"+File.separator+"test.txt");//定義要輸出文件的路徑
if (file.exists()){ //判斷文件是否存在後纔可以進行讀取
InputStream inputStream=new FileInputStream(file);//使用InputStream進行讀取
byte[]data=new byte[1024]; //準備一個1024的數組
int len=inputStream.read(data); //進行數據讀取,將內容保存到字節數組中
inputStream.close(); //關閉輸入流
System.out.println("【"+new String(data,0,len)+"】");//將讀取出來的字節數組變爲字符串進行輸出
}
}
}
//結果:
//【Hello World!!!】
例:採用while循環實現輸入流操作
package Project.Study.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Test2 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test"+File.separator+"test.txt");//定義輸出文件的路徑
if (file.exists()){ //判斷文件是否存在後纔可以進行讀取
InputStream inputStream=new FileInputStream(file); //使用InputStream進行讀取
byte[]data=new byte[1024]; //準備一個1024的數組
int foot=0; //表示字節數組的操作腳標
int temp=0; //表示接收每次讀取的字節數據
while((temp=inputStream.read())!=-1){ //當inputStream.read()!=-1,即輸出文件中還有內容
data[foot++]=(byte)temp; //有內容就進行保存
}
inputStream.close(); //關閉輸出流
System.out.println("【"+new String(data,0,foot)+"】");
}
}
}
//結果:
//【Hello World!!!】
字符輸出流:Writer
利用Writer類可以直接實現字符數組(包含字符串)的輸出。
Writer類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public void close() throws IOException | 普通 | 關閉字節輸出流 |
2 | public void flush() throws IOException | 普通 | 強制刷新 |
3 | public Writer append(CharSequence csq) throws IOException | 普通 | 追加數據 |
4 | public void write(String str) throws IOException | 普通 | 輸出字符串數據 |
5 | public void write(char[] cbuf) throws IOException | 普通 | 輸出字符數組數據 |
通過Writer類定義的方法可以發現,Writer類中直接提供了輸出字符串數據的方法。
Writer類的定義:
Writer類也是屬於抽象類,定義如下:
public abstract class Writer extends Object implement Appendable,Closeable,Flushable
通過繼承結構我們可以發現,Writer類中除了實現Closeable與Flushable接口外,還實現了一個Appendable接口,該接口定義如下:
public interface Appendable{
public Appendable append(char c)throws IOException;
public Appendable append(CharSequence csq)throws IOException;
public Appendable append(CharSequence csq,int start,int end)throws IOException;
}
在Appendable接口中定義了一系列數據追加操作,而追加的類型可以是CharSequence(可以保存String、StringBuffer、StringBuilder類對象)。
因爲Writer是一個抽象類,所以要使用java.io.FileWriter類實現Writer類對象的實例化操作。
FileWriter類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public FileWriter(File file) throws IOException | 構造 | 設置輸出文件 |
2 | public FileWriter(File file,boolean append) throws IOException | 普通 | 設置輸出文件以及是否進行數據追加 |
例:使用Writer類實現內容輸出
package Project.Study.WriterClass;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Test1 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test.txt");//定義要輸出文件的路徑
if (!file.getParentFile().exists()){//判斷目錄是否存在
file.getParentFile().mkdirs(); //創建文件目錄
}
Writer writer=new FileWriter(file); //實例化了Writer類的對象
String str="Hello World!!!"; //定義輸出內容
writer.write(str); //輸出字符串內容
writer.close(); //關閉輸出流
}
}
結果:
字符輸入流:Reader
java.io.Reader類是實現字符數據輸入的操作類,在進行數據讀取時可以不使用字節數據,而直接依靠字符數據(方便處理中文)進行操作。
Reader類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public void close() throws IOException | 普通 | 關閉字節輸入流 |
2 | public int read() throws IOException | 普通 | 讀取單個數據 |
3 | public int read() throws IOException | 普通 | 讀取單個字符 |
4 | public int read(char[] cbuf) throws IOException | 普通 | 讀取數據到字符數組中,返回讀取長度 |
5 | public long skip(long n) throws IOException | 普通 | 跳過字節長度 |
Reader類的定義結構:
public abstract class Reader extends Object implement Readable,Closeable
Readable接口定義如下:
public interface Readable{
public int read(CharBuffer cb)throws IOException;
}
在Reader接口中定義的read()方法可以將數據保存在CharBuffer(字符緩衝,類似於StringBuffer)對象中,也就是說利用此類對象就可以替代字符數組的操作。
同樣的,因爲Reader類是一個抽象類,要實現文件數據的字符流讀取,可以利用FileReader子類爲Reader類對象實例化。
FileReader類的常用方法如下:
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public FileReader(File file) throws FileNotFoundException | 構造 | 定義要讀取的文件路徑 |
例:使用Reader讀取數據
package Project.Study.ReaderClass;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class Test1 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test.txt");//定義要輸出的路徑
if (file.exists()){ //判斷文件是否存在
Reader reader=new FileReader(file); //爲Reader對象實例化
char[]data=new char[1024]; //開闢字符數組,接收讀取數據
int len=reader.read(data); //進行數據讀取
reader.close(); //關閉輸入流
System.out.println(new String(data,0,len));
}
}
}
//結果:
//Hello World!!!
字節流與字符流的區別
我們以文件操作爲例,字節流與字符流最大的區別就是:字節流直接與終端文件進行數據交互,字符流需要將數據經過緩衝區處理才與終端文件數據交互。在開發中,對字節數據處理是比較多的,而字符流最大的好處是它可以進行中文的有效處理,因此,在開發中,如果要處理中文時應優先考慮字符流,如果沒有中文問題,建議使用字節流。
在使用OutputStream輸出數據時,即使最後沒有關閉輸出流,內容也可以正常輸出,但是反過來如果使用的是字符輸出流Writer,在執行到最後如果不關閉輸出流,就表示在緩衝區中處理的內容不會被強制性清空,所以就不會輸出數據。如果有特殊情況不能關閉字符輸出流,可以使用flush()方法強制清空緩衝區。
例:錯誤示範(不關閉流)
package Project.Study.WriterClass;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Test2 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test2.txt");//定義輸出文件的路徑
if (!file.getParentFile().exists()){ //判斷文件目錄是否存在
file.getParentFile().mkdirs(); //若不存在就創建文件目錄
}
Writer writer=new FileWriter(file); //實例化了Writer類的對象
String str="Hi!!!"; //定義輸出內容
writer.write(str); //輸出字符串數據
}
}
結果:
通過上程序執行的結果我們可以看到,此時並沒有輸出結果,輸出文件中什麼也沒有。
例:強制清空字符流緩衝區
package Project.Study.WriterClass;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Test2 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test2.txt");//定義輸出文件的路徑
if (!file.getParentFile().exists()){ //判斷文件目錄是否存在
file.getParentFile().mkdirs(); //若不存在就創建文件目錄
}
Writer writer=new FileWriter(file); //實例化了Writer類的對象
String str="Hi!!!"; //定義輸出內容
writer.write(str); //輸出字符串數據
writer.flush(); //強制刷新緩衝區
}
}
結果:
上程序執行到最後並沒有執行流的關閉操作,所以從本質上講,內容將無法完整輸出。但因爲利用了flush()方法強制刷新緩衝區,所以它的內容完整輸出了,也就是說,在不關閉流又要完整輸出內容時就只能利用flush()方法強制刷新緩衝區。