文件流、字節(jié)流、字符流、緩沖流、轉(zhuǎn)換流、內(nèi)存流
文件流: 在JAVA 中,文件流負(fù)責(zé)操作文件,包括讀取和寫入;
FileInputStream // 文件的字節(jié)輸入流;
FileOutputStream // 文件的字節(jié)輸出流;
FileReader // 文件的字符輸入流;
FileWriter // 文件的字符輸出流;
FileInputStream 和 FileOutputStream的父類分別是InputStream和OutputStream,都是字節(jié)流,以面向字節(jié)的形式提供IO操作。FileReader 和 FileWriter的父類則是Reader和Writer,都是字符流,字符流以面向字符提供IO操作,同時(shí),字符流兼容Unicode,能夠提供更好的國際化支持。
在讀取數(shù)據(jù)時(shí),字節(jié)流的數(shù)據(jù)存儲(chǔ)單位是字節(jié),在接收字節(jié)流數(shù)據(jù)時(shí),會(huì)新建一個(gè)字節(jié)類型的數(shù)組(byte[])來接收讀取的數(shù)據(jù);字符流的數(shù)據(jù)存儲(chǔ)單位是字符,在接收字符流數(shù)據(jù)時(shí),會(huì)新建一個(gè)字符類組(char[])的數(shù)據(jù)來接收讀取的數(shù)據(jù)。
InputStream 和 OutputStream
InputStream:
InputStream及其子類的作用是用來表示不同數(shù)據(jù)源輸入數(shù)據(jù)的類,其中封裝了用于從數(shù)據(jù)源中讀取數(shù)據(jù)的操作。這些數(shù)據(jù)源包括:
- 字節(jié)數(shù)組:byte[];
- String對(duì)象;
- 資源文件:File;
- 資源管道;
- 其他流組成的流序列;
- 其他數(shù)據(jù)源,如網(wǎng)絡(luò)資源;
InputStream子類:
- ByteArrayInputStream:將內(nèi)存的緩沖區(qū)當(dāng)作數(shù)據(jù)源輸入;
- StringBufferInputStream:將String轉(zhuǎn)換成InputStream;
- FileInputStream:從本地文件中讀取數(shù)據(jù);
- PipedInputStream:資源管道的輸入端,也是PipedOutputStream的輸入源;
- SequenceInputStream:能夠?qū)?個(gè)或者以上的InputStream合并成一個(gè)單一的InputStream;
- FileterInputStream:為文件資源讀取提供篩選功能;
OutputStream:
OutputStream及其子類的作用是用來表示數(shù)據(jù)輸出將要去向的目標(biāo),比如:文件、資源管道、字節(jié)數(shù)組等。OutputStream也是因不同的目標(biāo)有不同的子類:
- ByteArrayOutputStream:在內(nèi)存中創(chuàng)建緩沖區(qū),所有輸出的數(shù)據(jù)都會(huì)暫存在緩沖區(qū)中;
- FileOutputStream:將數(shù)據(jù)寫出到文件中;
- PipedOutputStream:作為PipedInputStream的輸出端,與PipedInputStream一起實(shí)現(xiàn)資源的“管道化”;
- FileterOutputStream:為文件資源寫出提供篩選功能;
Reader 和 Writer
字符流有著和字節(jié)流近乎一樣的功能,每個(gè)字節(jié)流api都能在字符流中找到與之相對(duì)應(yīng)的,而且,字符流比字節(jié)流有著更好的性能;盡管如此,字符流的設(shè)計(jì)并不是為了取代字節(jié)流,相反,二者同時(shí)存在,相輔相成。
Reader 和 Writer及其子類都提供了對(duì)Unicode的支持,這是他們和InputStream與OutputStream系拓展最大的不同,詳細(xì)如下圖所示:
字節(jié)流和字符流的api對(duì)應(yīng)
文件過濾器
Java IO中提供了過濾器:FilterInputStream和FilterOutputstream,用于篩選特定的輸入流和輸出流。因?yàn)镕ilterInputStream和FilterOutputstream也是自InputStream與OutputStream繼承而來,所以他們都可以讀寫來自InputStream與OutputStream的數(shù)據(jù)。
FilterInputStream子類:
- DataInputStream:用于從流中讀取基本類型的數(shù)據(jù),比如:char、int、long等,往往與DataOutputStream配合使用;
- BufferedInputStream:代表“緩沖區(qū)”,可以將數(shù)據(jù)暫存在緩沖區(qū),使用它可以防止每次讀取都進(jìn)行實(shí)際的寫操作;
- LineNumberInputStream:追蹤輸入流中的行號(hào),可做調(diào)試用;
- PushbackInputStream:能彈出一個(gè)字節(jié)的緩沖區(qū),能夠?qū)⑽募淖詈笠粋€(gè)字符返回(操作系統(tǒng)使用-1表示磁盤文件的結(jié)尾標(biāo)記),通常作為編譯器的掃描器,往往被編譯器調(diào)用;
FilterOutputStream子類:
- DataOutputStream:用于從流中寫出基本類型的數(shù)據(jù),比如:char、int、long等,往往與DataInputStream配合使用;
- PrintStream:用于格式化輸出,比如:java 的運(yùn)行控制臺(tái)打印;能夠與DataOutputStream相互配合使用:DataOutputStream負(fù)責(zé)數(shù)據(jù)的存儲(chǔ),PrintStream負(fù)責(zé)數(shù)據(jù)的顯示;
- BufferedOutputStream:代表“緩沖區(qū)”,可以將數(shù)據(jù)暫存在緩沖區(qū),使用它可以防止每次都進(jìn)行實(shí)際的寫操作;可以使用flush()方法將緩沖區(qū)數(shù)據(jù)一次性寫出;
同樣,在Reader 和 Writer中也提供了與之對(duì)應(yīng)的類,詳細(xì)如下:
文件過濾器 子類
字節(jié)流
字節(jié)流又分字節(jié)輸入流(InputStream)和字節(jié)輸出流(OutputStream),分別對(duì)應(yīng)讀和寫;
字節(jié)輸入流:FileInputStream,字節(jié)輸入流的使用案例代碼如下:
import java.io.*;
/**
* 案例:使用字節(jié)流完成文件拷貝任務(wù)。
*/
public class StreamCopyDemo {
public static void main(String[] args) throws Exception {
// 分別創(chuàng)建文件的源對(duì)象和目標(biāo)對(duì)象
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
// 創(chuàng)建輸入和輸出管道
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
// 讀取/寫出數(shù)據(jù)
byte[] buffer = new byte[1024]; // 設(shè)置每次讀取的字節(jié)數(shù)
int len = -1; // 讀取到的字節(jié)總數(shù)
while ((len = in.read(buffer)) != -1) {
// 寫出數(shù)據(jù)
out.write(buffer, 0, len);
}
// 關(guān)閉輸入/輸出管道
in.close();
out.close();
}
}
字節(jié)輸入流在讀取數(shù)據(jù)時(shí)使用的是read()方法,該方法還有另外的重載方法:
void read(int b);
// 讀取單個(gè)字節(jié)
void read(byte[] b);
// 讀取多個(gè)字節(jié),并存儲(chǔ)到(byte[] b)中
void read(byte[] b, int off, int len);
// 讀取多個(gè)字節(jié),并存儲(chǔ)到(byte[] b)中,從數(shù)組b的off索引為止開始存儲(chǔ)
字節(jié)輸出流:FileOutputStream,字節(jié)輸出流的使用案例代碼如下:
import java.io.*;
/**
* 案例:使用字節(jié)流完成文件拷貝任務(wù)。
*/
public class StreamCopyDemo {
public static void main(String[] args) throws Exception {
// 分別創(chuàng)建文件的源對(duì)象和目標(biāo)對(duì)象
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
// 創(chuàng)建輸入和輸出管道
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
// 讀取/寫出數(shù)據(jù)
byte[] buffer = new byte[1024]; // 設(shè)置每次讀取的字節(jié)數(shù)
int len = -1; // 讀取到的字節(jié)總數(shù)
while ((len = in.read(buffer)) != -1) {
// 寫出數(shù)據(jù)
out.write(buffer, 0, len);
}
// 關(guān)閉輸入/輸出管道
in.close();
out.close();
}
}
與字節(jié)輸入流在讀取數(shù)據(jù)時(shí)使用的read()方法相對(duì)應(yīng)的,字節(jié)輸出流也提供了對(duì)應(yīng)的寫出數(shù)據(jù)的方法:write(),該方法也有一些重載方法:
out.write(int b);
// b 寫入文件的字節(jié)
out.write(byte[] b);
// b 要寫入文件的數(shù)據(jù),類型為byte數(shù)組
out.write(byte[] b, int off, int len);
// 把字節(jié)數(shù)組中的從off索引開始的len個(gè)字節(jié)寫到文件中
注意:字節(jié)流讀寫中的數(shù)據(jù)類型是字節(jié)數(shù)據(jù)類型(byte[])。
案例1,使用字節(jié)流完成文件拷貝任務(wù)。
import java.io.*;
/**
* 案例:使用字節(jié)流完成文件拷貝任務(wù)。
*/
public class StreamCopyDemo {
public static void main(String[] args) throws Exception {
// 分別創(chuàng)建文件的源對(duì)象和目標(biāo)對(duì)象
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
// 創(chuàng)建輸入和輸出管道
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
// 讀取/寫出數(shù)據(jù)
byte[] buffer = new byte[1024]; // 設(shè)置每次讀取的字節(jié)數(shù)
int len = -1; // 讀取到的字節(jié)總數(shù)
while ((len = in.read(buffer)) != -1) {
// 寫出數(shù)據(jù)
out.write(buffer, 0, len);
}
// 關(guān)閉輸入/輸出管道
in.close();
out.close();
}
}
字符流
字符流又分字符輸入流(Reader)和字符輸出流(Writer),分別對(duì)應(yīng)文件讀和寫;需要注意的是:字符流讀寫中的數(shù)據(jù)類型是字符數(shù)據(jù)類型(char[]),區(qū)別于字節(jié)流中的字節(jié)類型(byte[])。
字符輸入流,F(xiàn)ileReader,字符輸入流的使用案例代碼如下:
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
/**
* 文件的字符輸入流案例
*/
public class FileReaderDemo {
public static void main(String[] args) throws Exception {
// 創(chuàng)建資源對(duì)象
File src = new File("file_reader.txt");
// 創(chuàng)建文件字符輸入流
Reader reader = new FileReader(src);
char[] buffer = new char[1024]; // 每次讀取1024個(gè)字符
int len = -1; // 當(dāng)前讀取到的字符長度
while ((len = reader.read(buffer))!= -1) {
String line = new String(line, 0, len);
System.out.println(line);
}
// 關(guān)閉字符輸入流
reader.close();
}
}
字符輸入流在讀取數(shù)據(jù)時(shí)使用的是read()方法,該方法還有另外的重載方法:
int read();
// 每次讀取一個(gè)字符,直至文件末尾,返回-1為止int read(char[] buffer); // 每次都能讀物多個(gè)字符,并把讀取到的字符存儲(chǔ)到buffer中
字符輸出流,F(xiàn)ileWriter,字符輸出流的使用案例代碼如下:
import java.io.*;
/**
* 文件的字符輸出流案例
*/
public class FileWriterDemo {
public static void main(String[] args) throws Exception {
// 創(chuàng)建目標(biāo)對(duì)象
File target = new File("file_writer.txt");
// 創(chuàng)建文件字符輸出流
Writer writer = new FileWriter(target);
writer.write("dkgfkdsfowmsdfmowemfsdmfkemfkmfd");
writer.flush();
// 關(guān)閉字符輸出流
writer.close();
}
}
字符輸出流在讀取數(shù)據(jù)時(shí)使用的是write()方法,該方法還有另外的重載方法:
void write(int c);
// 向文件中寫出一個(gè)字符
void write(char[] buffer);
// 向文件中寫出多個(gè)字符(char[] buffer)
void write(char[] buffer, int off, int len);
// 向文件中寫出多個(gè)字符(char[] buffer),從off位置開始,長度為len
void write(String str);
// 向文字中寫出一個(gè)字符串
案例2,使用字符流完成文件拷貝任務(wù)
import java.io.*;
/**
* 案例:使用字符流完成文件拷貝任務(wù)。
*/
public class FileCopyDemo {
public static void main(String[] args) throws Exception {
// 創(chuàng)建文件源對(duì)象和目標(biāo)對(duì)象
File src = new File("file_reader.txt");
File target = new File("file_reader_copy.txt");
// 創(chuàng)建輸入流和輸出流
Reader reader = new FileReader(src);
Writer writer = new FileWriter(target);
// 文件讀和寫
char[] buffer = new char[1024];
int len = -1;
while((len = reader.read(buffer)) != -1) {
writer.write(buffer, 0, len);
}
// 關(guān)閉輸入流和輸出流
reader.close();
writer.close();
}
}
文件拷貝
案例3,使用流完成Java 代碼動(dòng)態(tài)編譯、運(yùn)行
import java.io.*;
/**
* 案例3,使用流完成Java 代碼動(dòng)態(tài)編譯、運(yùn)行
*/
public class EvalDemo {
public static void main(String[] args) {
String code = "System.out.println("Eval Java");";
try {
// 構(gòu)建java代碼
buildJavaCode(code);
// 編譯Java 代碼
compileJavaCode();
// 運(yùn)行Java 代碼
runJavaCode();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 構(gòu)建java代碼
*/
private static void buildJavaCode(String code) throws Exception {
// 拼接Java 主函數(shù)
StringBuilder builder = new StringBuilder(80);
builder.Append("public class Eval {");
builder.append("public static void main(String[] args) {");
builder.append(code);
builder.append("}");
builder.append("}");
// 保存拼接好的java代碼到文件中
OutputStream out = new FileOutputStream("Eval.java");
out.write(builder.toString().getBytes("UTF-8"));
out.close();
}
/**
* 編譯Java 代碼
*/
private static void compileJavaCode() throws Exception {
// 調(diào)用javac 編譯保存的java文件
Process javacProcess = Runtime.getRuntime().exec("javac Eval.java");
// 讀取編譯錯(cuò)誤結(jié)果,并輸出
InputStream error = javacProcess.getErrorStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = error.read(buffer)) != -1) {
String msg = new String(buffer, 0, len);
System.out.println("Eval 編譯:" + msg);
}
// 關(guān)閉javac 編譯進(jìn)程
error.close();
}
/**
* 運(yùn)行Java 代碼
*/
private static void runJavaCode() throws Exception {
// 調(diào)用java 運(yùn)行編譯好的java字節(jié)碼文件
Process javaProcess = Runtime.getRuntime().exec("java Eval");
// 讀取java字節(jié)碼文件的運(yùn)行信息。并打印輸出
InputStream info = javaProcess.getInputStream();
len = -1;
buffer = new byte[1024];
while ((len = info.read(buffer)) != -1) {
String msg = new String(buffer, 0, len);
System.out.println("Eval 運(yùn)行: " + msg);
}
info.close();
// 刪除java問價(jià)和class文件
new File("Eval.java").delete();
new File("Eval.class").delete();
}
}
緩沖區(qū)
在上述的字符輸出流的使用案例中,使用了flush()方法,該方法有何作用呢?
因?yàn)橛?jì)算機(jī)訪問外部設(shè)備(磁盤文件)時(shí),要比直接訪問內(nèi)存慢很多,如果每次調(diào)用write()方法都要直接把數(shù)據(jù)寫出到磁盤文件中,CPU會(huì)花更多的時(shí)間,計(jì)算機(jī)執(zhí)行性能會(huì)大大降低。
因此,我們可以準(zhǔn)備一個(gè)內(nèi)存緩沖區(qū),程序每次調(diào)用write()方法時(shí),會(huì)將數(shù)據(jù)先寫到內(nèi)存緩沖區(qū)中,暫存于緩沖區(qū)中;當(dāng)內(nèi)存緩沖區(qū)滿后,系統(tǒng)才把緩沖區(qū)中暫存的數(shù)據(jù)一次性寫出到磁盤文件中。這樣就能減少直接操作本地文件系統(tǒng)的頻率,使得系統(tǒng)性能得以提升。
flush()方法
flush(),刷新操作,輸出流中都有flush方法。該方法的作用正是把緩沖區(qū)中暫存的數(shù)據(jù)一次性寫出到磁盤文件中。
使用緩沖區(qū)的好處:
- 提高CPU使用率,提高系統(tǒng)的執(zhí)行性能;
- 有機(jī)會(huì)回滾寫入的數(shù)據(jù),能避免一定的臟數(shù)據(jù);
- 緩沖區(qū)大小一般使用容量整數(shù)倍,可以提高IO性能;
對(duì)于字節(jié)流,flush方法不是都有作用(部分字節(jié)流才有作用),對(duì)于字符流、緩沖流則都起作用。如果我們調(diào)用close方法關(guān)閉系統(tǒng)資源,那么,系統(tǒng)在關(guān)閉資源前,會(huì)先調(diào)用flush方法。
.
資源關(guān)閉
資源關(guān)閉分為手動(dòng)關(guān)閉和自動(dòng)關(guān)閉兩種:
- 手動(dòng)關(guān)閉:手動(dòng)調(diào)用流的close()方法;
- 自動(dòng)關(guān)閉:不需要手動(dòng)調(diào)用close方法,不過,前提是資源對(duì)象實(shí)現(xiàn)了AutoClose接口;
import java.io.*;
/**
* 資源關(guān)閉案例
*/
public class StreamCloseDemo {
public static void main(String[] args) {
// 資源手動(dòng)關(guān)閉
closeManual();
// 資源自動(dòng)關(guān)閉
closeAuto();
}
/**
* 資源手動(dòng)關(guān)閉
*/
private static void closeManual() {
InputStream in = null;
OutputStream out = null;
try {
// 分別創(chuàng)建文件的源對(duì)象和目標(biāo)對(duì)象
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
// 創(chuàng)建輸入和輸出管道
in = new FileInputStream(src);
out = new FileOutputStream(target);
// 讀取/寫出數(shù)據(jù)
byte[] buffer = new byte[1024]; // 設(shè)置每次讀取的字節(jié)數(shù)
int len = -1; // 讀取到的字節(jié)總數(shù)
while ((len = in.read(buffer)) != -1) {
// 寫出數(shù)據(jù)
out.write(buffer, 0, len);
}
} catch (Exception e ) {
e.printStackTrace();
} finally {
// 關(guān)閉輸入/輸出管道
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 資源自動(dòng)關(guān)閉
*/
private static void closeAuto() {
// 分別創(chuàng)建文件的源對(duì)象和目標(biāo)對(duì)象
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
try (// 創(chuàng)建輸入和輸出管道
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
) {
// 讀取/寫出數(shù)據(jù)
byte[] buffer = new byte[1024]; // 設(shè)置每次讀取的字節(jié)數(shù)
int len = -1; // 讀取到的字節(jié)總數(shù)
while ((len = in.read(buffer)) != -1) {
// 寫出數(shù)據(jù)
out.write(buffer, 0, len);
}
} catch (Exception e ) {
e.printStackTrace();
}
}
}
包裝類/緩沖流
字節(jié)緩沖輸入流,BufferedInputStream,案例代碼如下:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 字節(jié)緩沖輸入流
*/
public class BufferedInputStreamDemo {
public static void main(String[] args) throws Exception {
// 字節(jié)輸入流
InputStream in = new FileInputStream("stream.txt");
// 字節(jié)緩沖輸入流
BufferedInputStream bis = new BufferedInputStream(in);
// 寫出到文件的內(nèi)容
String content = "BufferedOutputStream";
// 通過字節(jié)緩沖輸入流讀取數(shù)據(jù)
byte[] buffer = new byte[1024];
int len = -1;
while ((len = bis.read(buffer)) != -1) {
String line = new String(buffer, 0, len);
System.out.println(line);
}
in.close();
bis.close();
}
}
字節(jié)緩沖輸出流,BufferedOutputStream,案例代碼如下:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
/**
* 字節(jié)緩沖輸出流
*/
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws Exception {
// 字節(jié)輸出流
OutputStream out = new FileOutputStream("stream.txt", true);
// 字節(jié)緩沖輸出流
BufferedOutputStream bos = new BufferedOutputStream(out);
// 寫出到文件的內(nèi)容
String content = "BufferedOutputStream";
// 通過字節(jié)緩沖輸出流寫出數(shù)據(jù)
bos.write(content.getBytes());
out.close();
bos.close();
}
}
字符緩沖輸入流,BufferedReader,案例代碼如下:
import java.io.*;
/**
* 字符緩沖輸入流
*/
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception {
// 字符輸入流
Reader reader = new FileReader("stream.txt");
// 字符緩沖輸入流
BufferedReader br = new BufferedReader(reader);
// 讀取文件內(nèi)容
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 關(guān)閉流
reader.close();
br.close();
}
}
字符緩沖輸出流,BufferedWriter,案例代碼如下:
import java.io.*;
/**
* 字符緩沖輸出流
*/
public class BufferedWriterDemo {
public static void main(String[] args) throws Exception {
// 字符輸出流
Writer writer = new FileWriter("stream.txt");
// 字符緩沖輸出流
BufferedWriter bw = new BufferedWriter(writer);
bw.write("這世界很美好");
bw.newLine();
bw.write("世界也很美好");
// 關(guān)閉流
writer.close();
bw.close();
}
}
字節(jié)流、緩沖流性能測(cè)試比較:
import java.io.*;
/**
* 字節(jié)流、緩沖流性能測(cè)試
*/
public class BufferedDemo {
public static void main(String[] args) {
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
try {
// 字節(jié)流性能測(cè)試
testSingleByteStream(src, target);
testSingleBufferedStream(src, target);
// 緩沖流性能測(cè)試
testMultiByteStream(src, target);
testMultiBufferedStream(src, target);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 字節(jié)流性能測(cè)試
* 每次讀寫都是一個(gè)字節(jié)
*
*/
private static void testSingleByteStream(File src, File target) throws Exception {
long begin = System.currentTimeMillis();
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
int len = -1;
while ((len = in.read()) != -1) {
out.write(len);
}
in.close();
out.close();
long end = System.currentTimeMillis();
System.out.println("SingleByteStream 耗費(fèi):" + (end - begin) + "ms");
}
/**
* 緩沖流性能測(cè)試
* 每次讀寫都是一個(gè)字節(jié)
*
*/
private static void testSingleBufferedStream(File src, File target) throws Exception {
long begin = System.currentTimeMillis();
// 創(chuàng)建文件流
InputStream srcStream = new FileInputStream(src);
OutputStream targetStream = new FileOutputStream(target);
// 穿甲緩沖流
InputStream in = new BufferedInputStream(srcStream);
OutputStream out = new BufferedOutputStream(targetStream);
int len = -1;
while ((len = in.read()) != -1) {
out.write(len);
}
// 關(guān)閉流
in.close();
out.close();
long end = System.currentTimeMillis();
System.out.println("SingleBufferedStream 耗費(fèi):" + (end - begin) + "ms");
}
/**
* 字節(jié)流性能測(cè)試
* 使用多字節(jié)讀寫
*
*/
private static void testMultiByteStream(File src, File target) throws Exception {
long begin = System.currentTimeMillis();
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
int len = -1;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
in.close();
out.close();
long end = System.currentTimeMillis();
System.out.println("MultiByteStream 耗費(fèi):" + (end - begin) + "ms");
}
/**
* 緩沖流性能測(cè)試
* 使用多字節(jié)讀寫
*
*/
private static void testMultiBufferedStream(File src, File target) throws Exception {
long begin = System.currentTimeMillis();
// 創(chuàng)建文件流
InputStream srcStream = new FileInputStream(src);
OutputStream targetStream = new FileOutputStream(target);
// 穿甲緩沖流
InputStream in = new BufferedInputStream(srcStream);
OutputStream out = new BufferedOutputStream(targetStream);
int len = -1;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
// 關(guān)閉流
in.close();
out.close();
long end = System.currentTimeMillis();
System.out.println("MultiBufferedStream 耗費(fèi):" + (end - begin) + "ms");
}
}
上述測(cè)試運(yùn)行結(jié)果如下:
SingleByteStream 耗費(fèi):3610ms
SingleBufferedStream 耗費(fèi):31ms
MultiByteStream 耗費(fèi):16ms
MultiBufferedStream 耗費(fèi):0ms
性能孰高孰低,測(cè)試結(jié)果已經(jīng)很能說明問題了。
轉(zhuǎn)換流
由于字節(jié)流和字符流同時(shí)存在,有時(shí)候會(huì)把二者結(jié)合起來使用,甚至在二者之間實(shí)現(xiàn)轉(zhuǎn)換;為了實(shí)現(xiàn)這樣的功能,Java IO中提供了字節(jié)流和字符流的適配器:
- InputStreamReader:可以把InputStream轉(zhuǎn)換為Reader;
- OutputStreamWriter:可以把OutputStream轉(zhuǎn)換為Writer;
以下便是這個(gè)適配器—“轉(zhuǎn)換流”的使用案例,案例通過文件的拷貝操作來體現(xiàn),在讀寫的過程中,均使用了特定編碼。案例代碼實(shí)現(xiàn)如下:
import java.io.*;
/**
* 轉(zhuǎn)換流案例
*/
public class StreamConvertDemo {
public static void main(String[] args) throws Exception {
// 創(chuàng)建文件對(duì)象
File src = new File("stream.txt");
File target = new File("stream_copy.txt");
// 創(chuàng)建文件流
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(target);
// 創(chuàng)建字節(jié)/字符轉(zhuǎn)換流
Reader reader = new InputStreamReader(in, "UTF-8");
Writer writer = new OutputStreamWriter(out, "UTF-8");
// 操作字符流完成文件讀寫
char[] buffer = new char[1024];
int len = -1;
while ((len = reader.read(buffer)) != -1) {
writer.write(buffer, 0, len);
}
// 關(guān)閉流
in.close();
out.close();
reader.close();
writer.close();
}
}
為什么只有字節(jié)轉(zhuǎn)字符流,卻沒有字符轉(zhuǎn)字節(jié)流呢?原因也很簡(jiǎn)單:
- 字節(jié)流可以操作一切文件,包括純文本文件、二進(jìn)制文件;
- 字符流是對(duì)字節(jié)流的增強(qiáng),是用來操作純文本使用的;
字節(jié)流和字符流之間并不對(duì)等,無法實(shí)現(xiàn)轉(zhuǎn)換,就比如C++程序可以運(yùn)行C程序,但C程序卻不能運(yùn)行C++程序。
數(shù)組流/內(nèi)存流
面向字節(jié)的數(shù)組流/內(nèi)存流:ByteArrayInputStream和ByteArrayOutputStream;該“流”會(huì)把數(shù)據(jù)暫存到內(nèi)存中,以便實(shí)時(shí)讀寫,讀寫時(shí)使用的字節(jié)類型(byte[]);
- ByteArrayInputStream:把數(shù)據(jù)從程序輸出到內(nèi)存中;
- ByteArrayOutputStream:把數(shù)據(jù)從內(nèi)存讀取到程序中;
代碼案例如下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
* 面向字節(jié)的數(shù)組流/內(nèi)存流
*/
public class ByteArrayDemo {
public static void main(String[] args) throws Exception {
String content = "dfjdslkfjdlsfjld";
// 字節(jié)數(shù)組輸出流:程序 -> 內(nèi)存
ByteArrayOutputStream bas = new ByteArrayOutputStream();
bas.write(content.getBytes());
// 緩存字節(jié)區(qū)
byte[] data = bas.toByteArray();
// 字節(jié)數(shù)組輸入流:內(nèi)存 -> 程序
ByteArrayInputStream bai = new ByteArrayInputStream(data);
byte[] buffer = new byte[1024];
int len = -1;
while ((len = bai.read(buffer)) != -1) {
String line = new String(buffer, 0, len);
System.out.println(line);
}
// 關(guān)閉流
bas.close();
bai.close();
}
}
面向字符的數(shù)組流/內(nèi)存流,CharArrayReader和CharArrayWriter,該“流”會(huì)把數(shù)據(jù)暫存到內(nèi)存中,以便實(shí)時(shí)讀寫,讀寫時(shí)使用的字符類型(char[]);
代碼案例如下:
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
/**
* 面向字符的數(shù)組流/內(nèi)存流
*/
public class CharArrayDemo {
public static void main(String[] args) throws Exception {
// 字符數(shù)組輸出流
CharArrayWriter writer = new CharArrayWriter();
writer.write("這世界很美好");
char[] data = writer.toCharArray();
// 字符數(shù)組輸入流
CharArrayReader reader = new CharArrayReader(data);
char[] buffer = new char[1024];
int len = -1;
while ((len = reader.read(buffer)) != -1) {
String line = new String(buffer, 0, len);
System.out.println(line);
}
// 關(guān)閉流
writer.close();
reader.close();
}
}
面向字符串的內(nèi)存流,StringReader/StringWriter,把數(shù)據(jù)臨時(shí)存儲(chǔ)到字符串中,也同樣是在內(nèi)存中,但數(shù)據(jù)的類型是字符串;
案例代碼如下:
import java.io.StringReader;
import java.io.StringWriter;
/**
* 面向字符串的內(nèi)存流
*/
public class StringWriterReaderDemo {
public static void main(String[] args) throws Exception {
// 字符串輸出流
StringWriter stringWriter = new StringWriter();
stringWriter.write("這世界很美好");
stringWriter.write("社會(huì)也很美好");
System.out.println(stringWriter.toString());
// 字符串輸入流
StringReader stringReader = new StringReader(stringWriter.toString());
char[] buffer = new char[1024];
int len = -1;
while ((len = stringReader.read(buffer)) != -1) {
String line = new String(buffer, 0, len);
System.out.println(line);
}
// 關(guān)閉流
stringReader.close();
stringWriter.close();
}
}
合并流
合并流,SequenceInputStream:能夠?qū)?個(gè)或者以上的InputStream合并成一個(gè)單一的InputStream,進(jìn)而進(jìn)行合并操作;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.SequenceInputStream;
/**
* 合并流
*/
public class SequenceInputStreamDemo {
public static void main(String[] args) throws Exception {
// 創(chuàng)建輸入隊(duì)列流
SequenceInputStream seqIn = new SequenceInputStream(
new FileInputStream("stream1.txt"),
new FileInputStream("stream2.txt"),
new FileInputStream("stream3.txt"));
byte[] buffer = new byte[1024];
int len = -1;
while ((len = seqIn.read(buffer)) != -1) {
String line = new String(buffer, 0, len);
System.out.println(line);
}
seqIn.close();
}
}
字符編碼
字符編碼的發(fā)展歷程:
階段1:計(jì)算機(jī)只認(rèn)識(shí)數(shù)字0和1,計(jì)算機(jī)里的一切數(shù)據(jù)都是以數(shù)字來表示;因?yàn)橛⑽姆?hào)有限,所以規(guī)定使用的字節(jié)的最高位是0,每一個(gè)字節(jié)都是以0~127之間的數(shù)字來表示,比如:A對(duì)應(yīng)65,a對(duì)應(yīng)97..這就是美國標(biāo)準(zhǔn)信息交換碼-ASCII;
階段2:隨著計(jì)算機(jī)在全球的普及,很多國家和地區(qū)都把自己的字符引入了計(jì)算機(jī)中,比如漢字;但是ASCII中的一個(gè)字節(jié)能表示數(shù)字范圍太小,不能包含所有的中文漢字,所以就規(guī)定使用兩個(gè)字節(jié)來表示一個(gè)漢字;
所以決定,原有的ASCII字符的編碼保持不變,仍然使用一個(gè)字節(jié)表示;為了區(qū)別一個(gè)中文字符與兩個(gè)ASCII碼字符:
- 中文字符的每個(gè)字節(jié)最高位規(guī)定為1(中文的二進(jìn)制是負(fù)數(shù)),這個(gè)規(guī)范就是GB2312編碼;
- 后來在GB2312的基礎(chǔ)上增加了更多的中文字符,比如簡(jiǎn)體中文,GBK應(yīng)運(yùn)而生;
階段3:新的問題產(chǎn)生了,在中國是認(rèn)識(shí)漢字的,支持GBK編碼,但是如果把GBK編碼的漢字傳遞給其他國家,該國家的編碼表中沒有收錄該漢字的字符,故不能正常顯示,很容易就出現(xiàn)了亂碼。
為了解決各個(gè)國家因?yàn)楸镜鼗址幋a帶來的影響,把全世界所有的符號(hào)統(tǒng)一進(jìn)行編碼-Unicode編碼;此時(shí)某一個(gè)字符在全世界任何地方都是固定的,比如字符:'哥',在任何地方都是以十六進(jìn)制的54E5來表示。Unicode的每個(gè)編碼字符都占有2個(gè)字節(jié)大小。
常見的字符集:
- ASCII:每個(gè)字符占一個(gè)字節(jié),只能包含128個(gè)符號(hào),不能表示漢字;
- ISO-8859-1:每個(gè)字符占一個(gè)字節(jié),收錄西歐語言,不能表示漢字;
- ANSI:每個(gè)字符占兩個(gè)字節(jié),在簡(jiǎn)體中文的操作系統(tǒng)中,ANSI 就指的是 GB2312;
- GB2312/GBK/GB18030:每個(gè)字符占兩個(gè)字節(jié),中文專用編碼;
- UTF-8:是一種針對(duì)Unicode的可變長度字符編碼,又稱萬國碼,是Unicode的實(shí)現(xiàn)方式之一,目前的應(yīng)用率最高;
- UTF-8 BOM:是微軟搞出來的編碼,默認(rèn)占3個(gè)字節(jié),使用率并不高;
編碼中的第一個(gè)字節(jié)仍與ASCII兼容,這使得原來處理ASCII字符的軟件無須或只須做少部份修改,即可繼續(xù)使用。因此,它逐漸成為電子郵件、網(wǎng)頁及其他存儲(chǔ)或傳送文字的應(yīng)用中,優(yōu)先采用的編碼。
互聯(lián)網(wǎng)工程工作小組(IETF)要求所有互聯(lián)網(wǎng)協(xié)議都必須支持UTF-8編碼。
存儲(chǔ)字母,數(shù)字和漢字
存儲(chǔ)字母和數(shù)字時(shí)無論是什么字符集都只占1個(gè)字節(jié)(因?yàn)楹虯SCII兼容),但是存儲(chǔ)漢字:GBK家族占兩個(gè)字節(jié),UTF-8家族占3個(gè)字節(jié)。
存儲(chǔ)中文時(shí),GBK和UTF-8都是OK的。
import java.util.Arrays;
/**
* 字符編碼
*/
public class EncodingDemo {
public static void main(String[] args) throws Exception {
String str = "老夫不正經(jīng)";
// 以特定編碼格式編碼字符串
byte[] data = str.getBytes("UTF-8");
System.out.println(Arrays.toString(data));
// 解碼字符串
str = new String(data, "ISO-8859-1");
System.out.println(str);
// 此時(shí),字符串str是亂碼:è??夫ä¸?æ£ç»?
// 解決方案便是,使用ISO-8859-1來讀取字節(jié)數(shù)據(jù),然后使用UTF-8重新編碼
data = str.getBytes("ISO-8859-1");
System.out.println(Arrays.toString(data));
str = new String(data, "UTF-8");
System.out.println(str);
}
}
小結(jié)
- InputStream 和 OutputStream
- Reader 和 Writer
- 文件過濾器
- 字節(jié)流
- 字符流
- 緩沖區(qū)
- 資源關(guān)閉
- 緩沖流
- 轉(zhuǎn)換流
- 內(nèi)存流
- 合并流
- 字符編碼
完結(jié),老夫雖不正經(jīng),但老夫一身的才華。