File類
- 位於
java.io
之下 - File類的一個物件,代表一個實際的文件檔案或一個資料夾
- File類只涉及檔案或資料夾的創建、刪除、改名…等方法,只能碰到外殼;讀取或寫入需要IO流才能做到
- File類的物件通常做為參數傳到流的構造器中,作為讀取或寫入的終點
實例化
構造器的幾種參數
File(String filepath)
:一般路徑File(String parentPath, String childPath)
:分成父目錄與子目錄,再拼接起來File(FileA parentFile, String childPath)
:相當於以FileA
為父目錄下的子目錄
路徑
-
不指明就是相對路徑,在IDEA中若使用
JUnit
單元測試位置是Module
下(與src
資料夾同層);若main()
方法則是在Project
下 -
指明則是絕對路徑,例如
"G:\\Java\\code\\guigu\\day26IO\\src\\hi.txt"
-
關於正反、雙斜槓等疑惑可以參考這篇:
-
File類中有一個常量
File.separator
可以根據系統自動變換分隔符
常用方法
預設以一個File類的實例物件.調用以下方法
String getAbsolutionPath()
:獲取絕對路徑String getPath()
:獲取相對路徑String getName()
:獲取檔案名稱String getParent()
:獲取上層文件夾目錄路徑(需本身是絕對路徑),若無則返回nullLong Length()
:獲取檔案大小,單位位元組Long LastModified()
:獲取最後修改時間戳file1.boolean renameTo(File file2)
:將file1搬到file2的路徑並改名成file2,必須有file1無file2(他是一個移動+改名,無法覆蓋)String[] List()
:獲取指定目錄下的所有資料夾名與檔案名,類似dir與ls,只適用於資料夾目錄File[] ListFiles()
:獲取指定目錄下的所有資料夾與檔案的File數組,只適用於資料夾目錄boolean isDirctory()
:判斷是否為文件夾boolean isFile()
:判斷是否為檔案boolean exists()
:判斷是否存在boolean canRead()
:判斷是否能讀boolean canWrite()
:判斷是否能寫boolean isHidden()
:判斷是否隱藏
創建/刪除檔案
不是實例化,而是真正在硬碟中創建檔案
boolean createNewFile()
:創建檔案,若檔案已存在則不創建並返回false
boolean mkdir()
:創建資料夾,若資料夾已存在或不存在上層目錄,則不創建並返回false
boolean mkdirs()
:創建資料夾,若不存在上層目錄,一併創建boolean delete()
:執行就馬上刪了,可不會進資源回收桶,慎用,如果是文件夾必須是空的才能刪
IO流
IO即in和out,輸入和輸出
流(Stream)是一種抽象的概念,指資料的傳遞,流有以下特性:
- 先進先出:最先寫入輸出流的資料最先被輸入流讀取到
- 順序存取:可以一個接一個地往流中寫入一串位元組,讀出時也將按寫入順序讀取一串位元組,不能隨機訪問中間的資料(
RandomAccessFile
除外) - 只讀或只寫:每個流只能是輸入流或輸出流的一種,不能同時具備兩個功能,輸入流只能進行讀操作,對輸出流只能進行寫操作。在一個資料傳輸通道中,如果既要寫入資料,又要讀取資料,則要分別提供兩個流
分類
台灣用法,字節=位元組;字符=字元,個人認為這邊中國翻譯完勝。
中國用位元-字節-字符,元是"基礎"能組成"字",形象的"符"比"節點"大,邏輯清晰、字數整齊且不易混淆。
台灣用位元-位元組-字元,長短不一就算了,結果又用回"元",元應該是用在基本的、最小的、固定的單位,這邊記java字元是16-bit;而C語言的字元又是8-bit,媽的char就不該翻成字元
-
按資料流的方向:輸入流、輸出流
-
按處理資料單位:字節流(8bit = 1byte)、字符流(16bit = 1char)
-
按功能:節點流、處理流(針對流的流)
字節(位元組,8-bit) 字符(字元,16-bit) 輸入 InputStream Reader 輸出 OutputStream Writer 適用於 非文字檔案 純文字檔案(底層自帶緩衝)
字符流
專門用於純文字
範例-字符流讀取文字檔案
@Test
public void test1() {
// 造一個讀取的流,聲明在這是為了finally要關,所以先賦null(沒有實際造)
FileReader reader = null;
try {
// 指定要作為流的起點的檔案
File file1 = new File("hi.txt");
// 將檔案作為形參,傳入構造器,實際造流
reader = new FileReader(file1);
// .read()方法按順序讀出一個一個int形式的char,直到沒東西了返回-1
int data;
while ((data = reader.read()) != -1) {
System.out.print(((char) data));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 永遠記得開了流就要關
try {
// 關閉前先判斷是否為"一開始就沒進去實際造"的null
if (reader != null) reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
改良-使用帶參數的 read(cbuf)
方法
try {
// 指定要作為流的起點的檔案
File file1 = new File("hi.txt");
// 將檔案作為形參,傳入構造器,實際造
reader = new FileReader(file1);
// .read(cbuf)方法按順序讀出一個一個char[]形式的數組,
// 返回讀出的char數量,直到沒東西了返回-1
char[] cbuf = new char[5]; // 假設一次拿5個char出來
int len; // 用來接收拿出來的數量
while ((len = reader.read(cbuf)) != -1) {
for (int i = 0; i < len; i++) {
// 注意這個for循環不能寫i < cbuf.length,否則拿到不滿5個時會出錯
// "錯誤"不是空指針,而是會重複輸出上一輪的後幾個字
System.out.print(cbuf[i]);
}
// 再改進,取代上面的for,直接用String裝
String str = new String(cbuf, 0, len);
System.out.print(str);
}
}
範例-字符流輸出文字到檔案
FileWriter writer = null;
try {
File file2 = new File("file2.txt");
writer = new FileWriter(file2, true); // 參數append預設是false
writer.write("something...");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert writer != null;
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
new FileWriter(file, true);
此構造器的參數append
可以選擇輸出方式,預設或不寫為false
,意思就是每次調用時覆蓋一個新的檔案;true
時則表示在原有檔案追加
字節流
可以用於所有地方,但讀取文字時要注意
範例-字節流讀取文字
@Test
public void test3() throws Exception { // 偷懶先拋個異常
File f3 = new File("hi.txt");
FileInputStream fileInputStream = new FileInputStream(f3);
byte[] buf = new byte[5];
int n = 0;
while ((n = fileInputStream.read(buf)) != -1) {
String str = new String(buf, 0, n);
System.out.print(str);
}
fileInputStream.close();
}
- 在UTF-8編碼下用字節流讀取中文會出現切字問題,原因後述
範例-字節流複製圖片
@Test
public void test4() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
File src = new File("pic.png");
File dest = new File("newpic.png");
inputStream = new FileInputStream(src);
outputStream = new FileOutputStream(dest);
byte[] buf = new byte[5];
int len;
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert inputStream != null; // 並列寫完全OK,反正都是要關
inputStream.close();
assert outputStream != null;
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
緩衝流
透過緩衝區提高讀寫效率
範例-使用緩衝流實現複製
@Test
public void bufferTest1() {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
File src = new File("陳菊.avi");
File dest = new File("陳菊2.avi");
fileInputStream = new FileInputStream(src);
fileOutputStream = new FileOutputStream(dest);
// 造緩衝流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
// 用緩衝流操作
byte[] buf = new byte[1024];
int len;
while ((len = bufferedInputStream.read(buf)) != -1) {
bufferedOutputStream.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉流,穿脫衣服原則,後穿的先脫
try {
assert bufferedOutputStream != null;
bufferedOutputStream.close();
assert bufferedInputStream != null;
bufferedInputStream.close();
// 其實關閉外層流就把內層的也關了,所以可省略
fileOutputStream.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 就是在原有的流上套一層緩衝流
- 使用緩衝流讀寫速度有明顯提升,這邊懶的特別寫測試了
- 緩衝流有
flush()
方法可以手動清空緩衝區,正常不需要,自動會清
- 緩衝流有
- 把input、outputStream換成Reader、Writer也能操作文字檔案,特別提下關於換行:
Reader
有readLine()
方法Writer
有newLine()
方法
練習-用流實現加密解密
@Test
public void picLock() {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
// 在流的構造參數直接寫路徑相當於new一個File物件並包裝起來傳入,懶人福音
fileInputStream = new FileInputStream("pic.png");
fileOutputStream = new FileOutputStream("lockedPic.png");
byte[] buf = new byte[10];
int len;
while ((len = fileInputStream.read(buf)) != -1) {
// 進行加密修改字節,注意不是用foreach,那是讀出數據賦到一個暫時變量=白改
for (int i = 0; i < buf.length; i++) {
byte b = buf[i];
b = (byte) (b ^ 5); // 異或,兩者不同為true
buf[i] = b;
}
fileOutputStream.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
兩個知識點:
- 在流的構造參數直接寫路徑,省略創造
File
物件的步驟 - 由於加密方法是
異或
,再執行一遍就是解密了
轉換流
提供字節流跟字符流之間的轉換,本身是一種處理流、字符流(看最尾詞綴)
解碼
-
字節 -> 字符 (從一串瑣碎看不懂的轉為看得懂的,小到大)
-
InputStreamReader
:將一個字節的輸入流轉換為字符的輸入流 -
new InputStreamReader(fileInputStream,"UTF-8");
- 根據讀入的字節流需要指明編碼格式,沒寫會依照預設的
編碼
-
字符 -> 字節 (把人看的轉成給機器看的,大到小)
-
OutputStreamWriter
:將一個字符的輸出流轉換為字節的輸出流 -
new OutputStreamWriter(fileOutputStream,"Big5");
- 一樣需要指明編碼,這邊有點不好理解,outputStreamWriter物件本身屬於字符流,調用
write()
方法時,根據編碼格式輸出對應的字節流
- 一樣需要指明編碼,這邊有點不好理解,outputStreamWriter物件本身屬於字符流,調用
Unicode與UTF-8
Unicode
字符集是定義字符唯一編號的集合,相當於一本超大超厚的電話簿,他使用16+1位面符來編號,所以上限不是65536個字,總之全世界的文字都能存的下- 考慮
Unicode
的"大小或長度"是沒有意義的,他只是一種規範、一張表,所謂"一個Unicode佔16bit=2char
“是不嚴謹、不正確的說法 - 真正用來傳輸、實現”
編碼
“的是Unicode Transformation Format,UTF UTF-8
就是用8bit
=1byte
當作最小單位,一個"字"可以由1~4個byte
表示,由檔頭0或1的個數宣告此"字"有幾個byte
;長度可變是為了靈活省空間- 理論上是1~6
byte
但實際只用到4,例如在MySQL
中用utf8
編碼會被打死,一定要用utf8mb4
- 理論上是1~6
- 純英數字跟基本符號只佔1
byte
,與ACSII
相同 UTF-8
大部分漢字佔3byte
;所以用字節流去裝漢字,很可能把字給切剩了造成亂碼
小結
- 流的基本分類:
- 方向:輸入(
Input
)、輸出(Output
) - 單位:字節(
Stream
)、字符(Reader
/Writer
) - 功能:節點、處理
- 方向:輸入(
- 節點流前綴:
- 文件:
File
- 記憶體:
CharArray
、ByteArray
、String
- 管道(進程通訊):
Piped
- 文件:
- 處理流:
- 緩衝流:
Buffered
為前綴,可加速讀寫,套上後操作緩衝流(含關閉) - 轉換流:
InputStreamReader
:將輸入的字節轉字符流(解碼)OutputStreamWriter
:將輸出的字符轉字節流(編碼)
- 物件流:
Object
- 數據流:
Data
- 過濾流:
Filter
- 計數流:
LineNumber
- 打印流:
Print
- 緩衝流:
上次修改於 2021-12-11