谷歌I/O大會(huì),現(xiàn)在趕一下時(shí)髦,在這里說(shuō)一下Java的I/O流。
I/O流可以簡(jiǎn)單的理解input/output流,既是輸入輸出流。谷歌I/O大會(huì)也可以解釋為谷歌輸入/輸出大會(huì),哈哈!電腦不就是用來(lái)輸入輸出的工具嘛,這樣理解也沒(méi)問(wèn)題。
話歸正題,I/O流在java中是屬于基礎(chǔ)類,是很重要的一個(gè)工具,有了它,才能夠?qū)崿F(xiàn)信息之間的交互,即從本地向網(wǎng)絡(luò)發(fā)出請(qǐng)求,然后在網(wǎng)絡(luò)中傳回信息。
input和output是相對(duì)而言的,即我們擁有一個(gè)客戶端處理器,如果外部有信息要傳進(jìn)來(lái)處理,那邊是輸入流input,而當(dāng)我們有信息要輸出時(shí),那邊是輸出流output了。
I/O流解析
舉個(gè)栗子來(lái)說(shuō)吧,
例子1:我們?cè)谄匠J褂檬謾C(jī)上網(wǎng)的時(shí)候,從手機(jī)上點(diǎn)擊鏈接,這時(shí)向互聯(lián)網(wǎng)發(fā)出指令,這是就是輸出output。而當(dāng)互聯(lián)網(wǎng)返回頁(yè)面的時(shí)候,那么就是輸入流了。
例子2:當(dāng)我們下載文件的時(shí)候,互聯(lián)網(wǎng)源源不斷的將文件內(nèi)容傳到手機(jī)上,這是輸入流input。但同時(shí)傳到手機(jī)上時(shí)只是一個(gè)緩存,還需要在存儲(chǔ)卡中把內(nèi)容保存起來(lái),因此手機(jī)把文件信息保存到內(nèi)置存儲(chǔ)卡中時(shí)便是輸出流output了。
解釋完什么是I/O,現(xiàn)在我們開(kāi)始輸入/輸出流的使用吧。
I/O流的使用
我們?cè)谶@里主要講解Stream流、Reader和Writer這三種。
字節(jié)流
Java中的Stream流主要指InputStream流和OutputStream流。
其中InputStream流和OutputStream流是抽象類,但是它們各自有同名的子類InputStream流和OutputStream,沒(méi)錯(cuò),它們的名字是一樣的。
作為抽象類的InputStream有讀取字節(jié)流(byte)的read()方法,它可以讀取字節(jié)或者字節(jié)數(shù)組,其次是close()方法,每次使用InputStream流之后記得使用close()方法,這樣才不會(huì)占用系統(tǒng)資源。
相應(yīng)的OutputStream流著擁有wrire()和close()方法,效果同InputSream,只是它是將直接寫(xiě)出。
作為抽象類的InputStream流和OutputStream流,他們各自有許多個(gè)實(shí)現(xiàn)的之類
如InputStream的子類有:
AudioInputStream
, ByteArrayInputStream
, FileInputStream
, FilterInputStream
, InputStream
, ObjectInputStream
, PipedInputStream
, SequenceInputStream
, StringBufferInputStream
如OutputStream的子類有:
ByteArrayOutputStream
, FileOutputStream
, FilterOutputStream
, ObjectOutputStream
, OutputStream
, PipedOutputStream
其中有個(gè)子類FileInputStream流,這個(gè)子類是從電腦磁盤(pán)或者內(nèi)存卡中的文件直接讀取字節(jié),而和FileOutputStream流則直接輸出信息到文件的流。
用法:這是復(fù)制文本文件的一個(gè)方法,src和dst分別指?jìng)魅牒蛡鞒龅奈谋疚募窂?/p>
public static void copyFile(File src, File dst) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dst);
int data = -1;
// fis.read()如果已到達(dá)文件末尾,則返回 -1。
while ((data = fis.read()) != -1) {
fos.write(data);
}
fis.close();
fos.close();
}
上面的方法雖然能達(dá)到目的,但是在實(shí)際使用的時(shí)候因?yàn)槿狈彺妫瑥?fù)制文件的時(shí)間過(guò)長(zhǎng)導(dǎo)致卡頓,因此可以使用緩存區(qū)來(lái)加速,可以將代碼修改為:
public static void copyFile(File src, File dst) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dst);
// 創(chuàng)建一個(gè)1M大小的緩沖區(qū),用來(lái)存放輸入流中的字節(jié)
byte[] buf = new byte[1024 * 1024];
// 用來(lái)保存實(shí)際讀到的字節(jié)數(shù)
int len = 0;
// fis.read(buf) 讀入緩沖區(qū)的字節(jié)總數(shù),如果因?yàn)橐呀?jīng)到達(dá)文件末尾而沒(méi)有更多的數(shù)據(jù),則返回 -1。
while ((len = fis.read(buf)) != -1) {
// fos.write(buf, 0, len) 將指定 byte 數(shù)組中從偏移量 0 開(kāi)始的 len 個(gè)字節(jié)寫(xiě)入此文件輸出流。
fos.write(buf, 0, len);
}
fis.close();
fos.close();
}
接下來(lái)就是ByteArrayInputStream流,它將字節(jié)數(shù)組當(dāng)作源的輸入流;ByteArrayOutputStream則是將字節(jié)數(shù)組當(dāng)作目標(biāo)的輸出流。值得注意的是這個(gè)輸入輸出流雖然都有一個(gè)close()方法,但是由于ByteArrayInputSteam/ByteArrayOutputStream并沒(méi)有調(diào)用系統(tǒng)資源,因此這個(gè)關(guān)閉方法沒(méi)有任何意義。
ByteArrayInputSteam使用示例:
public static void main(String[] args) throws IOException {
String str = "hello,World";
ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
int data = -1;
while ((data = bis.read()) != -1) {
System.out.print((char) data);
}
bis.close();
}
ByteArrayOutputStream使用示例:
public static void main(String[] args) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 寫(xiě)入字節(jié),字符a在Unicode編碼表中是97,A是65
bos.write(97);
bos.write(65);
bos.write("hello,world".getBytes());
byte[] buff = bos.toByteArray();
for (byte data : buff) {
System.out.print((char) data);
}
}
FilterInputStream/FilterOutputStream過(guò)濾流
FilterInputStream/FilterOutputStream過(guò)濾流,為底層透明的提供擴(kuò)展方法的輸入/輸出流的包裝。過(guò)濾流的構(gòu)造方法需要傳入一個(gè)參數(shù),F(xiàn)ilterInputStream流及其子類均需要傳入InputStream流,而FilterOutputStream流及其子類則需要傳入OutputStream流。
** FilterInputStream的子類有:**
BufferedInputStream,
CheckedInputStream,
CipherInputStream,
DataInputStream,
DeflaterInputStream,
DigestInputStream,
InflaterInputStream,
LineNumberInputStream,
ProgressMonitorInputStream,
PushbackInputStream
FilterOutputStream的子類有:
BufferedOutputStream,
CheckedOutputStream,
CipherOutputStream,
DataOutputStream,
DeflaterOutputStream,
DigestOutputStream,
InflaterOutputStream,
PrintStream
例如BufferedInputStream/BufferedOutputStream流,它們自帶一個(gè)緩沖區(qū),不必像上面copyFile()方法一般需要自定義一個(gè)byte數(shù)組來(lái)充當(dāng)緩沖區(qū),我們?cè)俅涡薷腸opyFile()方法,實(shí)現(xiàn)用過(guò)濾流復(fù)制文件。
public static void copyFile(File src, File dst) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dst);
// FileInputStream與FileOutputStream是InputStram和OutputStream的子類,
// 因此能直接被BufferedInputStream與BufferedOutputStream讀取
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// data用來(lái)保存實(shí)際讀到的字節(jié)數(shù)
int data = 0;
while ((data = bis.read()) != -1) {
bos.write(data);
}
bis.close();
bos.close();
}
但是對(duì)于上面的輸入輸出流仍然存在著一個(gè)問(wèn)題,那就是對(duì)于Java的基本數(shù)據(jù)類型的讀取,如int/double/boolean/char類型的讀取,因此引入了DataInputStream/DataOutputStream來(lái)處理。需要注意的是使用它們時(shí),讀取與寫(xiě)出的順序必須要一致。
DataInputStream使用示例:
public static void main(String[] args) throws IOException {
String name = "zhangsan";
int age = 10;
boolean flag = true;
char sex = '男';
double money = 100.56;
DataOutputStream dos = new DataOutputStream(new FileOutputStream("e:\\b.txt"));
dos.writeUTF(name);
dos.writeInt(age);
dos.writeBoolean(flag);
dos.writeChar(sex);
dos.writeDouble(money);
dos.close();
}
DataOutputStream使用示例(讀取上面使用DataInputStream寫(xiě)入的信息):
public static void main(String[] args) throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("e:\\b.txt"));
// 讀的順序必須和寫(xiě)的順序一致
System.out.println(dis.readUTF());
System.out.println(dis.readInt());
System.out.println(dis.readBoolean());
System.out.println(dis.readChar());
System.out.println(dis.readDouble());
dis.close();
}
字符流
原本使用字節(jié)流已經(jīng)滿足了I/O的所有需求的,但是可惜的是計(jì)算機(jī)的編碼格式太多,如中國(guó)的GBK,還有ASCII/Unicode等編碼,不同的編碼之間并不兼容,如GBK是一個(gè)漢字有兩個(gè)字節(jié),而ASCII/Unicode則是一個(gè)字母一個(gè)字節(jié),如果用ANSI來(lái)解析GBK則會(huì)造成亂碼。這時(shí)字符流Reader類和Writer類就派上用場(chǎng)了,要注意的是,字符流雖然讀取的是char數(shù)組,但它們的底層還是依靠byte字節(jié)來(lái)讀取的。
由于Reader和Writer是抽象類,因此要靠子類來(lái)實(shí)現(xiàn)它們的抽象方法
Reader類的子類:
BufferedReader,
CharArrayReader,
FilterReader,
InputStreamReader,
PipedReader,
StringReader
Writer類的子類:
BufferedWriter,
CharArrayWriter,
FilterWriter,
OutputStreamWriter,
PipedWriter,
PrintWriter,
StringWriter
FilterReader/FilterWriter
FilterReader/FilterWriter對(duì)應(yīng)的是字節(jié)流中的FilterInputStream和FilterOutputStream對(duì)應(yīng),能夠直接對(duì)文件進(jìn)行操作,但是它是假定文件中的默認(rèn)編碼格式是電腦中的默認(rèn)編碼格式來(lái)讀取的,是InputStreamReader和OutputStreamWriter的子類
示例:
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("e:\\c.txt");
FileWriter fw = new FileWriter("e:\\d.txt");
char[] buff = new char[100];
int len = 0;// 實(shí)際讀到的字符個(gè)數(shù)
while ((len = fr.read(buff)) != -1) {
fw.write(buff, 0, len);
// 當(dāng)FileWriter忘記關(guān)閉,而FileWriter緩存沒(méi)寫(xiě)滿時(shí),文件將寫(xiě)入失敗,
// 這是應(yīng)該調(diào)用flush()方法,強(qiáng)制清空緩存,將信息寫(xiě)入文件中
// fw.flush();
}
fr.close();
// close()在使用的時(shí)候會(huì)強(qiáng)制調(diào)用flush(),強(qiáng)制清空緩存
fw.close();
}
BuferedReader和BufferedWriter
通過(guò)緩沖輸入/輸出提高性能。這個(gè)方法所讀取得是字符輸入/輸出流,即InputStreamReader/OutputStreamWriter流,因?yàn)镽eader類和Writer類(如FileReader和FileWriter)會(huì)頻繁的調(diào)用底層資源,因此建議使用BufferedReader對(duì)其進(jìn)行包裝,如:BufferedReader in = new BufferedReader(new FileReader("foo.in"));
另外由于每個(gè)平臺(tái)如Linux和windows的換行符符號(hào)不同的,因此BufferedWriter中有newLine()方法來(lái)實(shí)現(xiàn)各的平臺(tái)統(tǒng)一換行。
BuferedReader和BufferedWriter示例:
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("e:\\EclipseDemo1.java");
BufferedReader br = new BufferedReader(fr);
FileWriter fw = new FileWriter("e:\\aa.java");
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
// br.readLine()到達(dá)流的末尾時(shí)會(huì)返回null
while ((line = br.readLine()) != null) {
System.out.println(line);
bw.write(line);
bw.newLine();// 換行
// flush()方法同F(xiàn)ileWriter
// bw.flush();
}
br.close();
bw.close();
}
轉(zhuǎn)換流InputStreamReader/OutputStreamWriter
轉(zhuǎn)換流能夠?qū)⒆止?jié)流和字符流相互轉(zhuǎn)換,如將InputStream流轉(zhuǎn)換為InputStreamReader字符流,用OutputStreamWriter轉(zhuǎn)換為OutputStream,需要傳入InputStream/OutputStream類的參數(shù)。
另外轉(zhuǎn)換流還有一個(gè)優(yōu)勢(shì),它能夠?qū)⒆x取指定編碼格式的文件信息,而FileReader只能夠讀取默認(rèn)編碼格式的文本文件。
OutputStringWriter示例:
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("e:\\1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
BufferedWriter bw = new BufferedWriter(osw);
bw.write("你好");
bw.close();
}
InputStreamReader示例:
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("e:\\1.txt");
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
BufferedReader br = new BufferedReader(isr);
String line = null;
// br.readLine()到達(dá)流的末尾時(shí)會(huì)返回null
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
總結(jié)
對(duì)于JAVA中的I/O流就到這里結(jié)束了,I/O流中的類和方法都大同小異,相信只要掌握了本文所說(shuō)的這些類和方法,那么就能掌握絕大部分的I/O流操作了。
I/O流之中有一些共同的特性,它們可以直接在各自的抽象類中找到.
如:
- 繼承自InputStream抽象類和Reader抽象類的子類中,它們都有read()方法,而且使用這個(gè)方法讀取到文件末尾時(shí)都是返回-1。
(字符流中的BufferedReader有readLine()方法,它返回的是null值。相應(yīng)的BufferedWriter中有newLine()方法用來(lái)?yè)Q行,但沒(méi)有返回值)
最后以獲得網(wǎng)絡(luò)圖片作為JAVA中的I/O流的結(jié)束吧!
/**
*
* @param urlString 下載網(wǎng)絡(luò)資源的地址,如圖片的下載地址
* @param fileName 要把下載的資源的重命名
* @param savePath 要下載資源保存到哪里
* @throws Exception
*/
public static void download (String urlString, String fileName,
String savePath) throws IOException {
URL url = new URL(urlString);
URLConnection conn = url.openConnection();
// 網(wǎng)絡(luò)連接超時(shí)的時(shí)間限制
conn.setReadTimeout(5*1000);
conn.setConnectTimeout(5*1000);
// 獲取文件流
InputStream is = conn.getInputStream();
byte[] buff = new byte[1024];
int len = 0;
// 判斷下載保存的路徑是否存在,不存在則創(chuàng)建
File file = new File(savePath);
if(!file.exists()){
file.mkdirs();
}
// 將獲取的流寫(xiě)入到電腦磁盤(pán)中
OutputStream os = new FileOutputStream(file.getAbsolutePath() + "\\" + fileName);
while((len = is.read(buff)) != -1 ) {
os.write(buff, 0, len);
}
is.close();
os.close();
}
補(bǔ)充知識(shí)點(diǎn)
最近重新看了一下IO流的知識(shí),發(fā)現(xiàn)又有些迷糊,然后又仔細(xì)研究了一下。
關(guān)于字節(jié)流:
字節(jié)流也就是byte,按照字節(jié)讀取,也就是說(shuō)只要集成了InputStream的流對(duì)象都是字節(jié)流,都使用byte。
字節(jié)流的讀取方法一般用以下兩種:
read() :從此輸入流中讀取下一個(gè)數(shù)據(jù)字節(jié),每次會(huì)返回一個(gè)字節(jié)
這種時(shí)候,讀取和寫(xiě)入的方法如同上面的FileInputStream的操作一般,每讀取一個(gè)字節(jié)就寫(xiě)入一個(gè)字節(jié)。
第二種辦法就是使用數(shù)組緩沖區(qū)的方法read(byte[] b) ,它將會(huì)將讀取的數(shù)據(jù)保存在緩沖區(qū)中,也是像上面使用緩沖的FileInputStream的操作一般,然后使用OutputStream寫(xiě)入。
但是在實(shí)際操作中發(fā)現(xiàn)寫(xiě)入的過(guò)程中使用write(byte b[])和write(byte b[], int off, int len)(上文中的FileInputStream緩沖區(qū)寫(xiě)入)方法均可寫(xiě)入,代碼如下:
File file1 = new File("D:\\a\\1.txt");
File file2 = new File("D:\\a\\2.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
// 創(chuàng)建一個(gè)1M大小的緩沖區(qū),用來(lái)存放輸入流中的字節(jié)
byte[] buf = new byte[1024 * 1024];
// fis.read(buf) 讀入緩沖區(qū)的字節(jié)總數(shù),如果因?yàn)橐呀?jīng)到達(dá)文件末尾而沒(méi)有更多的數(shù)據(jù),則返回 -1。
while (fis.read(buf) != -1) {
fos.write(buf);
}
fis.close();
fos.close();
這讓我不禁腦洞打開(kāi),或許只使用write(byte b[])方法便能夠完成操作,而不必調(diào)用傳入多個(gè)參數(shù)的方法呢?
但實(shí)際上這種方法并不可取,為什么呢?
在完成操作之后,仔細(xì)查看1.txt和2.txt的卻別,發(fā)現(xiàn)1.txt的文件大小之后1k,但是2.txt卻有1024k,打開(kāi)2.txt之后發(fā)現(xiàn)多出來(lái)的大小全部都是空格!
通過(guò)查看源碼我們得知:
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append);
// 寫(xiě)入了整個(gè)緩沖區(qū)的大小,包括沒(méi)有內(nèi)容的部分
}
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append);
// 按照實(shí)際的數(shù)組內(nèi)容長(zhǎng)度寫(xiě)入
}
這兩種方法同樣調(diào)用了writeBytes()方法,但是write(byte b[])它會(huì)將byte[]的大小設(shè)置為需要讀取的大小,也就是說(shuō)如果我們的緩沖區(qū)設(shè)置為1024*1024也就是1024k的大小,它就讀取1024k的大小,如果設(shè)置了1024m的大小也將同樣讀取1024M的大小,即使內(nèi)容的實(shí)際長(zhǎng)度并沒(méi)有這么多。
這邊是兩者之間的區(qū)別。
關(guān)于字符流
字符流也就是char,按照字符讀取,也就是說(shuō)只要集成了Reader的流對(duì)象都是字符流,都使用char。
如果字節(jié)流的讀寫(xiě)操作一樣,他們都有read()和writer()方法,區(qū)別只是將其中的byte換成char而已。
同樣寫(xiě)入方法有這兩種:
write(char[] cbuf)
write(char[] cbuf, int off, int len)
它們出現(xiàn)的問(wèn)題與字節(jié)流是一樣的。
因此我們寫(xiě)入文件操作的時(shí)候切記要設(shè)置好寫(xiě)入的長(zhǎng)度,否則寫(xiě)入空內(nèi)容不僅浪費(fèi)效率,還會(huì)導(dǎo)致保存的文件信息錯(cuò)誤而無(wú)法打開(kāi)。
由此明白了這些事情:
字節(jié)流設(shè)置了緩沖數(shù)組:
byte[] buf = new byte[1024 * 1024];
在InputStream讀取的過(guò)程中,會(huì)將內(nèi)容保存到buf中。
字符流設(shè)置緩沖數(shù)組:
char[] buff = new char[100];
在Reader的讀取過(guò)程中會(huì)將內(nèi)容保存到buff當(dāng)中。
但是在讀取過(guò)程中我們要設(shè)置當(dāng)前讀了多少,也就是設(shè)置:
int len = 0;// 實(shí)際讀到的字符個(gè)數(shù)
(len = fr.read(buff)) != -1
然后在通過(guò)write(char[]/byte[] cbuf, int off, int len) 保存相應(yīng)長(zhǎng)度的內(nèi)容。
流轉(zhuǎn)String
對(duì)于String字符串對(duì)象而已,也是一樣,
我們發(fā)現(xiàn)String的構(gòu)造方法中有char和byte的方法,如下:
String(byte[] bytes) // 通過(guò)使用平臺(tái)的默認(rèn)字符集解碼指定的 byte 數(shù)組,構(gòu)造一個(gè)新的 String。
String(byte[] bytes, int offset, int length)
// 通過(guò)使用平臺(tái)的默認(rèn)字符集解碼指定的 byte 子數(shù)組,構(gòu)造一個(gè)新的 String。
String(char[] value) // 分配一個(gè)新的 String,使其表示字符數(shù)組參數(shù)中當(dāng)前包含的字符序列。
String(char[] value, int offset, int count)
// 分配一個(gè)新的 String,它包含取自字符數(shù)組參數(shù)一個(gè)子數(shù)組的字符。
但是與字節(jié)流和字符流一樣,都是參數(shù)少的會(huì)調(diào)用參數(shù)多的方法來(lái)創(chuàng)建String,因此在將流存為字符串的時(shí)候,也要遵守上面的規(guī)則,也就是說(shuō)要設(shè)置讀取了多長(zhǎng)的內(nèi)容。
字節(jié)流轉(zhuǎn)為String方法如下:
File file1 = new File("D:\\a\\1.txt");
FileReader fr = new FileReader(file1);
// 創(chuàng)建一個(gè)1M大小的緩沖區(qū),用來(lái)存放輸入流中的字節(jié)
char[] buf = new char[1024 * 1024];
// 用來(lái)保存實(shí)際讀到的字節(jié)數(shù)
int len = 0;
// fis.read(buf) 讀入緩沖區(qū)的字節(jié)總數(shù),如果因?yàn)橐呀?jīng)到達(dá)文件末尾而沒(méi)有更多的數(shù)據(jù),則返回 -1。
while ((len = fr.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}
fr.close();
字符流轉(zhuǎn)換方法如下:
File file1 = new File("D:\\a\\1.txt");
FileInputStream fis = new FileInputStream(file1);
// 創(chuàng)建一個(gè)1M大小的緩沖區(qū),用來(lái)存放輸入流中的字節(jié)
byte[] buf = new byte[1024 * 1024];
// 用來(lái)保存實(shí)際讀到的字節(jié)數(shù)
int len = 0;
// fis.read(buf) 讀入緩沖區(qū)的字節(jié)總數(shù),如果因?yàn)橐呀?jīng)到達(dá)文件末尾而沒(méi)有更多的數(shù)據(jù),則返回 -1。
while ((len = fis.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}
fis.close();
關(guān)于I/O流就到此結(jié)束了