Java 輸入輸出流
本小節(jié)將會(huì)介紹基本輸入輸出的 Java 標(biāo)準(zhǔn)類,通過(guò)本小節(jié)的學(xué)習(xí),你將了解到什么是輸入和輸出,什么是流;輸入輸出流的應(yīng)用場(chǎng)景,File
類的使用,什么是文件,Java 提供的輸入輸出流相關(guān) API 等內(nèi)容。
1. 什么是輸入和輸出(I / O)
1.1 基本概念
輸入/輸出這個(gè)概念,對(duì)于計(jì)算機(jī)相關(guān)專業(yè)的同學(xué)并不陌生,在計(jì)算中,輸入/輸出(Input / Output
,縮寫(xiě)為 I / O)是信息處理系統(tǒng)(例如計(jì)算機(jī)))與外界(可能是人類或其他信息處理系統(tǒng))之間的通信。輸入是系統(tǒng)接收的信號(hào)或數(shù)據(jù),輸出是從系統(tǒng)發(fā)送的信號(hào)或數(shù)據(jù)。
那么在 Java 中,什么是輸入和輸出呢?要理解這個(gè)概念,可將 Java 平臺(tái)視作一個(gè)系統(tǒng)。Java 平臺(tái)是一個(gè)孤立的系統(tǒng),系統(tǒng)之外的所有東西都是它的環(huán)境。系統(tǒng)與其環(huán)境之間的交互是一種雙向?qū)υ?。系統(tǒng)要么從其環(huán)境接收消息,要么將其消息傳遞給環(huán)境。當(dāng)系統(tǒng)接收到消息時(shí),將其稱為輸入,與之相反的是輸出。
本小節(jié)將介紹 Java 的基本輸入和輸出,包括從鍵盤(pán)讀取文本輸入,將文本輸出到屏幕以及從文件系統(tǒng)讀取/寫(xiě)入文件。
Java 提供了兩個(gè)用于 I / O 的包:較舊的java.io
包(不支持符號(hào)鏈接)和較新的java.nio
(“new io”)包,它對(duì)java.nio.file
的異常處理進(jìn)行了改進(jìn)。
1.2 簡(jiǎn)單的 Java 輸出——打印內(nèi)容到屏幕
一直以來(lái),我們都在向屏幕輸出內(nèi)容以驗(yàn)證我們編寫(xiě)的代碼邏輯。向屏幕輸出內(nèi)容非常簡(jiǎn)單,可以由以下兩種方式來(lái)完成:
// 打印 Hello World,不換行
System.out.print("Hello World");
// 打印 Hello Java,并換行
System.out.println("Hello Java");
1.3 簡(jiǎn)單的 Java 輸入——從鍵盤(pán)輸入
java.util
包下的Scanner
類可用于獲取用戶從鍵盤(pán)輸入的內(nèi)容,我們?cè)?a href="http://idcbgp.cn/wiki/javalesson/scanner.html">Java Scanner 類這一小節(jié)已經(jīng)介紹過(guò)具體使用,實(shí)例如下:
import java.util.Scanner;
/**
* @author colorful@TaleLin
*/
public class ScannerDemo {
public static void main(String[] args) {
// 創(chuàng)建掃描器對(duì)象
Scanner scanner = new Scanner(System.in);
System.out.println("請(qǐng)輸入您的姓名:");
// 可以將用戶輸入的內(nèi)容掃描為字符串
String name = scanner.nextLine();
// 打印輸出
System.out.println("你好 ".concat(name).concat(" ,歡迎來(lái)到慕課網(wǎng)!"));
// 關(guān)閉掃描器
scanner.close();
}
}
運(yùn)行結(jié)果:
請(qǐng)輸入您的姓名:
Colorful
你好 Colorful ,歡迎來(lái)到慕課網(wǎng)!
2. 什么是流(Stream)
Java 中最基本的輸入/輸入是使用流來(lái)完成的。
流是代表數(shù)據(jù)源和數(shù)據(jù)目標(biāo)的對(duì)象,怎么理解這句話呢?簡(jiǎn)單來(lái)說(shuō),可以讀取作為數(shù)據(jù)源的流,也可以寫(xiě)入作為數(shù)據(jù)目標(biāo)的流。Java中的流是長(zhǎng)度不確定的有序字節(jié)序列,它是一連串流動(dòng)的字符,是以先進(jìn)先出的方式發(fā)送信息的通道。
3. 輸入輸出流的應(yīng)用場(chǎng)景
上面我們已經(jīng)了解了輸入輸出流的基本概念,那么它具體是做什么用的呢?
在web
產(chǎn)品的開(kāi)發(fā)中,最常開(kāi)發(fā)的功能就是上傳文件到服務(wù)器了,這個(gè)文件的讀寫(xiě)過(guò)程就要用到輸入輸出流。對(duì)于計(jì)算機(jī)中文件的讀寫(xiě)、復(fù)制和刪除等操作也都要用到輸入輸出流。輸入輸出流可以說(shuō)是無(wú)處不在,下面我們將會(huì)介紹 Java 中輸入輸出流相關(guān)的 API
。
4. File 類
在 Java 中,提供了java.io.File
類對(duì)文件和目錄進(jìn)行操作。
File 意思為文件,文件在計(jì)算機(jī)中非常重要,我們編寫(xiě)的 word 文檔、PPT 演示文稿、運(yùn)行游戲的.exe
可執(zhí)行文件以及我們編寫(xiě)的 Java 源代碼等等都是文件。
4.1 實(shí)例化
要實(shí)例化File
對(duì)象,需要傳入一個(gè)文件或目錄的路徑。
File 類提供了如下 4 個(gè)構(gòu)造方法:
File(File parent, String child)
:從父抽象路徑名和子路徑名字符串創(chuàng)建新的文件實(shí)例;File(String pathName)
:通過(guò)將給定的路徑名字符串轉(zhuǎn)換為抽象路徑名,創(chuàng)建一個(gè)新的文件實(shí)例(最常用);File(String parent, String child)
:從父路徑名字符串和子路徑名字符串創(chuàng)建新的文件實(shí)例;File(URI uri)
:通過(guò)將給定的文件: URI轉(zhuǎn)換為抽象路徑名,創(chuàng)建一個(gè)新的文件實(shí)例。
以Windows
系統(tǒng)為例,在桌面下有一個(gè)imooc
目錄,該目錄下有一個(gè)Hello.java
文件和一個(gè)空的images
目錄,截圖如下:

我們可以單擊Windows
的路徑欄,來(lái)獲取imooc
目錄的絕對(duì)路徑:

有了目錄和文件以及路徑。我們分別實(shí)例化兩個(gè)File
對(duì)象,實(shí)例如下:
import java.io.File;
public class FileDemo1 {
public static void main(String[] args) {
// 傳入目錄絕對(duì)路徑
File dir = new File("C:\\Users\\Colorful\\Desktop\\imooc\\images");
// 傳入文件絕對(duì)路徑
File file = new File("C:\\Users\\Colorful\\Desktop\\imooc\\Hello.java");
// 打印兩個(gè)File對(duì)象
System.out.println(dir);
System.out.println(file);
}
}
我們可以直接打印File
對(duì)象,File
類重寫(xiě)了toString()
方法,查看 Java 源碼,toString()
方法直接返回了getPath()
實(shí)例方法,此方法返回構(gòu)造方法傳入的路徑字符串:

運(yùn)行結(jié)果:
C:\Users\Colorful\Desktop\imooc\images
C:\Users\Colorful\Desktop\imooc\Hello.java
上面代碼中,使用\\
表示Windows
下的路徑分隔符\
,Linux
和MacOS
下使用正斜杠/
作為路徑分隔符。假設(shè)是同樣的目錄結(jié)構(gòu),在MacOS
和Linux
下是這樣表示的:
File dir = new File("/Users/Colorful/Desktop/imooc/images");
因?yàn)?code>Windows平臺(tái)和其他平臺(tái)路徑分隔符不同,使用不同平臺(tái)的開(kāi)發(fā)者就難以保證路徑分隔符的統(tǒng)一。
為了保證代碼更好的兼容性,File
類下提供了一個(gè)靜態(tài)變量separator
,用于表示當(dāng)前平臺(tái)的系統(tǒng)分隔符:
// 根據(jù)當(dāng)前平臺(tái)輸出 / 獲取 \
System.out.println(File.separator);
4.2 絕對(duì)路徑和相對(duì)路徑
在實(shí)例化File
對(duì)象時(shí),既可以傳入絕對(duì)路徑,也可以傳入相對(duì)路徑。
絕對(duì)路徑是以根目錄開(kāi)頭的完整的全路徑,上面代碼實(shí)例中傳入的是絕對(duì)路徑,我們?cè)賮?lái)看看什么是相對(duì)路徑,以及如何傳入相對(duì)路徑。
相對(duì)路徑指的是當(dāng)前文件所在的路徑引起的跟其它文件(或文件夾)的路徑關(guān)系。聽(tīng)起來(lái)有點(diǎn)繞,我們舉例來(lái)說(shuō)明一下,在imooc
目錄下新建一個(gè)FileDemo2.java
文件,此時(shí)imooc
目錄的文件目錄樹(shù)結(jié)構(gòu)如下:
└── imoooc
├── FileDemo2.java
├── Hello.java
└── images
內(nèi)容如下:
import java.io.File;
import java.io.IOException;
public class FileDemo2 {
public static void main(String[] args) throws IOException {
// 傳入目錄相對(duì)路徑
File dir = new File(".\\images");
File imoocDir = new File("..\\imooc");
// 傳入文件相對(duì)路徑
File file = new File(".\\Hello.java");
}
}
上面代碼的File
構(gòu)造方法中傳入的就是相對(duì)路徑,代碼中的.
表示當(dāng)前目錄,..
表示上級(jí)目錄。
Tips: 我們?cè)趯?shí)例化 File 對(duì)象時(shí),不會(huì)產(chǎn)生對(duì)磁盤(pán)的操作,因此即使傳入的文件或目錄不存在,代碼也不會(huì)拋出異常。只有當(dāng)調(diào)用 File 對(duì)象下的一些方法時(shí),才會(huì)對(duì)磁盤(pán)進(jìn)行操作。
File 對(duì)象下有 3 個(gè)表示路徑的實(shí)例方法:
String getPath()
:將抽象路徑名轉(zhuǎn)換為路徑名字符串;String getAbsolute()
:返回此抽象路徑名的絕對(duì)路徑名字符串;String getCanonicalPath()
:返回此抽象路徑名的規(guī)范路徑名字符串。
我們可以調(diào)用這 3 個(gè)方法并打印結(jié)果,實(shí)例如下:
import java.io.File;
import java.io.IOException;
public class FileDemo2 {
public static void main(String[] args) throws IOException {
// 傳入目錄相對(duì)路徑
File imagesDir = new File(".\\images");
File imoocDir = new File("..\\imooc");
// 傳入文件相對(duì)路徑
File file = new File(".\\Hello.java");
System.out.println("-- imagesDir ---");
System.out.println(imagesDir.getPath());
System.out.println(imagesDir.getAbsolutePath());
System.out.println(imagesDir.getCanonicalPath());
System.out.println("-- imoocDir ---");
System.out.println(imoocDir.getPath());
System.out.println(imoocDir.getAbsolutePath());
System.out.println(imoocDir.getCanonicalPath());
System.out.println("-- file ---");
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
運(yùn)行結(jié)果:
-- imagesDir ---
.\images
C:\Users\Colorful\Desktop\imooc\.\images
C:\Users\Colorful\Desktop\imooc\images
-- imoocDir ---
..\imooc
C:\Users\Colorful\Desktop\imooc\..\imooc
C:\Users\Colorful\Desktop\imooc
-- file ---
.\Hello.java
C:\Users\Colorful\Desktop\imooc\.\Hello.java
C:\Users\Colorful\Desktop\imooc\Hello.java
通過(guò)運(yùn)行結(jié)果可以看出,規(guī)范路徑名就是把.
和..
轉(zhuǎn)換為標(biāo)準(zhǔn)的絕對(duì)路徑。
4.3 判斷對(duì)象是文件還是目錄
我們可以通過(guò)如下兩個(gè)方法判斷 File 對(duì)象是文件還是目錄:
boolean isFile()
:測(cè)試此抽象路徑名表示的文件是否為普通文件;boolean isDirectory()
:測(cè)試此抽象路徑名表示的文件是否為目錄。
實(shí)例如下:
import java.io.File;
public class FileDemo3 {
public static void printResult(File file) {
// 調(diào)用isFile()方法并接收布爾類型結(jié)果
boolean isFile = file.isFile();
String result1 = isFile ? "是已存在文件" : "不是已存在文件";
// 掉用isDirectory()方法并接收布爾類型而己過(guò)
boolean directory = file.isDirectory();
String result2 = directory ? "是已存在目錄" : "不是已存在目錄";
// 打印該file對(duì)象是否是已存在文件/目錄的字符串結(jié)果
System.out.print(file);
System.out.print('\t' + result1 + '\t');
System.out.println(result2);
}
public static void main(String[] args) {
// 傳入目錄絕對(duì)路徑
File dir = new File("C:\\Users\\Colorful\\Desktop\\imooc\\images");
// 傳入文件絕對(duì)路徑
File file = new File("C:\\Users\\Colorful\\Desktop\\imooc\\test.java");
FileDemo3.printResult(dir);
FileDemo3.printResult(file);
}
}
運(yùn)行結(jié)果:
C:\Users\Colorful\Desktop\imooc\images 不是已存在文件 是已存在目錄
C:\Users\Colorful\Desktop\imooc\test.java 不是已存在文件 不是已存在目錄
代碼中我們封裝了一個(gè)靜態(tài)方法printResult()
,此方法打印 File 對(duì)象是否是文件/目錄。值得注意的是,我們的磁盤(pán)中不存在C:\Users\Colorful\Desktop\imooc\test.java
,因此無(wú)論調(diào)用isFile()
方法還是isDirectory()
方法,其返回結(jié)果都為false
。
4.4 創(chuàng)建和刪除目錄
4.4.1 創(chuàng)建目錄
對(duì)于一個(gè)不存在的目錄,我們可以使用boolean mkdir()
方法創(chuàng)建一個(gè)目錄。例如,我們想在imooc
目錄下創(chuàng)建一個(gè)codes
目錄,就可以使用該方法編寫(xiě)一段創(chuàng)建目錄的代碼。
實(shí)例如下:
import java.io.File;
public class FileDemo4 {
public static void main(String[] args) {
// 傳入目錄絕對(duì)路徑
File dir = new File("C:\\Users\\Colorful\\Desktop\\imooc\\codes");
if (!dir.exists()) {
// 調(diào)用 mkdir() 方法
boolean result = dir.mkdir();
if (result) {
System.out.println("目錄創(chuàng)建成功");
}
}
}
}
代碼中我們調(diào)用了File
對(duì)象的boolean exists()
方法,此方法用于測(cè)試由此抽象路徑名表示的文件或目錄是否存在。當(dāng)不存在時(shí),我們才去創(chuàng)建目錄。
運(yùn)行代碼前,imooc
文件目錄樹(shù)結(jié)構(gòu)如下:
└── imoooc
├── FileDemo2.java
├── Hello.java
└── images
運(yùn)行結(jié)果:
目錄創(chuàng)建成功
運(yùn)行代碼后,imooc
目錄下多了一個(gè)codes
目錄,樹(shù)結(jié)構(gòu)如下:
└── imoooc
├── FileDemo2.java
├── Hello.java
├── images
└── codes
另外,F(xiàn)ile 類也提供了一個(gè)boolean mkdirs()
方法,用來(lái)創(chuàng)建由這個(gè)抽象路徑名命名的目錄,包括任何必要但不存在的父目錄。實(shí)際上是在遞歸執(zhí)行mkdir()
方法。
4.4.2 刪除目錄
如果我們想要?jiǎng)h除剛剛創(chuàng)建的codes
目錄,可以調(diào)用boolean delete()
方法,實(shí)例如下:
import java.io.File;
public class FileDemo5 {
public static void main(String[] args) {
// 傳入目錄絕對(duì)路徑
File dir = new File("C:\\Users\\Colorful\\Desktop\\imooc\\codes");
if (dir.exists()) {
// 調(diào)用 delete() 方法
boolean deleted = dir.delete();
if (deleted) {
System.out.println("刪除目錄成功");
}
}
}
}
運(yùn)行代碼前,imooc
文件目錄樹(shù)結(jié)構(gòu)如下:
└── imoooc
├── FileDemo2.java
├── Hello.java
├── images
└── codes
運(yùn)行結(jié)果:
刪除目錄成功
運(yùn)行代碼后,樹(shù)結(jié)構(gòu)如下:
└── imoooc
├── FileDemo2.java
├── Hello.java
└── images
4.5 創(chuàng)建和刪除文件
對(duì)于文件類型的File
對(duì)象,可以通過(guò)boolean createNewFile()
方法創(chuàng)建一個(gè)新文件,使用boolean delete()
方法刪除文件。其調(diào)用方法和創(chuàng)建/刪除目錄相同,此處不再贅述。
關(guān)于更多File
對(duì)象的操作,可翻閱官方文檔。
5. InputStream 抽象類
5.1 概述
java.io.InputStream
抽象類是 Java 提供的最基本的輸入流,它是所有輸入流的父類。其最常用的抽象方法int read()
簽名如下:
public abstract int read() throws IOException;
這個(gè)方法用于讀取輸入流的下一個(gè)字節(jié),返回的int
如果為-1
,則表示已經(jīng)讀取到文件末尾。
InputStream
與其子類的 UML 圖如下所示:

5.2 FileInputStream 實(shí)現(xiàn)類
我們將以最常用的FileInputStream
實(shí)現(xiàn)類為例進(jìn)行學(xué)習(xí)。其他實(shí)現(xiàn)類大同小異,如有需要可翻閱官方文檔。
FileInputStream
就是從文件流中讀取數(shù)據(jù),我們?cè)?code>imooc目錄下新建一個(gè)文本文檔Hello.txt
,并輸入如下內(nèi)容:

讀取Hello.txt
文件中數(shù)據(jù)的實(shí)例代碼如下:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException {
// 實(shí)例化文件流
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Colorful\\Desktop\\imooc\\Hello.txt");
for (;;) {
int n = fileInputStream.read();
if (n == -1) {
// read() 方法返回-1 則跳出循環(huán)
break;
}
// 將n強(qiáng)制轉(zhuǎn)換為 char 類型
System.out.print((char) n);
}
// 關(guān)閉文件流
fileInputStream.close();
}
}
運(yùn)行結(jié)果:
Hello imooc!
如果我們打開(kāi)了一個(gè)文件并進(jìn)行操作,不要忘記使用close()
方法來(lái)及時(shí)關(guān)閉。這樣可以讓系統(tǒng)釋放資源。
6. OutputStream 抽象類
6.1 概述
OutPutStream
抽象類是與InputStream
對(duì)應(yīng)的最基本的輸出流,它是所有輸出流的父類。其最常用的抽象方法void write(int b)
簽名如下:
public abstract void write(int b) throws IOException;
這個(gè)方法用于寫(xiě)入一個(gè)字節(jié)到輸出流。
OutputStream
與其子類的 UML 圖如下所示:

6.2 FileOutputStream 實(shí)現(xiàn)類
我們同樣以最常用的FileOutputStream
實(shí)現(xiàn)類為例進(jìn)行學(xué)習(xí)。其他實(shí)現(xiàn)類大同小異,如有需要可翻閱官方文檔。
FileOutputStream
就是向文件流中寫(xiě)入數(shù)據(jù),下面我們向imooc
目錄下的文本文檔Hello.txt
輸入一段字符串HHH
。完整實(shí)例如下:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo1 {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Colorful\\Desktop\\imooc\\Hello.txt");
// 寫(xiě)入 3 個(gè)H字符
fileOutputStream.write(72);
fileOutputStream.write(72);
fileOutputStream.write(72);
fileOutputStream.close();
}
}
運(yùn)行代碼后,Hello.txt
后面成功寫(xiě)入了 3 個(gè)字符H
。
7. 小結(jié)
通過(guò)本小節(jié)的學(xué)習(xí),我們知道了什么是輸入輸出流的概念,輸入輸出流經(jīng)常用于上傳文件到服務(wù)器的場(chǎng)景。想要通過(guò) Java 操作文件和目錄,要學(xué)會(huì)使用java.io.File
類,InputStream
和OutputStream
分別是所有輸入流和所有輸出流的父類,FileInputStream
實(shí)現(xiàn)了文件流的輸入,FileOutputStream
實(shí)現(xiàn)了文件流的輸出。還有很多其它實(shí)現(xiàn)類我們沒(méi)有介紹到,但使用方法大同小異,希望同學(xué)可以在用到時(shí)自行查閱文檔來(lái)學(xué)習(xí)。