一文徹底弄懂Java中I/O操作
1. 引言
??I/O 操作主要是指 使用 Java 程序完成輸入(Input)、輸出(Output) 操作。輸入是指將文件內(nèi)容以數(shù)據(jù)流的形式讀入內(nèi)存,輸出是指通過 Java 程序?qū)?nèi)容中的數(shù)據(jù)寫入文件,輸入輸出操作在實際開發(fā)中比較廣泛。
- IO:輸入/輸出(Input/Output)
- 流:是一種抽象概念,是對數(shù)據(jù)傳輸?shù)目偡Q.也就是說數(shù)據(jù)在設(shè)備間的傳輸稱為流,流的本質(zhì)是數(shù)據(jù)傳輸
- IO流就是用來處理設(shè)備間數(shù)據(jù)傳輸問題的.常見的應(yīng)用: 文件復(fù)制; 文件上傳; 文件下載
IO流的分類:
(1)按照數(shù)據(jù)的流向
- 輸入流:讀數(shù)據(jù)
- 輸出流:寫數(shù)據(jù)
(2)按照數(shù)據(jù)類型來分:
- 字節(jié)流
- 字節(jié)輸入流
- 字節(jié)輸出流
- 字符流
- 字符輸入流
- 字符輸出流
IO流的使用場景
- 如果操作的是純文本文件,優(yōu)先使用字符流
- 如果操作的是圖片、視頻、音頻等二進(jìn)制文件,優(yōu)先使用字節(jié)流
- 如果不確定文件類型,優(yōu)先使用字節(jié)流,字節(jié)流是萬能的流
2. File 類
java.io 包中的 File 類 是唯一一個可以代表磁盤文件的對象,它定義了一些用于操作文件的方法。通過調(diào)用 File 類 提供的各種方法,可以創(chuàng)建、刪除或者重命名文件,判斷硬盤上某個文件是否存在,查詢文件最后修改時間,等等。本節(jié)將針對File 類 進(jìn)行詳細(xì)講解。創(chuàng)建File對象
2.1 創(chuàng)建 File 對象
File 類 提供了多個構(gòu)造方法用于創(chuàng)建 File 對象。File 類 的常用構(gòu)造方法如下所示:
| 方法聲明 | 功能描述 |
| FILE(String pathname) | 通過指定的 一個字符串類型的文件路徑 創(chuàng)建一個 FILE 對象 |
| FILE(String parent, String child) | 根據(jù)指定的 一個字符串類型的父路徑和一個字符串類型的子路徑(包括文件名稱)創(chuàng)建一個 FILE 對象 |
| FILE (FILE parent, String child) | 根據(jù)指定的 一個FILE 類的父路徑和一個字符串類型的子路徑(包括文件名稱)創(chuàng)建一個 FILE 對象 |
所有的構(gòu)造方法都需要傳入文件路徑,那么我們應(yīng)該如何去用呢?
- 如果程序只處理一個目錄和文件,并且知道該目錄或文件的路徑,就建議使用 構(gòu)造 1
- 如果程序處理的是一個公共目錄中的若干子目錄或文件,就建議使用 構(gòu)造 2 和 構(gòu)造 3
案例:
public static void main(String[] args) {
// 方法一:通過絕對路徑創(chuàng)建
File f1 = new File("D:\\file\\a.txt");
// 方法二:通過相對路徑創(chuàng)建
File f2 = new File("src\\Hello.txt");
}注:目錄符號(\) 用 \\ 表示,因為 \ 在Java中是特殊字符,具有轉(zhuǎn)義作用,因此用 \\ 表示,此外我們也可以用 / 來作目錄符號。
2.2 File 類的常用方法
| 方法聲明 | 功能描述 |
| boolean exists() | 判定 File 對象對應(yīng)的文件或目錄是否存在 |
| boolean delete() | 刪除 File 對象對應(yīng)的文件或目錄 |
| boolean createNewFile() | 當(dāng) File 對象對應(yīng)文件不存在時則創(chuàng)建新文件,并且將新建的 File 對象指向新文件 |
| String getName() | 返回 File 對象 表示的文件或目錄的名稱 |
| String getPath() | 返回 File 對象 表示的文件或目錄的路徑 |
| String getAbsolutePath() | 返回 File 對象 表示的文件或目錄的相對路徑
|
| String getParentFile() | 返回 File 對象 對應(yīng)目錄的父目錄(注:返回的目錄不包含最后一級子目錄) |
| boolean canRead() | 判定 File 對象對應(yīng)的文件或目錄是否可讀 |
| boolean canWrite() | 判定 File 對象對應(yīng)的文件或目錄是否可寫 |
| boolean isFile() | 判斷 File 對象對應(yīng)的是否是文件 |
| boolean isDirectory() | 判斷 File 對象對應(yīng)的是否是目錄 |
| boolean isAbsolute() | 判斷 File 對象對應(yīng)的是否是絕對路徑 |
| long lastModified() | 返回 1970 年 1 月 1 日 0 時 0 分 0 秒 到文件最后修改時間的 毫秒值 |
| long length() | 返回文件內(nèi)容的長度(注:單位為 字節(jié)) |
| String[] list() | 遞歸列出指定目錄的全部內(nèi)容(包括子目錄和文件),只列出名稱 |
| File[] listFiles() | 返回一個包含 File 對象所有子文件和子目錄的 File 數(shù)組 |
演示:
public static void main(String[] args) {
File file = new File("src/test.txt"); // / 也可以作 目錄符號
System.out.println("文件是否存在:" + file.exists());
System.out.println("文件名:" + file.getName());
// ... 具體大家自己可以實踐
}- 補(bǔ)充學(xué)習(xí): createTempFile() 方法 和 deleteOnExit() 方法
在一些特定情況下,程序需要讀寫一些臨時文件,為此,F(xiàn)ile類提供了createTempFile() 方法 和 deleteOnExit() 方法,用于操作臨時文件。createTempFile() 方法用于創(chuàng)建一個臨時文件,deleteOnExit() 方法在Java虛擬機(jī)退出時自動刪除臨時文件。
下面通過一個案例演示這兩個方法的使用:
public static void main(String[] args) throws IOException {
// 提供臨時文件的前綴和擴(kuò)展名
File file = File.createTempFile("itcast-", ".txt");
file.deleteOnExit(); // java 虛擬機(jī)退出時 自動刪除文件 file
System.out.println("file 是否為文件:" + file.isFile());
System.out.println("file 的相對路徑:" + file.getPath());
}2.3 遍歷目錄下的文件
File 類中提供了 list()方法,可以獲取目錄下所有文件和目錄的名稱。獲取目錄下所有文件和目錄名稱后,可以通過這些名稱遍歷目錄下的文件,按照調(diào)用方法的不同,對目錄下的文件遍歷可分為以下3種方式。
- 遍歷指定目錄下的所有文件
- 遍歷指定目錄下指定擴(kuò)展名的文件
- 遍歷包括子目錄中的文件在內(nèi)的所有文件
下面分別對這3種遍歷方式進(jìn)行詳細(xì)講解。
(1)遍歷指定目錄下的所有文件
File 類的 list()方法可以遍歷指定目錄下的所有文件。下面通過一個案例演示如何使用 list()方法遍歷目錄下的所有文件,如下:
public static void main(String[] args) {
File file = new File("src/IO");
if(file.isDirectory()) {
String[] names = file.list(); // 獲取目錄下所有文件的文件名
for(String name:names) {
System.out.println(name); //輸出文件名
}
}
}(2)遍歷指定目錄下指定擴(kuò)展名的文件
?? 上述代碼實現(xiàn)了遍歷一個目錄下所有文件的功能,然而有時程序只需要獲取指定類型的文件,如獲取指定目錄下所有擴(kuò)展名為“.java”的文件。
針對這種需求,F(xiàn)ile類提供了一個重載的 list()方法,該方法接收一個 FilenameFilter 類型的參數(shù)。FilenameFilter 是一個接口,被稱作文件過濾器,其中定義了抽象方法accept()用于依次對指定File的所有子目錄或文件進(jìn)行迭代。在調(diào)用list()方法時,需要實現(xiàn) FilenameFilter,并在accept()方法中進(jìn)行篩選,從而獲得指定類型的文件。
下面通過一個案例演示如何遍歷指定目錄下所有擴(kuò)展名為“.java”的文件,如下:
public static void main(String[] args) {
File file = new File("src/IO");
// 創(chuàng)建文件過濾器對象
FilenameFilter filter = new FilenameFilter() {
// 實現(xiàn) accept 方法
@Override
public boolean accept(File dir, String name) {
File currFile = new File(dir, name);
// 如果文件以 .java 結(jié)尾返回true
if(currFile.isFile() && name.endsWith(".java")){
return true;
}
else return false;
}
};
if(file.exists()) {
String[] lists = file.list(); // 獲取目錄下所有文件的文件名
for(String name:lists) {
System.out.println(name); //輸出文件名
}
}
}(3)遍歷包括子目錄下的文件在內(nèi)的所有文件
?? 前面的兩個例子演示的都是遍歷當(dāng)前目錄下的文件。有時候在一個目錄下,除了文件,還有子目錄,如果想獲取所有子目錄下的文件,list()方法顯然不能滿足要求,這時可以使用File 類提供的另一個方法—— listFiles()。
?? 該方法返回一個File對象數(shù)組,當(dāng)對數(shù)組中的元素進(jìn)行遍歷時,如果元素中還有子目錄需要遍歷,則可以遞歸遍歷子目錄。下面通過一個案例演示包括子目錄文件的所有文件的遍歷,如下:
public static void main(String[] args) {
// 創(chuàng)建一個代表目錄的 File 對象
File file = new File("src");
fileDir(file);
}
public static void fileDir(File dir)
{
File[] files = dir.listFiles(); // 獲得表示目錄下的所有文件數(shù)組
for(File file :files) // 遍歷所有子目錄和文件
{
if(file.isDirectory()){
fileDir(file); // 如果是目錄則遞歸調(diào)用
}
System.out.println(file.getAbsolutePath()); // 獲取文件的絕對路徑
}
}2.4 刪除文件及目錄
??在操作文件時,可能會遇到需要刪除一個目錄下某個文件或刪除整個目錄的操作,這時就可以調(diào)用File 類中的 delete() 方法。
public static void main(String[] args) {
File file = new File("src/IO");
if(file.exists()){
System.out.println(file.delete());
}
}
// 輸出
false為啥會輸出 false 呢?
??因為文件刪除失敗了,F(xiàn)ile 類中的 delete() 方法只能刪除一個指定的文件,假如 File 對象代表一個目錄,而且這個目錄下包含子目錄或文件,則 File 類中的 delete() 方法 時不允許刪除整個目錄的。 此時就需要采用遞歸的方法來全部刪除
public static void main(String[] args) {
File file = new File("src/IO");
deleteDir(file);
System.out.println("刪除成功??!");
}
public static void deleteDir(File dir)
{
if(dir.exists())
{
File[] files = dir.listFiles(); // 獲得表示目錄下的所有文件數(shù)組
for(File file :files) // 遍歷所有子目錄和文件
{
if(file.isDirectory()){
deleteDir(file); // 如果是目錄則遞歸調(diào)用
}
else file.delete();// 如果是文件直接刪除
}
// 刪除整個目錄下的所有文件之后,就刪除這個目錄
dir.delete();
}
}注意:
- 刪除目錄是從 Java 虛擬機(jī)直接刪除而不放入到回收站,文件一旦被刪除就無法恢復(fù),因此在進(jìn)行文件刪除操作的時候需要格外小心!??
3. 字節(jié)流
3.1 基本概念
?? 在程序的開發(fā)中,經(jīng)常需要處理設(shè)備之間的數(shù)據(jù)傳輸,而在計算機(jī)中,無論是文本,圖片、音頻還是視頻,所有文件都是以二進(jìn)制(字節(jié))形式存在的。對于字節(jié)的輸入輸出,I/0 系統(tǒng)提供了一系列流,統(tǒng)稱為字節(jié)流。字節(jié)流是程序中最常用的流,根據(jù)數(shù)據(jù)的傳輸方向可將其分為字節(jié)輸入流和字節(jié)輸出流。
?? JDK 提供了兩個抽象類—— InputStream 和 OutputStream ,它們是字節(jié)流的頂級父類,所有的字節(jié)輸入流都繼承 InputStream ,所有的字節(jié)輸出流都繼承 OutputStream 。
字節(jié)流抽象基類
- InputStream:這個抽象類是表示字節(jié)輸入流的所有類的超類
- OutputStream:這個抽象類是表示字節(jié)輸出流的所有類的超類
- 子類名特點:子類名稱都是以其父類名作為子類名的后綴
??注: I/O 流的輸入輸出是相對于 程序而言的
- InputStream 類常用方法
| 方法聲明 | 功能描述 |
| int read() | 從輸入流讀取一字節(jié)(8位), 把它轉(zhuǎn)化位 0 - 255 的整數(shù),并返回這個整數(shù) |
| int read (byte[ ] b) | 從輸入流讀取若干字節(jié),把它們保存到參數(shù) b 指定的字節(jié)數(shù)組中,返回的整數(shù)表示讀取的字節(jié)數(shù) |
| int read (byte[ ] b, int off, int len) | 從輸入流讀取若干字節(jié),把它們保存到參數(shù) b 指定的字節(jié)數(shù)組中,off指定字節(jié)數(shù)組保存數(shù)據(jù)的起始索引,len 表示讀取的字節(jié)數(shù) |
| void close() | 關(guān)閉輸入流并且釋放與其相關(guān)的所有系統(tǒng)資源 |
上表中的3個 read() 方法都是用來讀數(shù)據(jù)的。其中:
- 第一個 read() 方法是從輸入流中逐個讀入字節(jié);
- 而第二個和第三個read()方法則可以將若干字節(jié)以字節(jié)數(shù)組的形式一次性讀入,從而提高讀數(shù)據(jù)的效率。在進(jìn)行 I/O 操作時,當(dāng)前 I/O 流會占用一定的內(nèi)存,由于系統(tǒng)資源非常寶貴,因此,在I/0操作結(jié)束后,應(yīng)該調(diào)用close()方法關(guān)閉 I/O 流,從而釋放當(dāng)前 I/O 流 所占的系統(tǒng)資源。
- OutputStream 類常用方法
| 方法聲明 | 功能描述 |
| void write(int b) | 將指定的字節(jié)寫入此文件輸出流,一次寫一個字節(jié)數(shù)據(jù) |
| void write(byte[] b) | 將參數(shù) b指定的字節(jié)數(shù)組的所有字節(jié)寫入到此文件輸出流,一次寫一個字節(jié)數(shù)組數(shù)據(jù) |
| void write(byte[] b, int off, int len) | 將指定 byte 數(shù)組從偏移量off(起始索引)開始的 len字節(jié)寫入此文件輸出流 |
| void flush() | 刷新輸出流并且強(qiáng)制寫出所有緩沖的輸出字節(jié) |
| void close() | 關(guān)閉輸出流并且釋放與其關(guān)聯(lián)的所有系統(tǒng)資源 |
??上表前3個是重載的write()方法,都用于向輸出流寫入字節(jié)。
- 其中,第一個write()方法逐個寫入字節(jié);
- 后兩個write()方法將若干字節(jié)以字節(jié)數(shù)組的形式一次性寫人,從而提高寫數(shù)據(jù)的效率。
- flush()方法用來將當(dāng)前輸出流緩沖區(qū)(通常是字節(jié)數(shù)組)中的數(shù)據(jù)強(qiáng)制寫入目標(biāo)設(shè)備,此過程稱為刷新。
- close()方法用來關(guān)閉1/0流并釋放與當(dāng)前1/0流相關(guān)的系統(tǒng)資源。
InputStream 和 OutputStream 這兩個類雖然提供了一系列和讀寫數(shù)據(jù)有關(guān)的方法,但是這兩個類是抽象類,不能被實例化,因此,針對不同的功能, InputStream 類 和 OutputStream 類提供了不同的子類,形成了體系結(jié)構(gòu),如下圖:
InputStream 體系結(jié)構(gòu)圖:

