JAVA 中的 IO 流

File 類(lèi)的使用

  • java.io.File 類(lèi):文件和文件目錄路徑的抽象表示形式,與平臺(tái)無(wú)關(guān)
  • File 能新建、刪除、重命名文件和目錄,但不能訪問(wèn)文件內(nèi)容本身。如果需要訪問(wèn)文件內(nèi)容本身,則需要使用輸入/輸出流
  • 想要在 Java 程序中表示一個(gè)真實(shí)存在的文件或目錄,那么必須有一個(gè) File 對(duì) 象,但是 Java 程序中的一個(gè) File 對(duì)象,可能沒(méi)有一個(gè)真實(shí)存在的文件或目錄
  • File 對(duì)象可以作為參數(shù)傳遞給流的構(gòu)造器

File 類(lèi)的常用構(gòu)造器

  • public File(String pathname)

    以 pathname 為路徑創(chuàng)建 File 對(duì)象

  • public File(String parent,String child)

    以 parent 為父路徑,child 為子路徑創(chuàng)建 File 對(duì)象

  • public File(File parent,String child)

    根據(jù)一個(gè)父 File 對(duì)象和子文件路徑創(chuàng)建 File 對(duì)象

路徑中的每級(jí)目錄之間用一個(gè)路徑分隔符隔開(kāi),windows和DOS系統(tǒng)默認(rèn)使用 \ 來(lái)表示,UNIX 和 URL 使用 / 來(lái)表示。Java 程序支持跨平臺(tái)運(yùn)行,因此路徑分隔符要慎用。為了解決這個(gè)隱患,F(xiàn)ile 類(lèi)提供了一個(gè)常量:

  • public static final String separator

根據(jù)操作系統(tǒng),動(dòng)態(tài)的提供分隔符。

舉例:

File file1 = new File("d:\\atguigu\\info.txt");
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
File file3 = new File("d:/atguigu");

File 類(lèi)常用方法

File 類(lèi)的獲取功能

  • public String getAbsolutePath():獲取絕對(duì)路徑

  • public String getPath() :獲取路徑

  • public String getName() :獲取名稱(chēng)

  • public String getParent():獲取上層文件目錄路徑。若無(wú),返回 null

  • public long length() :獲取文件長(zhǎng)度(即:字節(jié)數(shù))。不能獲取目錄的長(zhǎng)度。

  • public long lastModified() :獲取最后一次的修改時(shí)間,毫秒值

  • public String[] list() :獲取指定目錄下的所有文件或者文件目錄的名稱(chēng)數(shù)組

  • public File[] listFiles() :獲取指定目錄下的所有文件或者文件目錄的 File 數(shù)組

File 類(lèi)的重命名功能

  • public boolean renameTo(File dest):把文件重命名為指定的文件路徑

File 類(lèi)的判斷功能

  • public boolean isDirectory():判斷是否是文件目錄

  • public boolean isFile() :判斷是否是文件

  • public boolean exists() :判斷是否存在

  • public boolean canRead() :判斷是否可讀

  • public boolean canWrite() :判斷是否可寫(xiě)

  • public boolean isHidden() :判斷是否隱藏

File 類(lèi)的創(chuàng)建功能

  • public boolean createNewFile() :創(chuàng)建文件。若文件存在,則不創(chuàng)建,返回 false

  • public boolean mkdir() :創(chuàng)建文件目錄。如果此文件目錄存在,就不創(chuàng)建了。如果此文件目錄的上層目錄不存在,也不創(chuàng)建。

  • public boolean mkdirs() :創(chuàng)建文件目錄。如果上層文件目錄不存在,一并創(chuàng)建

File 類(lèi)的刪除功能

  • public boolean delete():刪除文件或者文件夾

Java中的刪除不走回收站。要?jiǎng)h除一個(gè)文件目錄,請(qǐng)注意該文件目錄內(nèi)不能包含文件或者文件目錄。

IO 流原理及流的分類(lèi)

Java IO 原理

  • I/O 是 Input/Output 的縮寫(xiě), I/O 技術(shù)是非常實(shí)用的技術(shù),用于處理設(shè)備之間的數(shù)據(jù)傳輸。如讀/寫(xiě)文件,網(wǎng)絡(luò)通訊等。Java 程序中,對(duì)于數(shù)據(jù)的輸入/輸出操作以 流(stream) 的方式進(jìn)行。java.io 包下提供了各種 類(lèi)和接口,用以獲取不同種類(lèi)的數(shù)據(jù),并通過(guò)標(biāo)準(zhǔn)的方法輸入或輸出數(shù)據(jù)。
  • 輸入input:讀取外部數(shù)據(jù)(磁盤(pán)、光盤(pán)等存儲(chǔ)設(shè)備的數(shù)據(jù))到程序(內(nèi)存)中。
  • 輸出 output:將程序(內(nèi)存)數(shù)據(jù)輸出到磁盤(pán)、光盤(pán)等存儲(chǔ)設(shè)備中。

