轉自
開發者的天空
本文中我們來討論在
NIO2中怎樣
創建文件、讀取文件和寫文件。NIO2提供了多種創建
文件的方法,使得我們在創建文件的時候就可以指定文件的某些初始屬性。例如在支持POSIX的文件系統上指定文件的所有者,訪問權限等。關于文件的屬性,
請看上一篇文章
Java SE 7新特性之文件操作(5) - 管理元數據
創建文件
可以調用createFile(FileAttribute<?>)方法創建一個空文件。該方法的參數就是文件的初始屬性。下面的例子是怎樣
在創建文件的時候賦予該文件某些權限的屬性:
Path sourceFile =
;
Path newFile =
;
PosixFileAttributes attrs = Attributes.readPosixFileAttributes(sourceFile);
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(attrs.permissions());
try {
file.createFile(attr);
} catch (IOException x) {
//unable to create the file
}
如
果在調用該方法的時候沒有傳入任何參數,那么創建的文件將具有缺省的文件屬性。下面的代碼創建了一個具有缺省文件屬性的文件:
Path file =
;
try {
file.createFile(); //Create the empty file with default permissions, etc.
} catch (FileAlreadyExists x) {
System.err.format("file named %s already exists%n", file);
} catch (IOException x) {
//Some other sort of failure, such as permissions.
System.err.format("createFile error: %s%n", x);
}
如
果要創建的文件已經存在,該方法會拋出異常。
注意在調用createFile方法時,檢查文件是否存在和創建具有特定的屬性的文件是在同一個原子操作中。
還可以使用newOutputSteam方法來創建文件,在本文的后面我們會講到怎樣使用newOutputStream方法來創建文件。
通過Stream I/O讀文件
我們可以通過newInputStream(OpenOption...)方法打開和讀取文件。這個方法返回一個unbuffered輸入流(input
stream),我們可以用它來從文件中讀取字節內容。
Path file =
;
InputStream in = null;
try {
in = file.newInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException x) {
System.err.println(x);
} finally {
if (in != null) in.close();
}
注
意該方法接受可變個數的參數,參數類型為OpenOption,指定了文件怎樣打開。如果不傳入參數,則使用默認的READ方式打開。READ方式是所有
的實現都支持的方式。有一些實現也支持其他的打開方式。
如果傳入的OpenOption或其組合不正確,會拋出異常。如果程序沒有讀權限或I/O錯誤,也會拋出異常。
Creating and Writing a File by Using Stream I/O
使用Stream I/O來創建和寫文件
我們可以使用newOutputStream方法來創建文件、擴展文件或覆蓋已有文件。這個方法為了寫文件而打開或創建文件,該方法返回一個
unbuffered的輸出流(output stream)。newOutputStream方法有兩種形式:
- newOutputStream(OpenOption...)
- newOutputStream(Set<? extends OpenOption>,
FileAttribute<?>...)
這兩種形式都接受一組OpenOption作為參數,第二種形式還允許指定初始的文件屬性。這個方法支持的StandardOpenOption有:
- WRITE –為了寫訪問而打開該文件.
- APPEND – 將新數據擴展在文件的末尾。該選項和WRITE或CREATE選項一起使用。
- TRUNCATE_EXISTING – 將文件截斷為0字節長. 該選項和WRITE選項一起使用來覆蓋原有文件。
- CREATE_NEW – 創建一個新的文件。如果原來的文件存在這拋出異常。
- CREATE – 如果原文件存在這打開它,否則創建新的文件。
- DELETE_ON_CLOSE – 當Stream關閉時刪除該文件。這個選項對臨時文件比較有用。
- SPARSE – 表明新創建的文件是Sparse文件. 關于Sparse文件的具體信息請看http://space.itpub.net/8242091/viewspace-619756。
- SYNC – 保持該文件(包括內容和元數據)與底層存儲設備同步。
- DSYNC – 保持文件內容與底層存儲設備同步。
如果沒有指定OpenOption,該方法的行為是:如果文件不存在,則創建新文件;如果文件存在,那么截斷它。也就是說缺省的選擇是CREATE和
TRUNCATE_EXISTING選項的組合。
下面的代碼打開一個
日志文件,如果文件不存在,則創建一個新文件。如果文件
存在,這將新的內容擴展到文件尾部。
import static java.nio.file.StandardOpenOption.*;
Path logfile =
;
//Convert the string to a byte array.
String s =
;
byte data[] = s.getBytes();
OutputStream out = null;
try {
out = new BufferedOutputStream(logfile.newOutputStream(CREATE, APPEND));

out.write(data, 0, data.length);
} catch (IOException x) {
System.err.println(x);
} finally {
if (out != null) {
out.flush();
out.close();
}
}
使用Channel I/O來讀寫文件
Stream I/O每次讀取一個字符,Channel
I/O每次讀取一個緩沖塊的數據。ByteChannel接口提供了基本的讀寫功能。SeekableByteChannel擴展了
ByteChannel并提供了維護一個channel中的位置并改變該位置的能力。SeekableByteChannel還支持截斷文件和查詢文件大
小的功能。
移動到文件中不同的位置,從該位置開始讀或寫的能力使我們可以
隨機訪問文件。有兩種形式的
newByteChannel方法可以用來讀或寫文件,這兩種形式和newOutputStream方法一樣。
- newByteChannel(OpenOption...)
- newByteChannel(Set<? extends OpenOption>,
FileAttribute<?>...)
這兩個方法都允許指定OpenOption,newOutputStream所支持的選擇這里也支持,而且這里還支持另外一個選項READ,因為
SeekableByteChannel既支持讀也支持寫。
如果選項是READ,那么該channel就是為了讀訪問打開。如果選項是WRITE或APPEND,則該channel就是為了寫訪問打開。如果沒有指
定,該channel默認是為了讀打開。
下面的代碼從文件中讀取內容并輸出到控制臺上:
SeekableByteChannel sbc = null;
try {
sbc = file.newByteChannel(); //Defaults to READ
ByteBuffer buf = ByteBuffer.allocate(10);
//Read the bytes with the proper encoding for this platform.
//If you skip this step, you might see something that looks like Chinese
//characters when you expect Latin-style characters.
String encoding = System.getProperty("file.encoding");
while (sbc.read(buf) > 0) {
buf.rewind();
System.out.print(Charset.forName(encoding).decode(buf));
buf.flip();
}
} catch (IOException x) {
System.out.println("caught exception: " + x);
} finally {
if (sbc != null) sbc.close();
}
下
面的代碼是為了UNIX或其他支持POSIX的文件系統編寫的。這段代碼創建一個新的日志文件或者擴展原有的日志文件,該日志文件創建時指定了訪問權限
(所有者有讀寫權限,同組用戶只有讀權限,其他用戶沒有讀權限)。
import static java.nio.file.StandardCopyOption.*;
//Create the set of options for appending to the file.
Set<OpenOptions> options = new HashSet<OpenOption>();
options.add(APPEND);
options.add(CREATE);
//Create the custom permissions attribute.
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r------");
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
//Convert the string to a ByetBuffer.
String s =
;
byte data[] = s.getBytes();
ByteBuffer bb = ByteBuffer.wrap(data);
SeekableByteChannel sbc = null;
try {
sbc = file.newByteChannel(options, attr);
sbc.write(bb);
} catch (IOException x) {
System.out.println("exception thrown: " + x);
} finally {
if (sbc != null) sbc.close();
}
轉自
開發者的天空
管理元數據(文件屬性和文件存儲屬性)
在文件系統中,文件或者目錄的元數據是和文件或者目錄本身存儲在一起的,而且元數據保存了很多的信息,例如:
對象是文件還是目錄,抑或是符號鏈接。文件的大小、創建
時間、最后修改時間、文件的所有者、組、訪問權限等。
java.nio.file.attribute包提供了訪問和管理文件系統元數據(通常叫做文件屬性)的功能。不同的文件系統提供的文件屬性是不一樣
的,所以我們按照這個將文件的屬性劃分成了不同的視圖(View)。每個View對應一個文件系統的實現,如POSIX(Unix使用的文件系統)或
DOS;或者是所有文件系統共有的屬性,如文件的所有權。當我們讀取文件的屬性的時候,是一次讀取整個視圖的屬性的。
Java所支持的視圖有:
BasicFileAttributeView – 所有的文件系統實現都必須提供的屬性的視圖。
DosFileAttributeView – 擴展了BasicFileAttributeView,添加了DOS文件系統能夠支持的幾種屬性。
PosixFileAttributeView –
擴展了BasicFileAttributeView,添加了POSIX文件系統能夠支持的幾種屬性。
FileOwnerAttributeView – 任何文件系統實現都會支持文件所有者的屬性。這個View就包含這個屬性。
AclFileAttributeView –
支持讀取和更新文件的訪問控制列表。支持NFSv4訪問控制列表模型。所有定義了到NFSv4的映射的訪問控制列表模型也同樣支持,如Windows的訪
問控制列表模型。
UserDefinedFileAttributeView –
支持用戶自定義的元數據。這個View可以被映射到文件系統提供的擴展機制。例如在Solaris操作系統上,可以將這個View存儲MIMEtype。
不同的文件系統可能支持不同的一個或幾個view,也有可能含有上面所有的View都沒有包含的文件屬性。
Attributes類
大多數情況下,我們不需要直接使用FileAttributeView接口,Attributes類提供了轉換的方法來讀取和設置文件屬性。
下面是Attributes類提供的一些方法:
讀取或設置基本的文件屬性:
readBasicFileAttributes(FileRef, LinkOption...)
setLastAccessTime(FileRef, FileTime)
setLastModifiedTime(FileRef, FileTime)
讀取或設置POSIX文件屬性
readPosixFileAttributes(FileRef, LinkOption...)
setPosixFilePermissions(FileRef, Set<PosixFilePermission>)
讀取或設置文件的所有者
getOwner(FileRef)
setOwner(FileRef, UserPrincipal)
一次讀取所有的文件屬性
readAttributes(FileRef, String, LinkOption...)
讀取或設定特定的文件屬性
getAttribute(FileRef, String, LinkOption...)
setAttribute(FileRef, String, Object)
讀取DOS文件屬性
readDosFileAttributes(FileRef, LinkOption...)
讀取或設置文件的訪問控制列表。
getAcl(FileRef)
setAcl(FileRef, List<AclEntry>)
讀取文件存儲空間的屬性,如總空間、有效空間、未分配空間等。
readFileStoreSpaceAttributes(FileStore)
每個read方法都返回一個對象,該對象會提供訪問方法,我們通過這些訪問方法能夠很方便的獲得特定的文件屬性的值。
要想讀取基本的文件信息,那么可以調用readBasicFileAttributes(FileRef,
LinkOption...)方法。這個方法支持的LinkOption就只有NOFOLLOW_LINKS。這個方法會一次性的讀取所有的基本的文件屬
性并返回一個BasicFileAttributes對象,我們可以訪問該對象獲取具體的文件屬性。如果程序對文件沒有訪問權限,該方法會拋出
SecurityException異常。要注意的是,并不是所有文件系統的實現都支持創建時間、最后修改時間和最后訪問時間這三個屬性。如果某個屬性不
被支持,則調用該屬性的get方法時會返回null。下面就是一個讀取文件的基本屬性的例子:
Path file =
;
BasicFileAttributes attr = Attributes.readBasicFileAttributes(file);
if (attr.creationTime() != null) {
System.out.println("creationTime: " + attr.creationTime());
}
if (attr.lastAccessTime() != null) {
System.out.println("lastAccessTime: " + attr.lastAccessTime());
}
if (attr.lastModifiedTime() != null) {
System.out.println("lastModifiedTime: " + attr.lastModifiedTime());
}
System.out.println("isDirectory: " + attr.isDirectory());
System.out.println("isOther: " + attr.isOther());
System.out.println("isRegularFile: " + attr.isRegularFile());
System.out.println("isSymbolicLink: " + attr.isSymbolicLink());
System.out.println("size: " + attr.size());
下面的例子中,我們檢查了對一個文件的訪問權限,判斷
該文件是常規的文件還是目錄:
import static java.nio.file.AccessMode.*;
Path file =
;
boolean error=false;
try {
file.checkAccess(READ, EXECUTE);
if (!Attributes.readBasicFileAttributes(file).isRegularFile()) {
error = true;
}
} catch (IOException x) {
//Logic for error condition
error = true;
}
if (error) {
//Logic for failure
return;
}
//Logic for executable file
設置
時間戳
前面的文件基本屬性的代碼中演示了怎樣獲取文件的時間戳,Attributes類還提供了兩個方法來設置時間戳:setLastAccessTime和
setLastModifiedTime,下面是這兩個方法的示例:
Path file =
;
BasicFileAttributes attr = Attributes.readBasicFileAttributes(file);
long currentTime = System.currentTimeMillis();
if (attr.lastModifiedTime() != null) {
FileTime ft = FileTime.fromMillis(currentTime);
Attributes.setLastModifiedTime(file, ft);
} else {
System.err.println("lastModifiedTime time stamp not supported");
}
DOS的文件屬性
要獲取一個文件的DOS的文件屬性,需要調用readDosFileAttributes方法。這個方法會返回一個DosFileAttributes對
象,該對象提供了獲取DOS文件屬性的方法,例如:
Path file =
;
try {
DosFileAttributes attr = Attributes.readDosFileAttributes(file);
System.out.println("isReadOnly is " + attr.isReadOnly());
System.out.println("isHidden is " + attr.isHidden());
System.out.println("isArchive is " + attr.isArchive());
System.out.println("isSystem is " + attr.isSystem());
} catch (IOException x) {
System.err.println("DOS file attributes not supported:" + x);
}
我
們可以使用setAttribute方法來設置DOS文件屬性,如:
Path file =
;
Attributes.setAttribute(file, "dos:hidden", true);
要注意的是,不是只有DOS操作系統才支持DOS文
件屬性,有些操作系統如Samba也支持DOS文件屬性。
POSIX的文件屬性
POSIX是Portable Operation System Interface for
UNIX的縮寫,而且IEEE和ISO定義很多標準來保證不同的UNIX之間的戶操作性,因此對于開發人員來說,針對POSIX編寫的程序能夠很容易的運
行在不同的兼容POSIX的文件系統上。
要讀取POSIX文件屬性,需要調用readPosixFileAttributes方法。除了文件所有者和所屬組,POSIX還支持9種文件權限許可組
合:讀、寫、執行三種權限和文件所有者、同組的用戶和其他用戶三種角色的組合(3 × 3 = 9)。下面就是讀取POSIX文件屬性的簡單的例子:
Path file =
;
PosixFileAttributes attr = Attributes.readPosixFileAttributes(file);
System.out.format("%s %s %s%n", attr.owner().getName, attr.group().getName(),
PosixFilePermissions.toString(attr.permissions()));
下面的代碼讀取了一個文件的屬性,然后創建了一個新的
文件,將原有的文件的權限屬性賦予新創建的文件:
Path sourceFile =
;
Path newFile =
;
PosixFileAttributes attrs = Attributes.readPosixFileAttributes(sourceFile);
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(attrs.permissions());
try {
file.createFile(attr);
} catch (IOException x) {
//unable to create the file
}
上
面的代碼中我們使用了PosixFilePermission類,該類是一個幫助類,提供了一些方法來讀取和生成文件權限,這里就不詳細解釋了。
如果想指定創建的文件的權限,可以使用下面的代碼:
Path file =
;
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
try {
Attributes.setPosixFilePermissions(file, perms);
} catch (IOException x) {
System.err.println(x);
}
文
件有所有者的屬性和所屬組的屬性,在設置這些屬性的時候,我們需要傳入一個UserPrincipal對象作為參數,我們可以使用
UserPrincipalLookupService來根據用戶名或組名來創建該對象。UserPrincipalLookupService實例可以
通過FileSystem.getUserPrincipalLookupService方法獲得。下面就是設置所有者屬性的例子:
Path file =
;
try {
UserPrincipal owner = file.GetFileSystem().getUserPrincipalLookupService()
.lookupPrincipalByName("sally");
Attributes.setOwner(file, owner);
} catch (IOException x) {
System.err.println(x);
}
Attributes
類沒有提供設置所屬組的方法,如果要設置所屬組,需要調用POSIX文件屬性視圖來進行,下面是示例代碼:
Path file =
;
try {
GroupPrincipal group = file.getFileSystem().getUserPrincipalLookupService()
.lookupPrincipalByGroupName("green");
file.getFileAttributeView(PosixFileAttributeView.class).setGroup(group);
} catch (IOException x) {
System.err.println(x);
}
用戶定義的文件屬性
如果文件系統支持的屬性對你來說還不夠用,你可以通過UserDefinedAttributeView來創建和跟蹤自己的文件屬性。
下面的例子中,我們將MIME類型作為一個用戶自定義的屬性:
Path file =
;
UserDefinedFileAttributeView view = file
.getFileAttributeView(UserDefinedFileAttributeView.class);
view.write("user.mimetype", Charset.defaultCharset().encode("text/html");
要讀取MIME類型屬性,要使用以下的代碼:
Path file =
;
UserDefinedFileAttributeView view = file
.getFileAttributeView(UserDefinedFileAttributeView.class);
String name = "user.mimetype";
ByteBuffer buf = ByteBuffer.allocate(view.size(name));
view.read(name, buf);
buf.flip();
String value = Charset.defaultCharset().decode(buf).toString();
如果文件系統不支持擴展屬性,那么會拋出一個
UnsupportedOperationException異常,你可以咨詢系統管理員來確認系統是否支持文件的擴展屬性并進行相應的配置。
文件存儲屬性
文件存儲屬性其實我們應該非常熟悉的屬性,我們查看硬盤屬性的時候,經??吹接脖P總的存儲空間,使用了的存儲空間,空閑的存儲空間等就是文件存儲屬性。下
面是獲取文件存儲屬性的代碼:
Path file =
;
FileStore store = file.getFileStore();
FileStoreSpaceAttributes attr = Attributes.readFileStoreSpaceAttributes(store);
System.out.println("total: " + attr.totalSpace() );
System.out.println("used: " + (attr.totalSpace() - attr.unallocatedSpace()) );
System.out.println("avail: " + attr.usableSpace() );
轉自開發者的天空
刪除操作
通過Path類,我們可以刪除文件、目錄或符號鏈接。要注意的是當我們刪除符號鏈接時,其指向的目的目錄或文件不會被刪除。當要刪除一個目錄時,該目錄必須為空,否則會失敗。
Path類提供了兩個刪除方法。第一個是delete方法。Delete方法會直接刪除文件或目錄,如果刪除失敗則會拋出異常。例如如果要刪除的文件不存在,則會拋出NoSuchFileException。程序員可以catch這些異常并進行相應的處理。
try {
path.delete();
} catch (NoSuchFileException x) {
System.err.format("%s: no such file or directory%n", path);
} catch (DirectoryNotEmptyException x) {
System.err.format("%s not empty%n", path);
} catch (IOException x) {
//File permission problems are caught here.
System.err.println(x);
}
另外一個方法是deleteIfExists。這個方法同樣會刪除文件或目錄,和delete方法唯一不同的是如果文件不存在,這個方法不會拋出異常。
拷貝操作
Path類提供了拷貝文件或目錄的方法,就是copyTo方法。(以前要copy文件只能夠自己寫程序完成哦?。T谶M行拷貝的時候,我們可以指定如果目標文件或目錄已經存在時怎么處理;如果設置了REPLACE_EXISTING,則會覆蓋已有的文件或目錄;如果沒有設置
REPLACE_EXISTING,那么拷貝操作會失敗。
要注意的是拷貝目錄時,目錄中的內容并沒有被拷貝過去,新生成的目錄會是一個空目錄。要想將目錄中的內容一起拷貝過去,只有自己編程了。
在拷貝符號鏈接時,默認的行為是拷貝符號鏈接指向的目的文件或目錄。如果需要拷貝符號鏈接本身,需要指定NOFOLLOW_LINKS或
REPLACE_EXISTING選項。
CopyTo方法接受CopyOption類型的varargs。CopyOption是一個接口,目前有兩個實現類:StandardCopyOption和LinkOption。CopyTo方法能夠支持的選項有:
* REPLACE_EXISTING –
當要拷貝的是文件是,如果目標文件已經存在,則覆蓋目標文件。如果要拷貝的是目錄,當目標目錄已經存在時,如果目標目錄為空,覆蓋目標目錄;如果目標目錄不為空,拋出FileAlreadyExistsException。如果要拷貝的是符號鏈接,那么拷貝符號鏈接本身。
* COPY_ATTRIBUTES –
連文件或目錄的屬性一起拷貝。不同的文件系統和平臺支持不同的文件屬性,但是所有的文件系統和平臺都支持最后修改時間這個屬性。
* NOFOLLOW_LINKS – 如果要拷貝的是符號鏈接,直接拷貝符號鏈接本身。
下面是使用copyTo的代碼例子:
import static java.nio.file.StandardCopyOption.*;

try {
path.copyTo(newPath, REPLACE_EXISTING, COPY_ATTRIBUTES);
} catch (IOException x) {
//Logic for error condition
System.err.println(x);
return;
}
移動操作
Path還提供了moveTo方法來移動文件或目錄。如果沒有設置REPLACE_EXISTING選項,那么當目標文件或目錄存在時,操作會失敗。
空目錄能夠被移動,但是如果目錄不為空,是否能夠移動目錄要取決于是否能夠不移動目錄的內容。例如在Windows系統下,如果是同一個硬盤分區內的移動,就可以成功,但是如果是不同硬盤分區之間的移動,就會失敗,會拋出FileSystemException異常。同時要注意的是,目的目錄的父目錄一定要存在,否則會拋出NoSuchFileException。例如將c:"temp"test移動到c:"save"test,如果c:"save目錄不存在,則會拋出異常。
MoveTo方法也接受可變數量的參數,其支持的選項有:
REPLACE_EXISTING
– 覆蓋已經存在的文件或目錄。如果目標文件/目錄是一個符號鏈接,那么該鏈接會被覆蓋,但是起指向的文件或目錄不會受到影響。
* ATOMIC_MOVE –
移動操作是一個原子操作。如果文件系統不支持移動的原子操作,會拋出異常。原子操作能夠保證當你移動一個文件到一個目錄中時,監視該目錄的進程得到的是一個完整的文件。
下面是使用moveTo方法的例子
import static java.nio.file.StandardCopyOption.*;

try {
path.moveTo(newPath, REPLACE_EXISTING);
} catch (IOException x) {
// Logic for error condition
System.err.println(x);
return;
}