1 File類別
#
File
(檔案)類別這個名字有一定的誤導性;我們可能會認為它指涉的是文件,實際上卻並非如此。它既能代表一個特定檔案的名稱,也能代表一個目錄下的一組檔案的 #名稱。實際上,FilePath
(#檔案路徑#)對這個類別來說是更好的名字。
如果它指的是一個檔案集,我們就可以對此集合呼叫list()
方法,這個方法會傳回一個字串陣列。
程式語言的I/O類別函式庫常使用流這個抽象概念,它代表任何有能力產出資料的資料來源物件或是有能力接收資料的接收物件。 「流」屏蔽了實際的I/O設備中處理資料的細節。
我們很少使用單一的類別來建立流對象,而是透過疊加多個物件來提供所期望的功能(這是裝飾器設計模式)。實際上,Java中的「流」類別庫讓人迷惑的主要原因就在於:創建單一的結果流,卻需要建立多個物件。
在Java 1.0中,類別庫的設計者首先限定與輸入有關的所有類別都應該從InputStream
#繼承,而與輸出有關的所有類別都應該從OutputStream
#繼承。
InputStream或Reader中的read()用於讀取單一位元組或位元組數組,OutputStream或Writer用於寫入單一位元組或位元組數組。
InputStream
的作用是用來表示那些從不同資料來源產生輸入資料的類別。 每一種資料來源都有對應的InputStream子類別。這些資料來源包括:
位元組陣列。 An array of bbytes.
String物件。 A String object.
檔。 A file.
「管道」(A pipe),工作方式與實際管道相似,即,從一端輸入,從另一端輸出。
一個由其他種類的流組成的序列,以便我們可以將它們收集合併到一個流內。 A sequence of other streams.
其他資料來源,如Internet連線等。 Other sources.
# OutputStream
類別決定了輸出要去的目標:
#位元組數組
檔案
#管道
FilterInputStream
和FilterOutputStream
是用來提供裝飾器類別介面以控制#特定 輸入流(InputStream)和輸出流(OutputStream)的兩個類,它們的名字並不直觀。這兩個類別是裝飾器的必要條件(以便能為所有正在被修飾的物件提供通用介面)。
InputStream
和OutputStream
面對位元組#形式的I/O提供函數
#Reader
和Writer
面向字元 (相容Unicode)形式的I/O功能
設計
#Reader
和Writer
繼承層次結構主要是為了 國際化。舊的I/O流繼承層次結構僅支援8
位元組流,並且不能很好地處理16
位元的Unicode字元。由於Unicode用於字元國際化(Java本身的char也是16位元的Unicode),所以新增Reader
和Writer
繼承層次結構就是為了在所有的I/O操作中都支援Unicode
。另外新類別庫的設計使得它的操作比舊類別庫更快。
#RandomAccessFile
適用於由大小已知的記錄組成的文件,所以我們可以使用seek()
將記錄從一處轉移到另一處,然後讀取或修改記錄。
RandomAccessFile
擁有和別的I/O類型本質不同的行為,因為我們可以在一個檔案 內向前和向後移動。在任何情況下,它都是自我獨立的,直接從Object派生而來。
本質上說,RandomAccessFile的工作方式類似於把DataInputStream和DataOutputStream組合起來使用,也加入了一些新方法:
# getFilePointer()
用來尋找目前所處的檔案位置,
seek()
用於在檔案內移至新的位置,
length()
用來判斷檔案的最大尺寸。
儘管可以透過不同的方式組合I/O流類,但我們可能也只用到其中的幾種組合。下面的例子可以作為典型的I/O用法的基本參考。
標準I /O這個術語參考的是Unix中「程式所使用的單一資訊流」這個概念。
標準I/O的意義在於:我們可以很容易地把程式串聯起來,一個程式的標準輸出可以成為另一個程式的標準輸入。
的速度的提升來自於所使用的結構更接近作業系統執行I/O的方式:#通道和緩衝器。我們可以把它想像成一個煤礦,通道是一個包含煤層(資料)的礦藏,而緩衝器則是派送到礦藏的卡車。卡車載滿煤炭而歸,我們再從卡車上取得煤炭。也就是說,我們並沒辦法有直接和通道交互,我們只是和緩衝器交互,並把緩衝器派送到通道。通道要么從緩衝器獲得數據,要么向緩衝器發送數據。
唯一直接與通道互動的緩衝器是
ByteBuffer
#——也就是說,可以儲存未加工位元組的緩衝器。當我們查詢JDK文件中的java.nio.ByteBuffer
時,會發現它是相當基礎的類別:透過告知分配多少儲存空間來建立一個ByteBuffer
對象,並且還有一個方法選擇集,用於以原始的位元組形式或基本資料類型輸出和讀取資料。但是,沒辦法輸出或讀取對象,即使是字串對像也不行。這種處理雖然很低級,但卻正好,因為這是大多數作業系統中更有效的映射方式。
FileChannel
是操縱位元組流的。舊I/O類別庫中有三個類別被修改了,用以產生FileChannel
:
#FileInputStream.getChannel()
FileOutputSteam.getChannel()
RandomAccessFile.getChannel()
package net.mrliuli.io.nio;import java.nio.*;import java.nio.channels.*;import java.io.*;public class GetChannel { private static final int BSIZE = 1024; public static void main(String[] args) throws Exception { // Write a file: FileChannel fc = new FileOutputStream("data.txt").getChannel(); fc.write(ByteBuffer.wrap("Some text ".getBytes())); /* ByteBuffer buffer = ByteBuffer.allocate(1024); fc.read(buffer); // NonReadableChannelException System.out.println((char)buffer.get()); */ fc.close(); // Add to the end of the file: fc = new RandomAccessFile("data.txt", "rw").getChannel(); // Readable and Writable fc.position(fc.size()); // Move to the end fc.write(ByteBuffer.wrap("some more".getBytes())); fc.close(); // Read the file: fc = new FileInputStream("data.txt").getChannel(); ByteBuffer buff = ByteBuffer.allocate(BSIZE); fc.read(buff); //fc.write(ByteBuffer.wrap("again".getBytes())); //NonWritableChannelException buff.flip(); while(buff.hasRemaining()) System.out.print((char)buff.get()); // ByteBuffer.get() returns a byte System.out.println(); } }
# 將位元組存放於緩衝器ByteBuffer的方式:
使用put()
直接填充,填入一個或多個位元組,或基本資料類型的值;
使用wrap()
將已存在的位元組陣列包裝到ByteBuffer中。
設有一個輸入通道in
、一個輸出通道out
和一個緩衝器buffer
:
#in.read(buffer);
將fc
中的位元組輸入buffer
,此時必須再調buffer.flip();
做好讓別人從buffer
讀取位元組的準備。
out.write(buffer)
#將buffer
中的位元組輸出到out
,write()
操作之後,訊息仍在緩衝器buffer
中,必須呼叫buffer.clear();
對所有的內部指標重新安排,以便緩衝器在另一個read()
操作期間能夠做好接受資料的準備。
package net.mrliuli.io.nio; import java.io.*; import java.nio.*; import java.nio.channels.*;public class ChannelCopy { private static final int BSIZE = 1024; public static void main(String[] args) throws Exception { if(args.length != 2){ System.out.println("arguments : sourcefile destfile"); System.exit(1); } // 打开一个FileChaanel用于读(输入) FileChannel in = new FileInputStream(args[0]).getChannel(); // 打开一个FileChannel用于写(输出) FileChannel out = new FileOutputStream(args[1]).getChannel(); // 一个缓冲器,分配了BSIZE个字节 ByteBuffer buffer = ByteBuffer.allocate(BSIZE); /* * return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream * FileChanel.read() * */ // -1 一个分界符(源于Unix和C),表示到达了输入的末尾 while(in.read(buffer) != -1){ buffer.flip(); // Prepare for writing out.write(buffer); // write()操作之后,信息仍在缓冲器中,clear()操作对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接受数据的准备。 buffer.clear(); // Prepare for reading } } }
缓冲器容纳的是普通的字节,为了把它们转换成字符,我们要么在输入它们的时候对其进行编码(这样,它们输出时才具有意义),要么在将其从缓冲器输出时对它们进行解码。可以使用
java.nio.charset.Charset
类实现这些功能,该类提供子把数据编码成多种不同类型的字符集的工具。The buffer contains plain bytes, and to turn these into characters, we must either encode them as we put them in (so that they will be meaningful when they come out) or decode them as they come out of the buffer. This can be accomplished using the java.nio.charset.Charset class, which provides tools for encoding into many different types of character set.
视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此对视图的任何修改都会映射成为对ByteBuffer中数据的修改。
文件加锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到了本地操作系统的加锁工具。
exclusive lock 独占锁
Locking portions of a mapped file 对映射文件的部分加锁
cretical section 临界区
Java的对象序列化将那些实现了
Serilizable
接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络朝廷这意味着序列化机制能自动弥补不同操作系统之间的差异。就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现轻量级持久性(ligthweight persistence)。持久性意味着一个对象的生存周期并不取决于程序是否正在执行,它可以生存于程序的调用之间。
对象序列化的概念加入到语言中是为了支持两种主要特性:
一是Java的远程方法调用(Remote Method Invocation, RMI),它使存活于其他计算机的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
再者,对Java Beans来说,对象的序列化也是必需的。使用一个Bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复;这种具体工作就是由对象序列化完成的。
序列化一个对象和反序列化:
首先要创建一个ObjectOutputStream
对象,要通过构造函数含有一个 OutputStream
对象。
然后,只需调用 void writeObject(Object obj)
,即可将对象obj
序列化,即转换成字节序列输出到第一步所说的Outputstream
。
反序列化,即将字节序列还原为一个对象,则只需调用ObjectInputStream
的Object readObject()
,输入到一个InputStream
。
例:
Worm.java
反序列,即将字节序列还原为对象时,必须保证Java虚拟机能够找到要还原的对象的相关.class
文件,否则抛出java.lang.ClassNotFoundException
异常。
如果只希望一个对象的某些信息序列化而某些信息不序列化,即进行序列化控制,可使用Externalizable
接口。
Externalizable
接口继承自Serializable
接口,有两个方法如下,这两个方法会在序列化和反序列化过程中被自动调用。
void writeExternal(ObjectOutput obj)
,在该方法内部只对所需部分进行显式序列化。
void readExternal(ObjectInput in)
Externalizable
只序列化writeExternal()
中的部分,而Serializable
自动地全部序列化。
Externalizable
在反序列化时(即调用readObject()
时),会首先调用所有普通的默认构造器,然后调用readExternal()
。
Serializable
在反序列化时,对象完全以它存储的二进制位为基础来构造,而不用调用构造器。
例:
Blips.javaBlip3.java
transient
(瞬时)关键字如果我们正操作的是一个Serializable对象,那么所有序列化操作都会自动进行。为了能够予以控制,可以用
transient
(瞬时)关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据——我会自己处理的”。由于
Externalizable
对象在默认情况下不保存任何字段,所以transient
关键字只能和Serializable
对象一起使用。
我们可以通过一个字节数组来使用对象序列化,从而实现对任何可Serializable对象的“深度复制”(deep copy)——深度复制意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。
一个对象被序列化在单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。
一个对象被序列化在不同流中,再从不同流恢复时,得到的对象地址不同。
例:
MyWorld.java
对象序列化的一个重要限制是它只是Java的解决方案:只有Java程序才能反序列化这种对象。一种更具互操作性的解决方案是将数据转换为XML格式,这可以使其被各种各样的平台语言使用。
相关文章:
以上是Java程式設計思想學習課程(五)第18章-Java IO系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!