流的分類(lèi)

  • 按操作數(shù)據(jù)單位不同分為:字節(jié)流(8 bit),字符流(16 bit)

  • 按數(shù)據(jù)流的流向不同分為:輸入流,輸出流

  • 按流的角色的不同分為:節(jié)點(diǎn)流,處理流

image.png

FileInputStream 用于讀取非文本數(shù)據(jù)之類(lèi)的原始字節(jié)流。要讀取字符流,需要使用 FileReader。

FileOutputStream 用于寫(xiě)出非文本數(shù)據(jù)之類(lèi)的原始字節(jié)流。要寫(xiě)出字符流,需要使用 FileWriter。

在寫(xiě)入一個(gè)文件時(shí),如果使用構(gòu)造器 FileOutputStream(file),則目錄下有同名文
件將被覆蓋。如果使用構(gòu)造器 FileOutputStream(file,true),則目錄下的同名文件不會(huì)被覆蓋,在文件內(nèi)容末尾追加內(nèi)容。

在讀取文件時(shí),必須保證該文件已存在,否則報(bào)異常。

  • 字節(jié)流操作字節(jié),比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
  • 字符流操作字符,只能操作普通文本文件。最常見(jiàn)的文本文:.txt,.java,.c,.cpp 等語(yǔ)言的源代碼。尤其注意 .doc, excel, ppt 這些不是文本文件

緩沖流

為了提高數(shù)據(jù)讀寫(xiě)的速度,Java API 提供了帶緩沖功能的流類(lèi),在使用這些流類(lèi)時(shí),會(huì)創(chuàng)建一個(gè)內(nèi)部緩沖區(qū)數(shù)組,缺省使用 8192 個(gè)字節(jié)(8Kb)的緩沖區(qū)。

  • 當(dāng)讀取數(shù)據(jù)時(shí),數(shù)據(jù)按塊讀入緩沖區(qū),其后的讀操作則直接訪問(wèn)緩沖區(qū)
  • 當(dāng)使用 BufferedInputStream 讀取字節(jié)文件時(shí),BufferedInputStream 會(huì)一次性從文件中讀取 8192個(gè)(8Kb),存在緩沖區(qū)中,直到緩沖區(qū)裝滿(mǎn)了,才重新從文件中讀取下一個(gè) 8192 個(gè)字節(jié)數(shù)組
  • 向流中寫(xiě)入字節(jié)時(shí),不會(huì)直接寫(xiě)到文件,先寫(xiě)到緩沖區(qū)中直到緩沖區(qū)寫(xiě)滿(mǎn), BufferedOutputStream 才會(huì)把緩沖區(qū)中的數(shù)據(jù)一次性寫(xiě)到文件里。使用方法 flush() 可以強(qiáng)制將緩沖區(qū)的內(nèi)容全部寫(xiě)入輸出流
  • 關(guān)閉流的順序和打開(kāi)流的順序相反。只要關(guān)閉最外層流即可,關(guān)閉最外層流也會(huì)相應(yīng)關(guān)閉內(nèi)層節(jié)點(diǎn)流
  • flush() 方法的使用:手動(dòng)將 buffer 中內(nèi)容寫(xiě)入文件
  • 如果是帶緩沖區(qū)的流對(duì)象的 close() 方法,不但會(huì)關(guān)閉流,還會(huì)在關(guān)閉流之前刷新緩沖區(qū),關(guān)閉后不能再寫(xiě)出

轉(zhuǎn)換流

轉(zhuǎn)換流提供了在字節(jié)流和字符流之間的轉(zhuǎn)換。Java API提供了兩個(gè)轉(zhuǎn)換流:

  • InputStreamReader:將 InputStream 轉(zhuǎn)換為 Reader

  • OutputStreamWriter:將 Writer 轉(zhuǎn)換為 OutputStream

字節(jié)流中的數(shù)據(jù)都是字符時(shí),轉(zhuǎn)成字符流操作更高效,很多時(shí)候我們使用轉(zhuǎn)換流來(lái)處理文件亂碼問(wèn)題。實(shí)現(xiàn)編碼和解碼的功能。

public void testMyInput() throws Exception {
    FileInputStream fis = new FileInputStream("dbcp.txt");
    FileOutputStream fos = new FileOutputStream("dbcp1.txt");
    
    InputStreamReader isr = new InputStreamReader(fis, "GBK");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
    
    BufferedReader br = new BufferedReader(isr);
    BufferedWriter bw = new BufferedWriter(osw);
    
    String str = null;
    while ((str = br.readLine()) != null) {
        bw.write(str);
        bw.newLine();
        bw.flush();
    }
    bw.close();
    br.close();
}