OutputStream 體系結(jié)構(gòu)圖:

3.2 字節(jié)流讀文件
- ??InputStream 就是JDK提供的基本輸入流,它是所有輸入流的父類,FileInputStream 是InputStream 的子類,它是操作文件的字節(jié)輸入流,專門用于讀取文件中的數(shù)據(jù)。因為從文件讀取數(shù)據(jù)是重復(fù)的操作,所以需要通過循環(huán)語句實現(xiàn)數(shù)據(jù)的持續(xù)讀取。
下面通過一個案例實現(xiàn)字節(jié)流對文件數(shù)據(jù)的讀取。在實現(xiàn)案例之前,先做以下操作:
- 首先在 Java項目的根目錄下創(chuàng)建文本文件test.txt
- 在文件中輸入內(nèi)容“itcast” 并保存
- 然后使用字節(jié)輸入流對象讀取 test.txt文本文件
案例代碼:
public static void main(String[] args) throws IOException {
// 創(chuàng)建一個文件字節(jié)輸入流,并且指定源文件名稱
FileInputStream in = new FileInputStream("src/IO/test.txt");
int b = 0; // 定義 int 類型的變量 b,用于 記住每次讀取的 1 字節(jié)
while(true) {
b = in.read(); // 變量 b 記住讀取的每一字節(jié)
if(b == -1){ // 如果讀取的字節(jié) 位 -1,則跳出循環(huán)
break;
}
System.out.println(b + " "); // 否則輸出b
}
in.close();
}
// 輸出
105 116 99 97 115 116由于計算機(jī)中的數(shù)據(jù)都是以字節(jié)的形式存在的。在test.txt文件中,字符i、 t、c、a、s、t 各占一字節(jié),所以最終結(jié)果顯示的就是文件test.txt中的6字節(jié)對應(yīng)的十進(jìn)制數(shù)(即這6個字母的ASCII碼值)。
?? 注意:
- 有時,在文件讀取的過程中可能會發(fā)生錯誤。例如,由于文件不存在而導(dǎo)致無法讀取。
- 或者用戶沒有讀取權(quán)限等等。這些錯誤都由Java虛擬機(jī)自動封裝成 IOException 異常并拋出。例如,當(dāng)讀取一個不存在的文件時,控制臺會報告異常信息,
當(dāng)讀取一個不存在的文件時,程序就會有一個潛在的問題。如果文件讀取過程中發(fā)生了 I/O 錯誤,InputStream 就無法正常關(guān)閉,系統(tǒng)資源也無法及時釋放,這樣會造成系統(tǒng)資源浪費(fèi)。
對此,可以使用 try…· finally 語句保證 InputStream 在任何情況下都能夠正確關(guān)閉。修改上述代碼,將讀取文件的代碼放入try語句塊中,將關(guān)閉輸入流的代碼放入finaly語句塊中,具體代碼如下:
public static void main(String[] args) throws Exception {
InputStream input = null;
try {
// 創(chuàng)建一個文件字節(jié)輸入流
FileInputStream in = new FileInputStream("src/IO/test.txt");
int b = 0; // 定義 int 類型的變量 b,用于 記住每次讀取的 1 字節(jié)
while (true) {
b = in.read(); // 變量 b 記住讀取的每一字節(jié)
if (b == -1) { // 如果讀取的字節(jié) 位 -1,則跳出循環(huán)
break;
}
System.out.print(b + " "); // 否則輸出b
}
} finally {
if (input != null) {
input.close();
}
}
}3.3 字節(jié)流寫文件
OutputStream 是JDK提供的基本輸出流,與InputStream類似.
- OutputStream是所有輸出流的父類。
- OutputStream 是一個抽象類,如果使用此類,則必須先通過子類實例化對象。
- OutputStream類有多個子類,其中FileOutputStream子類是操作文件的字節(jié)輸出流,專門用于把數(shù)據(jù)寫入文件。
案例演示:
public static void main(String[] args) throws Exception {
OutputStream out = new FileOutputStream("src/IO/example.txt");
String str = "Island1314";
byte[] b = str.getBytes();
for(int i = 0; i < b.length; i++){
out.write(b[i]);
}
out.close();
}由上可知,使用 FileOutputStream 寫數(shù)據(jù)時,程序自動創(chuàng)建了文件 example.txt,并將數(shù)據(jù)寫入example.txt 文件。需要注意的是,如果通過 FileOutputStream 向一個已經(jīng)存在的文件中寫入數(shù)據(jù),那么該文件中的數(shù)據(jù)會被覆蓋。
若希望在已存在的文件內(nèi)容之后追加新內(nèi)容,我們應(yīng)該怎么做:
- 可使用 FileOutputStream 的構(gòu)造函數(shù) public FileOutputStream(String name,boolean append)
- 創(chuàng)建文件輸出流以指定的名稱寫入文件,并把a(bǔ)ppend參數(shù)的值設(shè)置為true。如果第二個參數(shù)為true ,則字節(jié)將寫入文件的末尾而不是開頭
public static void main(String[] args) throws Exception {
OutputStream out = new FileOutputStream("src/IO/example.txt",true);
String str = "\r\n201314";
byte[] b = str.getBytes();
for(int i = 0; i < b.length; i++){
out.write(b[i]);
}
out.close();
}
// 在 example.txt 查看
Island1314
201314
// 解釋:程序通過字節(jié)輸出流對象out向文件example.txt寫入后,并沒有將文件原來的數(shù)據(jù)清空,而是將新寫入的數(shù)據(jù)追加到了文件的末尾。上面的 \r \n 又是什么意思呢 》 解釋如下:
對于字節(jié)流寫數(shù)據(jù),應(yīng)該如何實現(xiàn)換行
- windows:\r\n
- linux:\n
- mac:\r
需要注意的是:I/O流 在進(jìn)行數(shù)據(jù)讀寫操作時會出現(xiàn)異常。為了保持代碼的簡潔,在InputStream 讀文件和OutputStream寫文件的程序中都使用了throws關(guān)鍵字將異常拋出。然而一旦遇到 I/O異常,I/O流 的 close()方法 將無法得到執(zhí)行,I/O流 對象占用的系統(tǒng)資源將得不到釋放。
因此,為了保證I/O流 的 close()方法 必須執(zhí)行,通常將關(guān)閉 I/O流 的操作寫在 finally代碼塊中。
3.4 字節(jié)流復(fù)制文件
在應(yīng)用程序中,I/O 流通常都是成對出現(xiàn)的,即輸入流和輸出流一起使用。例如:文件的復(fù)制就需要通過輸入流讀取一個文件中的數(shù)據(jù),再通過輸出流將數(shù)據(jù)寫入另一個文件。
下面通過一個案例演示文件內(nèi)容的復(fù)制:
- 首先在 src 項目的根目錄下創(chuàng)建 source目錄和 target 目錄,
- 然后在 source 目錄中存放 a.png文件,
- 最后將 source目錄下的 a.png 復(fù)制到 target 目錄下并重新命名為 b.png。
public static void main(String[] args) throws Exception{
// 創(chuàng)建一個文件輸入流,用于讀取 sorce 目錄的 a.png 文件
InputStream in = new FileInputStream("src/source/a.png");
// 創(chuàng)建一個文件輸出流,用于將讀取數(shù)據(jù)寫入到 target 目錄的 b.png 文件
OutputStream out = new FileOutputStream("src/target/b.png");
int len; //用于記住每次讀取的 1 字節(jié)
// 獲取復(fù)制文件前的系統(tǒng)時間
long begintime = System.currentTimeMillis();
while ((len = in.read())!= -1){ // 讀取 1 字節(jié)并且判斷是否讀到文件末尾
out.write(len); // 將讀取的 1 字節(jié)寫入文件
}
// 獲取文件復(fù)制結(jié)束后的時間
long endtime = System.currentTimeMillis();
System.out.println("復(fù)制文件所消耗時間:" + (endtime - begintime) + "ms");
in.close();
out.close();
}
// 輸出:
復(fù)制文件所消耗時間:6038ms上述代碼實現(xiàn)了文件的復(fù)制:
- 通過while循環(huán)將a.png的所有字節(jié)逐個進(jìn)行復(fù)制。
- 每循環(huán)一次,就通過調(diào)用FileInputStream的read()方法讀取一字節(jié),
- 并通過調(diào)用FileOutputStream 的write()方法將該字節(jié)寫入指定文件,直到 len 的值為 -1,表示讀到了文件末尾,結(jié)束循環(huán),完成文件的復(fù)制。
- 程序運(yùn)行結(jié)束后,會在命令行窗口打印復(fù)制文件所消耗的時間。
- 由上可知,程序復(fù)制文件共消耗了6038ms。在復(fù)制文件時,由于計算機(jī)性能等各方面原因,會導(dǎo)致復(fù)制文件所消耗的時間不確定,因此每次運(yùn)行程序的結(jié)果未必相同。
在程序運(yùn)行結(jié)束后,打開target目錄,發(fā)現(xiàn)source目錄中的 a.png 文件被成功復(fù)制到 target目錄中
注意事項:
上述實現(xiàn)的文件復(fù)制過程是逐字節(jié)讀寫,需要頻繁地操作文件,效率非常低
打個比方:
- 從北京運(yùn)送烤鴨到上海,如果有一萬只烤鴨,每次運(yùn)送一只,就必須運(yùn)輸一萬次,這樣的效率顯然非常低。為了減少運(yùn)輸次數(shù),可以先把一批烤鴨裝在車廂中,這樣就可以成批地運(yùn)送烤鴨,這時的車廂就相當(dāng)于一個緩沖區(qū)
因此在通過流的方式復(fù)制文件時,為了提高效率,也可以定義一個字節(jié)數(shù)組作為緩沖區(qū)。
- 在復(fù)制文件時,可以一次性讀取多個字節(jié)的數(shù)據(jù),并保存在字節(jié)數(shù)組中,然后將字節(jié)數(shù)組中的數(shù)據(jù)一次性寫入文件。
- 程序中的緩沖區(qū)就是一塊內(nèi)存,它主要用于暫時存放輸入輸出的數(shù)據(jù),由于使用緩沖區(qū)減少了對文件的操作次數(shù),所以可以提高數(shù)據(jù)的讀寫效率。
利用緩沖區(qū)復(fù)制文件,修改代碼如下:
public static void main(String[] args) throws Exception{
// 創(chuàng)建一個文件輸入流,用于讀取 sorce 目錄的 a.png 文件
InputStream in = new FileInputStream("src/source/a.png");
// 創(chuàng)建一個文件輸出流,用于將讀取數(shù)據(jù)寫入到 target 目錄的 b.png 文件
OutputStream out = new FileOutputStream("src/target/b.png");
// 以下是用 緩沖區(qū) 讀寫文件
byte[] buff = new byte[1024]; // 定義一個字節(jié)數(shù)組作緩沖區(qū)
int len; //用于記住每次讀取的 1 字節(jié)
// 獲取復(fù)制文件前的系統(tǒng)時間
long begintime = System.currentTimeMillis();
while ((len = in.read(buff))!= -1){ // 讀取 1 字節(jié)并且判斷是否讀到文件末尾
out.write(buff, 0, len); // 將讀取的 1 字節(jié)寫入文件
}
// 獲取文件復(fù)制結(jié)束后的時間
long endtime = System.currentTimeMillis();
System.out.println("復(fù)制文件所消耗時間:" + (endtime - begintime) + "ms");
in.close();
out.close();
}
// 輸出:
復(fù)制文件所消耗時間:8ms
可以看出復(fù)制文件消耗時間明顯減少,說明使用緩沖區(qū)讀寫文件可以有效地提高程序讀寫效率
4. 字符流
4.1 字符流定義及基本用法
?? 前面講解的內(nèi)容都是通過字節(jié)流直接對文件進(jìn)行讀寫。如果讀寫的文件內(nèi)容是字符,考慮到使用字節(jié)流讀寫字符可能存在傳輸效率以及數(shù)據(jù)編碼問題、此時建議使用字符流。
同字節(jié)流一樣,字符流也有兩個抽象的頂級父類,分別是 Reader類 和 Writer類。
- Reader 類是字符輸入流,用于從某個源設(shè)備讀取字符;
- Writer類是字符輸出流。用于向某個目標(biāo)設(shè)備寫入字符。
- 在JDK中,Reader 類和Writer 類提供了一系列與讀寫數(shù)據(jù)相關(guān)的方法。
注:字符流 = 字節(jié)流 + 編碼表
Reader 類的常用方法
| 方法聲明 | 功能描述 |
| int read() | 以字符為單位讀數(shù)據(jù) |
| int read(char[] cbuf) | 將數(shù)據(jù)讀入 char 類型的數(shù)組,并返回數(shù)組長度 |
| int read(char[] cbuf, int off, int len) | 將數(shù)據(jù)讀入 char 類型的數(shù)組的指定區(qū)間,并返回數(shù)組長度 |
| void close() | 關(guān)閉數(shù)據(jù)流 |
| long transferTo(Writer out) | 將數(shù)據(jù)之間讀入字符輸出流 |
Writer 類的常用方法
| 方法聲明 | 功能描述 |
| void write(int c) | 以字符為單位寫數(shù)據(jù) |
| void write(char[] cbuf) | 將 char 類型的數(shù)組中的數(shù)據(jù)寫出 |
| void write(char[] cbuf, int off, int len) | 將 char 類型的數(shù)組中指定區(qū)間的數(shù)據(jù)寫出 |
| void write(String str) | 將 String 類型的數(shù)據(jù)寫出 |
| void write(String str, , int off, int len) | 將 String 類型中指定區(qū)間的數(shù)據(jù)寫出 |
| void flush() | 強(qiáng)制將緩沖區(qū)的數(shù)據(jù)同步到輸出流 (刷新流),之后還可以繼續(xù)寫數(shù)據(jù) |
| void close() | 關(guān)閉數(shù)據(jù)流 |
Reader 類 和 Writer 類作為字符流的頂級父類,也有許多子類,形成了體系結(jié)構(gòu),分別如下:
Reader 體系結(jié)構(gòu)圖:

