您好,登錄后才能下訂單哦!
這篇文章主要講解了“java的文件IO舉例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java的文件IO舉例分析”吧!
JDK 1.4的java.nio.*包中引入了新的IO類庫,其目的在于提高速度。實際上,舊的IO包已經使用nio重新實現過,以便充分利用這種速度提高。速度提高源自于所使用的結構更接近于操作系統執行IO的方式:通道和緩沖器。我們可以把它想象成一個煤礦,通道是一個包含煤層(數據)的礦藏,而緩沖器則是派送到礦藏的卡車。卡車載滿煤炭而歸,我們再從卡車上獲得煤炭。也就是說,我們并沒有直接和通道交互,我們只是和緩沖器交互,并把緩沖器派送到通道。要么從緩沖器獲得數據,要么向緩沖器發送數據。
唯一直接與通道交互的緩沖器是ByteBuffer——可以存儲未加工字節的緩沖器。java.nio.ByteBuffer是相當基礎的類:通過告知分配多少存儲空間來創建一個ByteBuffer對象,并且還有一個方法選擇集,用于以原始的字節形式或基本數據類型輸出和讀取數據。但是,沒辦法輸出或讀取對象,即使是字符串對象也不行。這種處理雖然很低級,但卻正好,因為這是大多數操作系統中更有效的映射方式。
舊IO類庫有三個類被修改了,用以產生FileChannel。這三個被修改類是FileInputStream、FileOutputStream以及用于既讀又寫的RandomAccessFile。這些都是字節操作流,與底層nio性質一致。Reader和Writer這些字符模式類不能用于產生通道;但是java.nio.channels.Channels類提供了適用方法,用于在通道中產生Reader和Writer。
//演示上面三種類型的流,用于產生可寫的,可讀可寫的及可讀的通道 package com.wise.tiger; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class GetChannel { private static final int BUFF_SIZE = 1024; public static void main(String[] args) throws IOException { // 寫文件 FileChannel fc = new FileOutputStream("data.txt").getChannel(); /* * 使用warp()方法將已存在的字節數組包裝到ByteBuffer中 也可以使用put方法直接進行填充 */ fc.write(ByteBuffer.wrap("I'm Peppa Pig.".getBytes())); fc.close(); // 文件末尾添加內容 fc = new RandomAccessFile("data.txt", "rw").getChannel(); fc.position(fc.size());// 移動到文件末尾 fc.write(ByteBuffer.wrap("This is my little brother, George".getBytes())); fc.close(); // 讀文件 fc = new FileInputStream("data.txt").getChannel(); /* * 分配ByteBuffer,對于只讀訪問,必須顯式地使用靜態的allocate()方法來分配ByteBuffer * nio的目標就是快速移動大量數據,因此ByteBuffer的大小就顯得尤為重要(必須通過實際運行程序找到最佳尺寸) */ ByteBuffer buff = ByteBuffer.allocate(BUFF_SIZE); /* * 一旦調用read()來告知FileChannel向ByteBuffer存儲字節,就必須調用緩沖器上的flip(),讓它做好讓別人讀取字節的準備。 * 如果打算使用緩沖器執行進一步read()操作,也必須得使用clear()來為每個read()做好準備,如下copy文件 */ fc.read(buff); buff.flip(); while (buff.hasRemaining()) { System.out.print((char) buff.get()); } fc.close(); } }
這里展示的任何流類,getChannel()將會產生一個FileChanel。通道是一個相當基礎的東西:可以向它傳送用于讀寫的ByteBuffer,并且可以鎖定文件的某些區域用于獨占式訪問。
public class ChannelCopy { private static final int CAPACITY = 1024; public static void main(String[] args) throws IOException { if(args.length != 2)System.exit(1); FileChannel in = new FileInputStream(args[0]).getChannel(), out = new FileOutputStream(args[1]).getChannel(); ByteBuffer buff = java.nio.ByteBuffer.allocate(CAPACITY); while(in.read(buff) != -1) { buff.flip(); out.write(buff); buff.clear(); } out.close(); in.close(); } }
然而,這個程序并不是處理此類操作的理想方式,特殊方法transferTo()和transferFrom()則允許我們將一個通道和另一個通道直接相連
public class ChannelCopy { private static final int CAPACITY = 1024; public static void main(String[] args) throws IOException { if(args.length != 2)System.exit(1); FileChannel in = new FileInputStream(args[0]).getChannel(), out = new FileOutputStream(args[1]).getChannel(); in.transferTo(0,in.size(),out);//or out.transferFrom(in,0,in.size); out.close(); in.close(); } }
在GetChannel.java中,必須每次只讀取一個字節的數據,然后將每個byte類型強制轉換成char類型。而java.nio.CharBuffer有一個toString方法:返回一個包含緩沖器中所有字符的字符串。ByteBuffer可以看做是具有asCharBuffer()方法的CharBuffer。
public class BufferToText { private static final int CAPACITY = 1024; public static void main(String[] args) { try{ var fc = new FileOutputStream("data.txt").getChannel(); fc.write(ByteBuffer.wrap("來來,我是一個香蕉。。".getBytes())); fc.close(); fc = new FileInputStream("data.txt").getChannel(); var buff = ByteBuffer.allocate(CAPACITY); fc.read(buff); buff.flip(); System.out.println(buff.asCharBuffer()); buff.rewind();//返回到數據開始部分 var encoding = System.getProperty("file.encoding");//獲取默認字符集 System.out.println("使用" + encoding + "解碼結果: " + Charset.forName(encoding).decode(buff)); fc = new FileOutputStream("data.txt").getChannel(); fc.write(ByteBuffer.wrap("來來,我是一個榴蓮".getBytes("UTF-8"))); fc.close(); fc = new FileInputStream("data.txt").getChannel(); buff.clear(); fc.read(buff); buff.flip(); System.out.println(buff.asCharBuffer()); fc = new FileOutputStream("data.txt").getChannel(); buff = ByteBuffer.allocate(24); buff.asCharBuffer().put("如花貌美容顏"); fc.write(buff); fc.close(); fc = new FileInputStream("data.txt").getChannel(); buff.clear(); fc.read(buff); buff.flip(); System.out.println(buff.asCharBuffer()); }catch (IOException e) { e.printStackTrace(); } } }
緩沖器容納的是普通的字節,為了把它們轉換成字符,我們要么在輸入它們的時候對其進行編碼,要么在將其從緩沖器輸出時對他們進行解碼(可以使用java.nio.charset.Charset類實現這些功能).
盡管ByteBuffer只能保存字節類型數據,但是它可以從其所容納的字節中產生出各種不同的基本類型值的方法
CharBuffer ====> asCharBuffer();
ShorBuffer ====> asShortBuffer();
IntBuffer ====> asIntBuffer();
LongBuffer ====> asLongBuffer();
FloatBuffer ====> asFloatBuffer();
DoubleBuffer ====> asDoubleBuffer();
這些Buffer覆蓋了你能通過IO發送的基本數據類型:byte, short, int, long, float, double 和 char。
注意:使用shortBuffer的put()方法時,需要進行類型轉換。
視圖緩沖器(view buffer)可以讓我們通過某個特定的基本數據類型的視窗查看其底層的ByteBuffer。ByteBuffer依然是實際存儲數據的地方,“支持”著前面的視圖,因此對視圖的任何修改都會映射成為對ByteBuffer中數據的修改。
一旦底層的ByteBuffer通過視圖緩沖器填滿了整數或其他基本類型時,就可以直接被寫到通道中。正像從通道中讀取那樣容易,然后使用視圖緩沖器可以把任何數據都轉化為某一特定的基本類型。
下圖闡明了nio類之間的關系,便于我們理解怎么移動和轉換數據。如果想把一個字節數組寫到文件中去,那么就應該使用ByteBuffer.wrap()方法把字節數組包裝起來,然后用getChannel()方法在FileOutputStream上打開一個通道,接著將來自于ByteBuffer的數據寫到FileChannel。
注意:BytBuffer是將數據移進移出通道的唯一方式,并且只能創建一個獨立的基本類型緩沖器,或者使用as方法從ByteBuffer中獲得。也就是說,不能把基本類型的緩沖器轉換成ByteBuffer。然而,我們可以經由視圖緩沖器將基本類型數據移進移出Bytebuffer,所以也就不是什么真正的限制了。
Buffer有數據和可以高效地訪問及操作這些數據的四個索引組成,mark(標記)、position(位置)、limit(界限)和capacity(容量)。下面是用于設置和復位索引以及查詢它們的值的方法。
capacity() | 返回緩沖區容量 |
clear() | 清空緩存區,將position設置為0,limit設置為容量。可以調用此方法覆寫緩沖區 |
flip() | 將limit設置為position,position置位0.此方法用于準備從緩沖區讀取已經寫入的數據 |
limit() | 返回limit的值 |
limit(int lim) | 返回limit的值 |
mark() | 將mark設置為position |
position() | 返回position值 |
position(int pos) | 返回position值 |
remaining() | 返回(limit - position) |
hasRemaining() | 是否有介于position和limit之間的元素 |
在緩沖器中插入和提取數據的方法會更新這些索引,用于反應所發生的變化。
內存映射文件允許創建和修改因為太大而不能放入內存的文件。可以假定整個文件都放在內存中,而且可以完全把它當作非常大的數組訪問。
public class LargeMappedFiles { static int length = 0x8FFFFFF; // 128 MB public static void main(String[] args) throws Exception { MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel() .map(FileChannel.MapMode.READ_WRITE, 0, length); for (int i = 0; i < length; i++) { out.put((byte) 'x'); } print("Finished writing"); for (int i = length / 2; i < length / 2 + 6; i++) { printnb((char) out.get(i)); } } }
使用map()產生MappedByteBuffer,一個特殊類型的直接緩沖器,注意:必須指定映射文件初始位置和映射區域長度,意味著可以映射某個大文件的較小部分。
MappedByteBuffer由ByteBuffer繼承而來,因此它具有ByteBuffer的所有方法。
前面程序創建的文件為128MB,這可能比操作系統所允許一次載入內存的空間大。但似乎我們可以一次訪問到整個文件。因為只有一部分文件放入了內存,其他被交換了出去。用這種方式,很大的文件(可達2GB)也可以很容易地修改。注意底層操作系統的文件映射工具是用來最大化地提高性能的。
映射文件的所有輸出必須使用RandomAccessFile。
JDK 1.4引入了文件加鎖機制,允許同步訪問某個作為共享資源的文件。文件鎖對其他的操作系統進程是可見的,因為Java的文件加鎖直接映射到本地操作系統的加鎖工具。
public class FileLocking { public static void main(String[] args) throws Exception { FileOutputStream fos= new FileOutputStream("file.txt"); FileLock fl = fos.getChannel().tryLock(); if(fl != null) { System.out.println("Locked File"); TimeUnit.MILLISECONDS.sleep(100); fl.release(); System.out.println("Released Lock"); } fos.close(); } } /* Locked File Released Lock */
通過對FileChannel調用tryLock()或lock(),就可以獲得整個文件的FileLock。(SocketChannel、DatagramChannel和ServerSocketChannel不需要加鎖,因為它們是從單進程實體繼承而來,通常不在兩個進程之間共享socket。)tryLock()是非阻塞式的,它設法獲取鎖,如果不能得到(當其他一些進程已經持有相同的鎖,并且不共享時),它將直接從方法調用返回。lock()是阻塞式的,它要阻塞進程直至鎖可以獲得,或調用lock()的線程中斷,或調用lock()的通道關閉。使用FileLock.release()可以釋放鎖。
也可以使用如下方法對文件的一部分上鎖:
tryLock(long position, long size, boolean shared)
或者
lock(long position, long size, boolean shared)
其中加鎖區域由size-position決定,第三個參數指定是否共享鎖。
盡管無參的加鎖方法將根據文件尺寸變化而變化,但是具有固定尺寸的鎖不隨文件尺寸變化而變化。如果你獲得了某一區域(從position到position+size)上的鎖,當文件增大超出position+size時,那么在position+size之外的部分不會被鎖定。無參數的加鎖方法會對整個文件進行加鎖,甚至文件變大后也是如此。
對于獨占鎖或者共享鎖的支持必須有底層的操作系統提供。如操作系統不支持共享鎖并未每一個請求都創建鎖,那么它就會使用獨占鎖。鎖的類型可以通過FileLock.isShared()進行查詢。
對映射文件的部分加鎖
文件映射通常應用于極大的文件。我們可能需要對這種巨大的文件進行部分加鎖,以便其他進程可以修改文件中未被加鎖的部分。例如,數據庫就是這樣,因此多個用戶可以同時訪問到它。
public class LockingMappedFIles { static final int LENGTH = 0x8FFFFFF;//128M static FileChannel fc; public static void main(String[] args) throws IOException { fc = new RandomAccessFile("test.dat", "rw").getChannel(); MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH); for(int i = 0; i < LENGTH; i++)out.put((byte)'x'); new LockAndModify(out, 0, 0 + LENGTH / 3); new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4); } private static class LockAndModify extends Thread{ private ByteBuffer buff; private int start,end; public LockAndModify(ByteBuffer buff, int start, int end) { this.start = start; this.end = end; buff.limit(end); buff.position(start); this.buff = buff.slice(); } @Override public void run() { try { FileLock lock = fc.lock(start,end,false); System.out.println("Locked:" + start + "to" + end); while(buff.position() < buff.limit() - 1) buff.put((byte)(buff.get() + 1)); lock.release(); System.out.println("Released:" + start + "to" + end); } catch (IOException e) { e.printStackTrace(); } } } }
線程類LockAndModify創建了緩沖區和利于修改的slice(),然后再run中,獲得文件通道上的鎖(不能獲得緩沖器上的鎖,只能獲得通道上的)。lock()類似于獲得一個對象的線程鎖----現在處在臨界區,即對該部分文件具有獨占訪問權。
如果有java虛擬機,它會自動釋放鎖,或者關閉加鎖的通道。不過也可以像程序中那樣,顯式地為FileLock對象調用release()來釋放。
感謝各位的閱讀,以上就是“java的文件IO舉例分析”的內容了,經過本文的學習后,相信大家對java的文件IO舉例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。