數(shù)據(jù)流

為了方便地操作 Java 語(yǔ)言的基本數(shù)據(jù)類(lèi)型和 String 的數(shù)據(jù),可以使用數(shù)據(jù)流。為了方便地操作Java語(yǔ)言的基本數(shù)據(jù)類(lèi)型和 String 的數(shù)據(jù),可以使用數(shù)據(jù)流。

  • DataInputStreamDataOutputStream
  • 分別“套接”在 InputStreamOutputStream 子類(lèi)的流上

DataInputStream 中的方法

  • boolean readBoolean()
  • byte readByte()
  • char readChar()
  • float readFloat()
  • double readDouble()
  • short readShort()
  • long readLong()
  • int readInt()
  • String readUTF()
  • void readFully(byte[] b)
DataInputStream dis = null;
try {
    dis = new DataInputStream(new FileInputStream("destData.dat"));
    String info = dis.readUTF();
    boolean flag = dis.readBoolean();
    long time = dis.readLong();
    System.out.println(info);
    System.out.println(flag);
    System.out.println(time);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (dis != null) {
        try {
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

DataOutputStream 中的方法

將上述的方法的 read 改為相應(yīng)的 write 即可。

DataOutputStream dos = null;
try { // 創(chuàng)建連接到指定文件的數(shù)據(jù)輸出流對(duì)象
    dos = new DataOutputStream(new FileOutputStream("destData.dat"));
    dos.writeUTF("我愛(ài)北京天安門(mén)"); // 寫(xiě)UTF字符串
    dos.writeBoolean(false); // 寫(xiě)入布爾值
    dos.writeLong(1234567890L); // 寫(xiě)入長(zhǎng)整數(shù)
    System.out.println("寫(xiě)文件成功!");
} catch (IOException e) {
    e.printStackTrace();
} finally { // 關(guān)閉流對(duì)象
    try {
        if (dos != null) {
            // 關(guān)閉過(guò)濾流時(shí),會(huì)自動(dòng)關(guān)閉它包裝的底層節(jié)點(diǎn)流
            dos.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    } 
}

對(duì)象流(ObjectInputStream和OjbectOutputSteam)

用于存儲(chǔ)和讀取基本數(shù)據(jù)類(lèi)型數(shù)據(jù)或?qū)ο蟮奶幚砹鳌K膹?qiáng)大之處就是可以把 Java 中的對(duì)象寫(xiě)入到數(shù)據(jù)源中,也能把對(duì)象從數(shù)據(jù)源中還原回來(lái)。

  • 序列化:用 ObjectOutputStream 類(lèi)保存基本類(lèi)型數(shù)據(jù)或?qū)ο蟮臋C(jī)制。

  • 反序列化:用 ObjectInputStream 類(lèi)讀取基本類(lèi)型數(shù)據(jù)或?qū)ο蟮臋C(jī)制。

注意:ObjectOutputStream 和ObjectInputStream 不能序列化 static 和 transient 修飾的成員變量。

對(duì)象序列化機(jī)制允許把內(nèi)存中的 Java 對(duì)象轉(zhuǎn)換成平臺(tái)無(wú)關(guān)的二進(jìn)制流,從而允許把這種二進(jìn)制流持久地保存在磁盤(pán)上,或通過(guò)網(wǎng)絡(luò)將這種二進(jìn)制流傳輸?shù)搅硪粋€(gè)網(wǎng)絡(luò)節(jié)點(diǎn)。當(dāng)其它程序獲取了這種二進(jìn)制流,就可以恢復(fù)成原來(lái)的 Java 對(duì)象。

序列化的好處在于可將任何實(shí)現(xiàn)了 Serializable 接口的對(duì)象轉(zhuǎn)化為字節(jié)數(shù)據(jù),使其在保存和傳輸時(shí)可被還原。

序列化是 RMI(Remote Method Invoke – 遠(yuǎn)程方法調(diào)用)過(guò)程的參數(shù)和返回值都必須實(shí)現(xiàn)的機(jī)制,而 RMI 是 JavaEE 的基礎(chǔ)。因此序列化機(jī)制是 JavaEE 平臺(tái)的基礎(chǔ)。

如果需要讓某個(gè)對(duì)象支持序列化機(jī)制,則必須讓對(duì)象所屬的類(lèi)及其屬性是可序列化的,為了讓某個(gè)類(lèi)是可序列化的,該類(lèi)必須實(shí)現(xiàn)如下兩個(gè)接口之一。否則,會(huì)拋出 NotSerializableException 異常。

  • Serializable

  • Externalizable

