转自
开发者的天空
本文中我们来讨论在
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异常,你可以咨询系统管理员来确认系统是否支持文件的扩展属性并进行相应的配置。
文件存储属性
文件存储属性其实我们应该非常熟悉的属性,我们查看硬盘属性的时候,经常看到硬盘总的存储空间,使用了的存储空间,空闲的存储空间等就是文件存储属性。下
面是获取文件存储属性的代码:
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文件只能够自己写程序完成哦!)。在进行拷贝的时候,我们可以指定如果目标文件或目录已经存在时怎么处理;如果设置了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;
}
转自
开发者的天空
Path类提供了很多方法来对文件和目录进行读、写和其他的操作。在看这些方法之前,我们先需要了解一些其他的概念:
Varargs实际上是Variable number of arguments的缩写,也就是可变数目的参数。例如在下面的方法声明中,CopyOption参数后面的省略号表明这个方法接受可变个数的参数。
Path moveTo(Path, CopyOption...)
当一个方法可以接受可变数目的参数时,你可以传入以逗号分隔的多个参数,或者传入一个数组。
对于上面的moveTo方法,可以这样调用:
import static java.nio.file.StandardCopyOption.*;
Path orig = ;
Path new = ;
orig.moveTo(new, REPLACE_EXISTING, ATOMIC_MOVE);
Path的很多方法在文件系统上执行的操作都是原子操作,例如moveTo方法。原子操作是指不会被中断或不会部分执行的操作。操作要么是完全成功,要么是完全失败。当有多个进程操作同文件系统的相同的区域的时候这一点就很重要。
很多的文件I/O方法支持方法链的概念。
调用第一个方法会返回一个对象,我们可以直接调用这个对象的方法,这个方法依然返回一个对象,我们又可以直接调用该对象的方法,就这样持续下去。例如:
String value = Charset.defaultCharset().decode(buf).toString();
UserPrincipal group = file.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByName("me");
这个技术能够使我们编写更加紧凑的代码,避免声明一些我们不需要的临时变量。
Path类实现了FileRef接口。FileRef接口包含了定位文件和访问文件的方法。
Path类有两个方法能够接受带模式匹配的参数。下面是这种参数的规则:
星号*匹配任意数目的字符(也可能是没有)
两个星号**同样是代表任意数目的字符,不同的是这个匹配可以穿越目录边界。例如 c:"a**"bar可以匹配c:"abc"bar,也可以匹配c:"am"cn"bar。
问号?匹配一个字符
花括号{}表明子模式的集合,例如{sun,moon,stars}可以匹配'sun','moon'或‘stars’;{temp*,tmp*}可以匹配以temp或tmp开始的任意字符串。
要匹配*,?或其他的特殊字符,可以使用转义符"。例如""匹配单个的","?匹配问号。
方括号[]表示一组单独的字符,当使用了-的时候,也代表一个范围的字符,例如:
[aeiou]匹配任何单独的元音字符,[0-9]匹配任意数字,[A-Z]匹配任意大写字母,[a-z,A-Z]匹配任意大写或小写字母。在方括号中,星号、问号和"都只是表示它们自身,不再作为特殊符号。
下面是一些例子:
*.html匹配所有以.html结尾的字符串。
???匹配所有长度为3的字符串
*[0-9]*匹配所有包含有数字的字符串
*.{html,htm,pdf}匹配所有以.html,.html或.pdf结尾的字符串
a?*.java匹配所有以a开头,后面跟至少一个字符,然后以.java结尾的字符串。
{foo*,*[0-9]*}匹配以foo开头的字符串或包含有数字的字符串。
关于参数中的模式的使用,请参考FileSystem类的getPathMatcher方法的帮助文档。
如果这种模式匹配仍然不能够满足需要,我们还可以使用正则表达式。
Path会指向文件或者目录,但是我们还不能确定这个文件或者目录是否存在,是否可读,是否可写,是否可以执行。要确定文件/目录是否存在以及程序是否可以访问该文件/目录,可以使用checkAccess(AccessMode...)方法。可选的AccessMode有:
* READ – 检查文件/目录是否存在以及程序是否有权限读该文件/目录
* WRITE – 检查文件/目录是否存在以及程序是否有权限写该文件/目录
* EXECUTE – 检查文件/目录是否存在以及程序在该文件/目录上是否有执行权限
如果调用checkAccess的时候没有传入任何参数,该方法只是检查文件是否存在。
下面的例子演示了怎样验证文件是否存在以及程序是否有读和执行的权限。
import static java.nio.file.AccessMode.*;
Path file = ...;
try {
file.checkAccess(READ, EXECUTE);
} catch (IOException x) {
//Logic for error condition...
return;
}
//Logic for executable file...
需要注意的是,当checkAccess执行完之后,文件的权限可能被其他的用户(例如系统管理员)修改。这实际上是很多应用程序都有的安全性的问题。如果你感兴趣,可以搜索TOCTTOU (time of check to time of use)。
当文件系统中存在符号链接的时候,可能两个不同的路径会指向同一个文件或目录。方法isSamePath会比较两个Path来检查它们是否指向同一个文件/目录。
Path p1 = ...;
Path p2 = ...;
try {
if (p1.isSameFile(p2)) {
//Logic when the paths locate the same file
}
} catch (IOException x) {
//Logic for error condition...
return;
}
转自开发者的天空
在上面的一篇文章中,我们已经说过了
Path类的操作主要有两种:对路径的操作和对文件的操
作。这篇文章中我们就来了解一下对路径的操作。
创建Path实例
Path实例包含了指定文件或目录位置的信息,在实例化Path类时,需要指定一个或多个目录或文件名。路径的根目录不是必须的;路径信息可能仅仅是一个
目录或文件的名称。
最简单的创建Path实例的方式就是使用Paths(注意这里有一个s)类的get方法:
Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get("file:///Users/joe/FileTest.java");
Path类接受String或URI作为参数。
获取路径信息
前面我们已经说过了,File
System一般是树形结构,因此我们可以把Path理解为按顺序存储的一系列的名称(目录名称和文件名称)。目录结构中最高一层的目录名就是序列中
index为0的那一个,目录结构中最低一层的目录名或者文件名就是序列中index为n-1的那一个(这里n是路径中层次的数目)。Path类提供方法
来通过index获取序列中的一个元素或一个子序列。
随后的例子中我们使用的目录结构如下图:
下面的代码定义了一个Path对象并获取其中的信息。要注意的是这些代码中除了isHidden方法外,其他的方法并不需要指定的目录或文件存在;如果不
存在,isHidden方法会抛出异常。
Path path = Paths.get("C:\\home\\joe\\foo"); // Microsoft Windows syntax
//Path path = Paths.get("/home/joe/foo"); // Solaris syntax
System.out.format("toString: %s%n", path.toString());
System.out.format("getName: %s%n", path.getName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %d%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());
System.out.format("isHidden: %s%n", path.isHidden());
下面是这段代码的输出情况
方法 |
Solaris下的输出 |
Windows下的
输出 |
备注 |
toString |
/home/joe/foo |
C:\home\joe\foo |
|
getName |
foo |
foo |
获
取名称序列中的最后一个,也就是最底层的目录或文件名 |
getName(0) |
home |
home |
获
取名称序列中的第一个,也就是最靠近根目录的那一层。注意根目录不在名称序列中 |
getNameCount |
3 |
3 |
获
取名称序列的元素个数 |
subpath(0,2) |
home/joe |
home\joe |
获
取从指定的开始点到指定的结束点的子路径。注意这里包括开始点,但不包括结束点。 |
getParent |
/home/joe |
\home\joe |
返
回Path指定的目录或文件的父目录 |
getRoot |
/ |
C:\ |
返
回根目录 |
isHidden |
false |
false |
如果文件是
隐藏文件,或者目录是隐藏目录,返回true。因为要访问文件的属性,所以如果Path指定的目录或者文件不存在,会抛出异常。 |
上面的代码中我们创建Path时使用的是绝对路径,下面我们来看看创建路径时使用相对路径时,这段代码的执行结果
//Path path = Paths.get("sally/bar"); // Solaris syntax
Path path = Paths.get("sally\\bar"); // Microsoft Windows syntax
大
家可以自行去实验一下具体的输出是什么。
去除Path中的冗余
在很多文件系统中我们使用'.'来代表当前目录,使用'..'代表父目录。在有些情况下我们创建的路径中会有冗余的路径信息,例如:
/home/./joe/foo
/home/sally/../joe/foo
方法normalize会去除这些冗余信息,包括'.'或'directory/..'。上面的两个例子在去除冗余信息后都是/home/joe
/foo。
要注意的是normalize方法并不去检查文件系统,它只是简单的进行语法操作。在第二个例子中,如果sally是一个指向其他的目录的符号链接,那么
去除了sally/..后可能导致Path不在指向原来的文件或目录。
如果你需要清除冗余信息,又要保证结果仍然指向正确的文件或目录,可以使用toRealPath方法。在下面我们会讲到这个方法。
转换Path
有3个方法用来转换Path。
- toUri方法
如果你需要将Path转换为可以在浏览器中打开的字符串格式,可以使用toUri方法,例如:
Path p1 = Paths.get("/home/logfile");
System.out.format("%s%n", p1.toUri()); // 结果是 file:///home/logfile
注意在这里即使/home/logfile'指向的目录或文件不存在,这段代码同样能够执行成功。
同样的,toAbsolutePath方法并不需要
Path所指向的文件或目录存在。
- toRealPath方法
这个方法会返回一个已经存在的文件或目录的真实路径(如果文件或目录不存在或无法访问,该方法会抛出异常)。该方法会执行以下的操作:
如果传入的参数是true并且文件系统支持符号链接,则解析路径中存在的符号链接(如果有的话)。
如果原来的Path是相对路径,将其转换成绝对路径。
如果路径中含有冗余信息,返回的Path中这些冗余信息会被去除。
连接两个Path
可以使用resolve方法来将两个Path连接起来。该方法的参数是一个字符串。如果该字符串代表的是一个相对路径,那么这个路径会被扩展到原来的路径
后。如果传入的字符串是一个绝对路径,那么返回的值就是传入的这个绝对路径。例如:
Path p1 = Paths.get("C:\\home\\joe\\foo");
System.out.format("%s%n", p1.resolve("bar")); // 结果是 C:\home\joe\foo\bar
Paths.get("foo").resolve("c:\\home\joe"); // 结果是 C:\home\joe
创建两个路径之间的路径
这个功能说起来有些绕口,实际的功能就是创建两个指定的目录或文件之间的相对路径。例如:
Path p1 = Paths.get("joe/foo");
Path p2 = Paths.get("sally");
在这个例子中,由于两个路径都是相对路径,没有其他的
信息,我们会认为这两个joe和sally是同一级的兄弟目录,因此有以下的结果
Path p1_to_p2 = p1.relativize(p2); // 结果是 ../../sally
Path p2_to_p1 = p2.relativize(p1); // 结果是 ../joe/foo
让我们看看另外一个例子
Path p1 = Paths.get("home");
Path p3 = Paths.get("home/sally/bar");
Path p1_to_p3 = p1.relativize(p3); // 结果是 sally/bar
Path p3_to_p1 = p3.relativize(p1); // 结果是 ../..
在这个例子中,两个路径共享同一个节点-home,
所以结果并不是../home/sally/bar和../../../home.
如果两个路径中有一个是绝对路径,另外一个是相对路径,relative方法会抛出异常。如果两个路径都是绝对路径,那么relative方法的行为和系
统相关,不同的系统可能不同。
我在Windows操作系统下实验了一下,如果两个路径属于同一个硬盘,那么可以执行成功,否则会抛出异常。
Path path1 = Paths.get("c:\\abcd\\efg");
Path path2 = Paths.get("c:\\temp");
System.out.println(path1.relativize(path2)); //结果是..\..\temp
System.out.println(path2.relativize(path1)); //结果是..\abcd\efg
Path path3 = Paths.get("c:\\abcd\\efg");
Path path4 = Paths.get("d:\\temp");
System.out.println(path3.relativize(path4)); //抛出异常
Path
的比较
Path提供
equals方法来检查两个Path是否相等。但是这里
要注意的是比较的并不是两个Path是否指向同一个目录或者文件。请看下面的例子:
Path path1 = Paths.get("abcd\\123");
Path path2 = Paths.get("abcd\\123");
Path path3 = Paths.get("abcd\\.\\123");
System.out.println(path1.equals(path2)); //true
System.out.println(path1.equals(path3)); //false
System.out.println(path1.equals(path3.normalize())); //true
System.out.println(path1.equals(path1.toAbsolutePath())); //false
Path
类还提供了startsWith和endsWith方法,这两个方法用来检查路径是否以指定的字符串开始或者结束,例如:
Path path = ;
Path otherPath = ;
Path beginning = Paths.get("/home");
Path ending = Paths.get("foo");
if (path.equals(otherPath)) {
//equality logic here
} else if (path.startsWith(beginning)) {
//path begins with "/home"
} else if (path.endsWith(ending)) {
//path ends with "foo"
}
Path类实现了Iterable接口,iterator方法会返回一个Iterator对象,该对象中的第一个元素就是原
路径中最上层(最靠近根节点)的目录。下面是使用这个方法的例子:
Path path = ;
for (Path name: path) {
System.out.println(name);
}
Path类还实现了Comparable接口,因此可以使用compareTo来比较两个Path。比较的算法和结果是和文
件系统的提供者和系统平台相关的。大家在使用之前,最后先实验一下。
Path类还提供了一个方法isSameFile来检查两个Path是否指向同一个目录或文件。如果作为参数的Path为null,那么会直接返回
false,不会去检查Path指向的文件是否存在。如果两Path来自不同的文件系统提供者,那么也会直接返回false,不会去检查文件或目录是否存
在。如果两个Path执行equals方法的返回结果为true,那么这个方法直接返回true,也不会去检查文件或目录是否存在。其他的情况下是否会去
打开或访问Path指向的文件或目录是与具体的实现相关的,也就是说不同的JDK/JRE可能会有不同的行为。
验证文件或目录是否存在
上面所介绍的很多方法都不会去验证Path指向的文件或目录是否存在,只是操作Path实例自身。但是在有些情况下,你
需要访问文件系统来验证文件、目录存在与否,这时你可以使用exists和notExists方法。需要注意的是!path.exists()并不等于
path.notExists()。当你调用这两个方法时,有以下3中情况:
- 文件或者目录被证实存在
- 文件或者目录被证实不存在
- 不知道文件或目录是否存在。当程序没有访问这个文
件或目录的权限的时候这个情况会发生。
如果exists()和notExists()都返回false,说明无法验证该文件是否存在。
在下面一篇文章中,我们会介绍怎样进行文件的操作。
转载自开发者的天空
1. Path的概念
文件系统会把文件进行组织并存放在媒介上,通常是一个或多个硬盘上。目前使用的大多数的文件系统是以树形的方式来存储文件的。在树的顶端是一个(或多个)
根节点,在根节点之下是文件和目录,目录下又可以包含文件和目录。理论上可以这样一层层的一直嵌套下去(当然实际中是不可能的,例如Windows系统对
路径的长度有限制)。
下面的图中展示了一个包含一个根节点的目录树。(在Windows系统中支持多个根节点,例如C:\和D:\分别是一个根节点。)
Path能够唯一的标识文件系统中的某一个文件。例如上面图中的statusReport文件在Windows系统中就可以使用以下的Path来标识:
c:\home\sally\statusReport
在solaris系统中,其path为
/home/sally/statusReport.
Path中的分隔符在不同的文件系统中是不同的,例如在Windows系统中使用\,在Solaris系统中使用/。
Path又可以分为绝对路径和相对路径。绝对路径总是含有根节点和用来定位文件的完整的目录列表。例如/home/sally/statusReport
就是绝对路径。相对路径需要和另外的路径结合在一起才能够确定一个文件。例如加哦加哦哦joe/foo就是一个相对路径,如果没有其他的信息,程序就没有
办法定位这个文件。我们必须说这个是相对/home的相对路径,这样我们才能够找到它。
除了我们熟悉的目录和文件之外,有一些文件系统中还支持符号链接(symbolic link),也称为symlink或soft
link。一个符号链接是一个特殊的文件,它实际上是另外一个链接的引用。在大多数情况下符号链接对应用程序是透明的,对符号链接的操作会自动的重定向到
链接所指向的目的地,除非操作是删除这个符号链接、重命名这个符号链接或修改符号链接的目的地。
在下面的图中,logFile对于用户来说就是一个常规的文件,但是实际上是一个符号链接,链接到dir/logs/HomeLogFile文件。
HomeLogFile是这个链接的目的地。
不恰当的使用符号链接会导致循环引用,例如目录A是一个符号链接,指向目录B,目录B下有一个子目录是符号链接,指向目录A的父目录。这样,当程序企图递
归的遍历整个目录树时,就会出问题。幸运的是,这个问题已经被发现了,并且不会导致程序的死循环。
2.Path类
Path类是JDK7新引入的一个类,该类是java.io.file包的基石。
和类的名称所展示的一样,Path类就是文件系统中path概念的程序表现。Path对象包含了文件名和目录列表,这些信息可以用来创建path,并且用
来检验、定位和操作文件。
Path实例是和底层操作系统相关的。在Solaris系统中,Path使用Solaris的句法(/home/joe/foo);在Window操作系
统中,Path会使用Window的句法(C:\home\joe\foo)。因此Path不是操作系统无关的。你不能够比较一个来自Solaris文件
系统的Path和一个来自Windows文件系统的Path并期望它们相等,在目录结构和文件完全一样的时候也不行。
Path对应的文件或者目录可以不存在。我们可以以不同的方式创建Path实例并操作它:扩展路径、抽取路径的一部分、和其他的Path比较。我们也可以
检查Path对应的目录或文件是否存在、
创建文件或目录、打开或删除文件、修改许可权限等。
Path类是能够区分符号链接的。所有的Path的方法要么会检测是否是符号链接并执行不同的操作,或者提供了一个选择来使用户能够配置当遇到符号链接的
时候执行什么操作。
Path类提供了很多易于使用的特性,Path类的方法可以分成两类:
- 路径操作—返回路径的一部分如根节点、名称、父目录的方法或者其他操作路径的方法。
- 文件操作—创建文件、打开文件、创建目录、删除文件、
拷贝文件等操作的方法。
在下一篇文章中,我们会具体介绍Path类的这些方法。
有同事问到在程序中怎样知道数据库表中那些字段是主键。当时不知道,晚上回来看了看JDK的文档。
在使用JDBC来查询数据库的时候,通常的步骤是:
1. 注册驱动程序
2. 获取数据库连接
3. 执行查询语句
4. 关闭连接。
在获得数据库连接后,就可以通过getMetaData()方法来获取DatabaseMetaData;然后通过DatabaseMetaData的getPrimaryKeys ()方法来获取主键的信息。
下面是我做的示例程序,该程序在JBuilder2005+oracle8i下通过:
import java.sql.*;
import javax.sql.*;
public class TestJDBC {
public TestJDBC() {
}
public static void main(String[] args) throws SQLException {
Connection con = null;
Statement st = null;
ResultSet rst = null;
try{
//注册数据库驱动程序
Class.forName("oracle.jdbc.driver.OracleDriver");
//获取数据库连接
con = DriverManager.getConnection("jdbc:oracle:thin:@10.60.203.80:1521:TestDB","123","123");
//获取主键信息
rst = con.getMetaData().getPrimaryKeys(null,null,"USER");
//打印主键信息
if (!rst.isAfterLast()) {
rst.next();
System.out.println(rst.getString("TABLE_NAME") + " " +
rst.getString("COLUMN_NAME"));
}
}
catch (Exception e){
System.out.println(e.getLocalizedMessage());
}
finally{
try{
//关闭连接
if (rst != null)
rst.close();
if (con != null)
con.close();
}
catch (SQLException e){
throw e;
}
}
}
}
上面的程序中,在获取主键信息的时候,语句
rst = con.getMetaData().getPrimaryKeys(null,null,"USER");
用来获取主键信息。关于该函数的详细信息,请参阅JDK的文档。这里要说的是,在测试中发现第三个参数(数据库表名)是大小写敏感的,如果写成user是查不到结果的。
在Spring 的AOP中,如果一个Proxy同时实现MethodBeforeAdvice、AfterReturningAdvice和MethodInterceptor接口,那么这三个Advice的执行顺序是什么样的呢?
经过试验,是和xml文件中的定义顺序有关的。
如果Proxy的接口实现定义为
MethodBeforeAdvice
AfterReturningAdvice
MethodInterceptor
那么执行的结果是
MethodBeforeAdvice
MethodInterceptor: before call
Really method excuting
MethodInterceptor: after call
AfterReturningAdvice
也就是说,执行顺序是:MethodBeforeAdvice,MethodInterceptor的调用前的部分,目标方法,MethodInterceptor的调用后的部分,AfterReturningAdvice。
如果proxy的定义是
MethodBeforeAdvice
MethodInterceptor
AfterReturningAdvice
执行的结果是
MethodBeforeAdvice
MethodInterceptor: before call
Really method excuting
AfterReturningAdvice
MethodInterceptor: after call
也就是说,执行的顺序是:MethodBeforeAdvice,MethodInterceptor的调用前的部分,目标方法,AfterReturningAdvice,MethodInterceptor的调用后的部分。
如果proxy的定义是
MethodInterceptor
MethodBeforeAdvice
AfterReturningAdvice
执行的结果是:
MethodInterceptor: before call
MethodBeforeAdvice
Really method excuting
AfterReturningAdvice
MethodInterceptor: after call
也就是说,执行的顺序是:MethodInterceptor的调用前的部分,MethodBeforeAdvice,目标方法,AfterReturningAdvice,MethodInterceptor的调用后的部分。
以上的顺序是在springframework 1.2.5中测试的。
今天在看Spring in Action,看到了一个很有意思的例子。
假如你编写了一个类:
public class ClassA{
private String fieldA;
public ClassA(){
fieldA = "This is Class A";
}
public myPrint(){
System.out.println(fieldA);
}
}
OK。按照面向对象的思想,其他的类应该不能够修改ClassA的fieldA字段吧?因此无论什么时候你调用ClassA的myPrint方法,都应该打印的是“This is Class A”。
但是,实际情况并不是如此,请看下面的例子:
import java.lang.reflect.Field;
public class TestChangeApp
{
public static void main(String[] args)
{
System.out.println("Begin to test change.");
ClassA test = new ClassA();
test.myPrint();
Field[] fields = test.getClass().getDeclaredFields();
try
{
for (int i=0;i {
if (fields[i].getType().equals(java.lang.String.class)){
fields[i].setAccessible(true);
fields[i].set(test, "This field have been changed!");
}
}
}
catch (Exception e)
{
}
test.myPrint();
}
}
运行结果是什么呢?是
Begin to test change.
This is ClassA
This field have been changed!
也就是说,在TestChangeApp类中,可以修改ClassA的私有成员变量。