Writer 體系結(jié)構(gòu)圖:

?? 在上面我們可以看到字符流的繼承關(guān)系和字節(jié)流的繼承關(guān)系類似,Reader 類 和 Writer 類的很多子類都是成對出現(xiàn)。例如:
- FileReader 和 FileWriter 用于讀寫文件
- BufferedReader 和 BufferedWriter 是具有緩沖功能的字符流,使用他們可以提高讀寫效率
4.2 字符流讀文件
??在程序開發(fā)中,經(jīng)常需要對文本文件的內(nèi)容進(jìn)行讀取。如果想從文件中直接讀取字符,便可以使用字符輸入流 FileReader,通過它可以從關(guān)聯(lián)的文件中讀取一個或一組字符。
下面通過一個案例演示如何使用 FileReader 讀取文件中的字符:
- 首先新建文本文件 test.txt 并在其中輸入字符 “itcast”
- 然后創(chuàng)建字符輸入流 FileReader對象以讀取 reader.txt文件中的內(nèi)容
public static void main(String[] args) throws Exception {
// 創(chuàng)建一個 FileReader 對象,用來讀取文件字符
FileReader reader = new FileReader("src/IO/test.txt");
int ch; // 用于記錄讀取的字符
while((ch = reader.read()) != -1){ // 循環(huán)判斷是否讀到文件末尾
System.out.print((char) ch); // 不是文件末尾就打印字符
}
reader.close(); // 關(guān)閉字符輸入流,釋放資源
}
// 輸出
itcast注:FileReader對象的 read() 方法返回的是 int 類型的值,如果想獲得字符,就必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
4.3 字符流寫文件
????上面講解了字符流對文本文件內(nèi)容的讀取?,F(xiàn)在講解通過字符流向文本文件中寫入內(nèi)容,此時需要使用FileWriter類,該類可以一次向文件中寫人一個或一組字符。
下面通過一個案例演示如何使用 FileWriter 將字符寫入文件
public static void main(String[] args) throws Exception {
// 創(chuàng)建一個 FileWriter 對象,用于向文件寫入數(shù)據(jù)
FileWriter writer = new FileWriter("src/IO/example.txt");
String str = "IsLand1314";
writer.write(str); // 將字符數(shù)據(jù)寫入到文本文件中
writer.write("\r\n"); //輸出換行
writer.close();
}注意:
FileWriter 同 FileOutputStream 一樣,如果指定的文件不存在,就會先創(chuàng)建文件,再寫入數(shù)據(jù);如果文件存在,則原文件內(nèi)容會被覆蓋。如果想在文件末尾追加數(shù)據(jù),同樣需要調(diào)用重載的構(gòu)造方法,將上面第三行代碼修改為:
FileWriter writer = new FileWriter("src/IO/example.txt", true);
再次運(yùn)行程序就可以在文件中實現(xiàn)追加的功能
4.4 數(shù)據(jù)編碼解碼問題
由于字節(jié)流操作中文不是特別的方便,所以Java就提供字符流
- 字符流 = 字節(jié)流 + 編碼表
中文的字節(jié)存儲方式
- 用字節(jié)流復(fù)制文本文件時,文本文件也會有中文,但是沒有問題,原因是最終底層操作會自動進(jìn)行字節(jié)拼接成中文,如何識別是中文的呢?
- 漢字在存儲的時候,無論選擇哪種編碼存儲,第一個字節(jié)都是負(fù)數(shù)
| 函數(shù)聲明 | 功能描述 |
| byte[] getBytes() | 使用平臺的默認(rèn)字符集將該 String編碼為一系列字節(jié) |
| byte[] getBytes(String charsetName) | 使用指定的字符集將該 String編碼為一系列字節(jié) |
| String(byte[] bytes) | 使用平臺的默認(rèn)字符集解碼指定的字節(jié)數(shù)組來創(chuàng)建字符串 |
| String(byte[] bytes, String charsetName) | 通過指定的字符集解碼指定的字節(jié)數(shù)組來創(chuàng)建字符串 |
代碼演示:
public static void main(String[] args) throws UnsupportedEncodingException {
//定義一個字符串
String s = "中國";
//byte[] bys = s.getBytes(); //[-28, -72, -83, -27, -101, -67]
//byte[] bys = s.getBytes("UTF-8"); //[-28, -72, -83, -27, -101, -67]
byte[] bys = s.getBytes("GBK"); //[-42, -48, -71, -6]
System.out.println(Arrays.toString(bys));
//String ss = new String(bys);
//String ss = new String(bys,"UTF-8");
String ss = new String(bys,"GBK");
System.out.println(ss);
}5. 轉(zhuǎn)換流
?? 前面提到I/0流分為字節(jié)流和字符流,字節(jié)流和字符流之間可以進(jìn)行轉(zhuǎn)換。JDK提供了兩個類用于將字節(jié)流轉(zhuǎn)換為字符流,分別是 InputStreamReader 和 OutputStreamReader。
InputStreamReader:是從字節(jié)流到字符流的橋梁,父類是 Reader
- 它讀取字節(jié),并使用指定的編碼將其解碼為字符
- 它使用的字符集可以由名稱指定,也可以被明確指定,或者可以接受平臺的默認(rèn)字符集
OutputStreamReader:是從字符流到字節(jié)流的橋梁,父類是 Writer
- 是從字符流到字節(jié)流的橋梁,使用指定的編碼將寫入的字符編碼為字節(jié)
- 它使用的字符集可以由名稱指定,也可以被明確指定,或者可以接受平臺的默認(rèn)字符集
通過 InputStreamReader 和 OutputStreamReader 將字節(jié)流轉(zhuǎn)換為字符流,可以提高文件的讀寫效率
| 方法聲明 | 功能描述 |
| InputStreamReader(InputStream in) | 使用默認(rèn)字符編碼創(chuàng)建InputStreamReader對象 |
| InputStreamReader(InputStream in,String chatset) | 使用指定的字符編碼創(chuàng)建InputStreamReader對象 |
| OutputStreamWriter(OutputStream out) | 使用默認(rèn)字符編碼創(chuàng)建OutputStreamWriter對象 |
| OutputStreamWriter(OutputStream out,String charset) | 使用指定的字符編碼創(chuàng)建OutputStreamWriter對象 |
下面通過一個案例演示如何將字節(jié)流轉(zhuǎn)為字符流
- 首先.在src項目的根目錄下新建文本文件 test.txt
- 并在文件中輸入“Island1314”
- 其次,在sre文件夾中創(chuàng)建一個類,在類中創(chuàng)建字節(jié)輸入流 FileInputStream對象讀取src.txt文件中的內(nèi)容,并將字節(jié)輸入流轉(zhuǎn)換成字符輸入流。
- 再次,創(chuàng)建一個字節(jié)輸出流對象,并指定目標(biāo)文件為des.txt
- 最后,將字節(jié)輸出流轉(zhuǎn)換成字符輸出流將字符輸出到文件中
public static void main(String[] args) throws Exception {
// 創(chuàng)建字節(jié)輸入流 in ,并且指定源文件 test.txt
FileInputStream in = new FileInputStream("src/IO/test.txt");
// 將字節(jié)輸入流 in 轉(zhuǎn)化為 字符輸入流 isr
InputStreamReader isr = new InputStreamReader(in);
// 創(chuàng)建字節(jié)輸出流 out ,并且指定源文件 des.txt
FileOutputStream out = new FileOutputStream("src/IO/des.txt");
// 將字節(jié)輸出流 out 轉(zhuǎn)化為 字符輸出流 osw
OutputStreamWriter osw = new OutputStreamWriter(out);
int ch; // 定義一個變量用于記錄讀取的字符
while((ch = isr.read()) != -1) // 循環(huán)判斷是否讀到文件末尾
{
osw.write(ch);
}
isr.close(); // 關(guān)閉字符輸入流,節(jié)省資源
osw.close(); // 關(guān)閉字符輸出流,節(jié)省資源
}6. 緩沖流
6.1 字節(jié)緩沖流
- BufferedOutputStream:該類實現(xiàn)緩沖輸出流.通過設(shè)置這樣的輸出流,應(yīng)用程序可以向底層輸出流寫入字節(jié),而不必為寫入的每個字節(jié)導(dǎo)致底層系統(tǒng)的調(diào)用
- BufferedInputStream:創(chuàng)建BufferedInputStream將創(chuàng)建一個內(nèi)部緩沖區(qū)數(shù)組.當(dāng)從流中讀取或跳過字節(jié)時,內(nèi)部緩沖區(qū)將根據(jù)需要從所包含的輸入流中重新填充,一次很多字節(jié)
- 字節(jié)流緩沖區(qū)的核心優(yōu)勢就是一次讀取多個字節(jié)數(shù)據(jù),從而減少硬盤操作子樹
構(gòu)造方法:
| BufferedOutputStream(OutputStream out) | 創(chuàng)建字節(jié)緩沖輸出流對象 |
| BufferedInputStream(InputStream in) | 創(chuàng)建字節(jié)緩沖輸入流對象 |
public static void main(String[] args) throws IOException {
//字節(jié)緩沖輸出流:BufferedOutputStream(OutputStream out)
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/IO/test.txt"));
//寫數(shù)據(jù)
bos.write("hello\r\n".getBytes());
bos.write("world\r\n".getBytes());
//釋放資源
bos.close();
//字節(jié)緩沖輸入流:BufferedInputStream(InputStream in)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/IO/test.txt"));
//一次讀取一個字節(jié)數(shù)據(jù)
// int by;
// while ((by=bis.read())!=-1) {
// System.out.print((char)by);
// }
//一次讀取一個字節(jié)數(shù)組數(shù)據(jù)
byte[] bys = new byte[1024];
int len;
while ((len=bis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
//釋放資源
bis.close();
}6.2 字符緩沖流
- BufferedWriter:將文本寫入字符輸出流,緩沖字符,以提供單個字符,數(shù)組和字符串的高效寫入,可以指定緩沖區(qū)大小,或者可以接受默認(rèn)大小。默認(rèn)值足夠大,可用于大多數(shù)用途
- BufferedReader:從字符輸入流讀取文本,緩沖字符,以提供字符,數(shù)組和行的高效讀取,可以指定緩沖區(qū)大小,或者可以使用默認(rèn)大小。 默認(rèn)值足夠大,可用于大多數(shù)用途
| BufferedWriter(Writer out) | 創(chuàng)建字符緩沖輸出流對象 |
| BufferedReader(Reader in) | 創(chuàng)建字符緩沖輸入流對象 |
public static void main(String[] args) throws IOException {
//創(chuàng)建字符緩沖輸出流
BufferedWriter bw = new BufferedWriter(new FileWriter("src/IO/test.txt"));
//寫數(shù)據(jù)
for (int i = 0; i < 10; i++) {
bw.write("hello" + i);
//bw.write("\r\n");
bw.newLine();
bw.flush();
}
//釋放資源
bw.close();
//創(chuàng)建字符緩沖輸入流
BufferedReader br = new BufferedReader(new FileReader("src/IO/test.txt"));
String line;
while ((line=br.readLine())!=null) {
System.out.println(line);
}
br.close();
}
7. 序列化反序列化
?? 程序在運(yùn)行過程中,數(shù)據(jù)都保存在Java對象(內(nèi)存)中,但很多情況下還需要將一些數(shù)據(jù)永久保存到磁盤上。為此,Java 提供了對象序列化機(jī)制,可以將對象中的數(shù)據(jù)保存到磁盤。
對象序列化(serialize)是指將一個Java對象轉(zhuǎn)換成一個 I/O流 的字節(jié)序列的過程。
- 對象序列化機(jī)制可以使內(nèi)存中的Java對象轉(zhuǎn)換成與平臺無關(guān)的二進(jìn)制流,
- 通過編寫程序,既可以將這種二進(jìn)制流持久地保存在磁盤上,
- 又可以通過網(wǎng)絡(luò)將其傳輸?shù)搅硪粋€網(wǎng)絡(luò)節(jié)點。
?? 其他程序在獲得了二進(jìn)制流后,還可以將二進(jìn)制流恢復(fù)成原來的Java對象,這種將 I/O流 中的字節(jié)序列恢復(fù)為Java對象的過程稱為 反序列化(deserialize)。
?? 如果想讓某個對象支持序列化機(jī)制,那么這個對象所屬的類必須是可序列化的。在Java中,可序列化的類必須實現(xiàn) Serializable 或 Externalizable 兩個接口之一。
Serializable 接口或 Externalizable 接口實現(xiàn)序列化機(jī)制的主要區(qū)別
| Serializable 接口 | Externalizable 接口 |
| 系統(tǒng)自動存儲必要的信息 | 由程序員自己決定要存儲的信息 |
| Java 內(nèi)部支持,易于實現(xiàn),只需實現(xiàn)該接口即可,不需要其他代碼支持 | 該接口只提供了兩個抽象方法,實現(xiàn)該接口時必須重寫這兩個抽象方法 |
| 性能較差 | 性能較好 |
?? 與實現(xiàn) Serializable 接口相比,雖然實現(xiàn) Externalizable 接口可以帶來性能上的一定提升,但由于后者需要實現(xiàn)兩個抽象方法,所以將導(dǎo)致編程的復(fù)雜度提高。
- 在實際開發(fā)時,大部分情況下使用Serializable 接口的方式實現(xiàn)對象序列化。
??使用Serializable 接口實現(xiàn)對象序列化非常簡單,只需要讓目標(biāo)類實現(xiàn) Serializable 接口即可,無須實現(xiàn)任何方法。例如,自定義Person類,讓Person類實現(xiàn) Serializable接口,如下:
public class Person implements Serializable{
// 為該類指定 serialVersionUID 變量值
private static final long serialVersionUID = 1L;
// 聲明變量
private int id;
private String name;
private int age;
//... 此處省略各屬性的 gettter 和 setter 方法
}?? 在上述代碼中,Person類實現(xiàn)了 Serializable接口,并指定了 serialVersionUID變量值,該屬性的值的作用是標(biāo)識Java類的序列化版本。如果不顯式定義 serialVersionUID變量值,那么serialVersionUID屬性的值將由 Java 虛擬機(jī) 根據(jù)類的相關(guān)信息計算得出
補(bǔ)充知識:serialVersionUID
?? serialVersionUID適用于Java的對象序列化機(jī)制。簡單來說,Java的對象序列化機(jī)制是通過判斷類的 serialVersionUID 來驗證版本一致性的。在進(jìn)行反序列化時,Java虛擬機(jī)會把字節(jié)流中的 serialVersionUID 與本地相應(yīng)實體類的 serialVersionUID 進(jìn)行比較。如果相同,就認(rèn)為是一致的,可以進(jìn)行反序列化;否則就會拋出序列化版本不一致的異常。
- 因此,為了在反序列化時確保序列化版本的兼容性,最好在每一個要序列化的類中加入 private static final long serialVersionUID 的變量值,具體數(shù)值可自定義,默認(rèn)是1L。
- 如果不顯式指定 serialVersionUID 的值,系統(tǒng)可以根據(jù)類名、接口名、成員方法及屬性等生成一個64位的哈希值,將這個哈希值作為serialVersionUID的值。
- 定義了serialVersionUID的值,如果serialVersionUID所屬類的某個對象被序列化,即使該對象對應(yīng)的類被修改了,該對象也依然可以被正確地反序列化。
8. 小結(jié)
本章主要介紹了 I/O流 的相關(guān)知識。
?? 包括File類,包括創(chuàng)建File對象、File 類的常用方法、遍歷目錄下的文件和刪除文件及目錄;字節(jié)流,包括字節(jié)流的概念、字節(jié)流讀文件、字節(jié)流寫文件和文件的復(fù)制;字符流,包括字符流的定義及基本用法、字符流讀文件和字符流寫文件;轉(zhuǎn)換流的使用;序列化和反序列化。通過本章的學(xué)習(xí),讀者應(yīng)該了解 I/O 流,并且熟練掌握了 I/O 流的相關(guān)知識。
補(bǔ)充:字節(jié)流與字符流區(qū)別
字節(jié)流是IO中最基礎(chǔ)的形式,它以字節(jié)(8位)為單位進(jìn)行數(shù)據(jù)傳輸,適用于處理所有類型的數(shù)據(jù),包括文本、圖片、音頻和視頻等二進(jìn)制數(shù)據(jù)。在Java中,字節(jié)流的基類是InputStream和OutputStream。字節(jié)流在操作時通常不會使用緩沖區(qū),直接與文件本身進(jìn)行操作,這意味著每次調(diào)用read方法都可能伴隨著一次磁盤IO,因此效率相對較低。為了提高效率,可以使用如BufferedInputStream和BufferedOutputStream這樣的緩沖字節(jié)流。
字符流則是以Unicode碼元(16位)為單位進(jìn)行數(shù)據(jù)傳輸,主要用于處理文本數(shù)據(jù)。字符流在處理數(shù)據(jù)時會涉及字符編碼的轉(zhuǎn)換,如UTF-8或GBK等。在Java中,字符流的基類是Reader和Writer。字符流在輸出前會完成Unicode碼元序列到相應(yīng)編碼方式的字節(jié)序列的轉(zhuǎn)換,并使用內(nèi)存緩沖區(qū)來存放轉(zhuǎn)換后的字節(jié)序列,等待都轉(zhuǎn)換完畢再一同寫入磁盤文件中。
主要區(qū)別在于:
字節(jié)流操作的基本單元為字節(jié),而字符流操作的基本單元為Unicode碼元。
字節(jié)流不使用緩沖區(qū),字符流使用緩沖區(qū)。
字節(jié)流可以處理任何類型的數(shù)據(jù),字符流主要處理文本數(shù)據(jù)。
字節(jié)流與文件直接操作,字符流在操作時使用緩沖區(qū)。
到此這篇關(guān)于Java中I/O操作的文章就介紹到這了,更多相關(guān)Java I/O操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?中?hashCode()?與?equals()?的關(guān)系(面試)
這篇文章主要介紹了Java中hashCode()與equals()的關(guān)系,ava中hashCode()和equals()的關(guān)系是面試中的??键c,文章對hashCode與equals的關(guān)系做出詳解,需要的小伙伴可以參考一下2022-09-09
SpringBoot管理RabbitMQ中的Channel詳解
這篇文章主要介紹了SpringBoot管理RabbitMQ中的Channel詳解,channel僅存在于connection的上下文中,而不會單獨存在,當(dāng)channel關(guān)閉時,其上的所有channel也會關(guān)閉,需要的朋友可以參考下2023-08-08
使用mybatis-plus分頁出現(xiàn)兩個Limit的問題解決
在使用MyBatis-Plus進(jìn)行分頁查詢時,可能會遇到查詢SQL中出現(xiàn)兩個limit語句的問題,這通常是由于在多個模塊中重復(fù)引入了MyBatis-Plus的分頁插件所導(dǎo)致的,下面就來介紹一下如何解決,感興趣的可以了解一下2024-10-10
Java中ArrayList和LinkedList有什么區(qū)別舉例詳解
這篇文章主要介紹了Java中ArrayList和LinkedList區(qū)別的相關(guān)資料,包括數(shù)據(jù)結(jié)構(gòu)特性、核心操作性能、內(nèi)存與GC影響、擴(kuò)容機(jī)制、線程安全與并發(fā)方案,以及工程實踐場景,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02
Java編程實現(xiàn)五子棋人人對戰(zhàn)代碼示例
這篇文章主要介紹了Java編程實現(xiàn)五子棋人人對戰(zhàn)代碼示例,具有一定借鑒價值,需要的朋友可以參考下。2017-11-11
mybatis使用foreach語句實現(xiàn)IN查詢(三種)
這篇文章主要介紹了mybatis使用foreach語句實現(xiàn)IN查詢(三種),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12