凡是實(shí)現(xiàn) Serializable 接口的類(lèi)都有一個(gè)表示序列化版本標(biāo)識(shí)符的靜態(tài)變量:

  • private static final long serialVersionUID

如果類(lèi)沒(méi)有顯示定義這個(gè)靜態(tài)常量,它的值是 Java 運(yùn)行時(shí)環(huán)境根據(jù)類(lèi)的內(nèi)部細(xì)節(jié)自動(dòng)生成的。若類(lèi)的實(shí)例變量做了修改,serialVersionUID 可能發(fā)生變化。故建議,顯式聲明。

簡(jiǎn)單來(lái)說(shuō),Java 的序列化機(jī)制是通過(guò)在運(yùn)行時(shí)判斷類(lèi)的 serialVersionUID 來(lái)驗(yàn)證版本一致性的。在進(jìn)行反序列化時(shí),JVM 會(huì)把傳來(lái)的字節(jié)流中的 serialVersionUID 與本地相應(yīng)實(shí)體類(lèi)的 serialVersionUID 進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會(huì)出現(xiàn)序列化版本不一致的異常(InvalidCastException)。

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“data.txt"));
Person p = new Person("韓梅梅", 18, "中華大街", new Pet());
oos.writeObject(p);
oos.flush();
oos.close();

注意:寫(xiě)一次,操作 flush() 一次。

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“data.txt"));
Person p1 = (Person)ois.readObject();
System.out.println(p1.toString());
ois.close();

隨機(jī)存取文件流(RandomAccessFile 類(lèi))的使用

RandomAccessFile 聲明在 java.io 包下,但直接繼承于 java.lang.Object 類(lèi)。并且它實(shí)現(xiàn)了 DataInput、DataOutput 這兩個(gè)接口,也就意味著這個(gè)類(lèi)既可以讀也可以寫(xiě)

構(gòu)造器

  • public RandomAccessFile(File file, String mode)
  • public RandomAccessFile(String name, String mode)

創(chuàng)建 RandomAccessFile 類(lèi)實(shí)例需要指定一個(gè) mode 參數(shù),該參數(shù)指定 RandomAccessFile 的訪問(wèn)模式:

  • r:以只讀方式打開(kāi)
  • rw:打開(kāi)以便讀取和寫(xiě)入
  • rwd:打開(kāi)以便讀取和寫(xiě)入;同步文件內(nèi)容的更新
  • rws::打開(kāi)以便讀取和寫(xiě)入;同步文件內(nèi)容和元數(shù)據(jù)的更新

如果模式為只讀 r。則不會(huì)創(chuàng)建文件,而是會(huì)去讀取一個(gè)已經(jīng)存在的文件,如果讀取的文件不存在則會(huì)出現(xiàn)異常。 如果模式為 rw 讀寫(xiě)。如果文件不存在則會(huì)去創(chuàng)建文件,如果存在則不會(huì)創(chuàng)建。

RandomAccessFile 對(duì)象包含一個(gè)記錄指針,用以標(biāo)示當(dāng)前讀寫(xiě)處的位置。 RandomAccessFile 類(lèi)對(duì)象可以自由移動(dòng)記錄指針:

  • long getFilePointer():獲取文件記錄指針的當(dāng)前位置
  • void seek(long pos):將文件記錄指針定位到 pos 位置
// 讀取文件
RandomAccessFile raf = new RandomAccessFile(“test.txt”, “rw”);
// 指針跳到角標(biāo)為5的位置
raf.seek(5);
byte [] b = new byte[1024];
int off = 0;
int len = 5;
raf.read(b, off, len);
String str = new String(b, 0, len);
System.out.println(str);
raf.close();
// 寫(xiě)入文件
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
raf.seek(5);
//先讀出來(lái)
String temp = raf.readLine();
// 指針跳到角標(biāo)為5的位置,可以理解為在角標(biāo)為5的位置插入數(shù)據(jù)
raf.seek(5);
raf.write("xyz".getBytes());
raf.write(temp.getBytes());
raf.close();

我們可以用 RandomAccessFile 這個(gè)類(lèi),來(lái)實(shí)現(xiàn)一個(gè)多線程斷點(diǎn)下載的功能,用過(guò)下載工具的朋友們都知道,下載前都會(huì)建立兩個(gè)臨時(shí)文件,一個(gè)是與被下載文件大小相同的空文件,另一個(gè)是記錄文件指針的位置文件,每次暫停的時(shí)候,都會(huì)保存上一次的指針,然后斷點(diǎn)下載的時(shí)候,會(huì)繼續(xù)從上一次的地方下載,從而實(shí)現(xiàn)斷點(diǎn)下載或上傳的功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容