Java NIO() (New Input/Output) API定义了buffers,这个buffers中包含数据或其他结构,如:字符集、channels和可选的channels。
- 字符集,是在bytes 字节和Unicode字符集中间映射。
- Channels通道,表示与能够执行I/O操作的实体的连接。
- 可选的Channels通道,是那些可以被多路复用的通道,意味着它们可以在一个通道中处理多个I/O操作。
目录
一、Java NIO 示例
下列代码示例演示了Java NIO API:
1.1 Grep NIO 示例
这个示例在一个文件列表中搜索匹配给定正则表达式模式的行。它演示了nio映射的字节缓冲区、字符集和正则表达式。
public class Grep {
// 用于ISO-8859-15的字符集和解码器
private static Charset charset = Charset.forName("ISO-8859-15");
private static CharsetDecoder decoder = charset.newDecoder();
// 用于解析行的模式
private static Pattern linePattern = Pattern.compile(".*\r?\n");
// 我们要找的输入模式
private static Pattern pattern;
// 从命令行编译模式pattern
private static void compile(String pat) {
try {
pattern = Pattern.compile(pat);
} catch (PatternSyntaxException x) {
System.err.println(x.getMessage());
System.exit(1);
}
}
// 使用linpattern将给定的CharBuffer分成几行,将输入模式应用到每一行,看看是否有匹配
private static void grep(File f, CharBuffer cb) {
Matcher lm = linePattern.matcher(cb); // Line matcher
Matcher pm = null; // Pattern matcher
int lines = 0;
while (lm.find()) {
lines++;
CharSequence cs = lm.group(); // The current line
if (pm == null)
pm = pattern.matcher(cs);
else
pm.reset(cs);
if (pm.find())
System.out.print(f + ":" + lines + ":" + cs);
if (lm.end() == cb.limit())
break;
}
}
// 搜索给定文件中出现的输入模式
private static void grep(File f) throws IOException {
// 打开文件,然后从流中获取一个通道
try (FileInputStream fis = new FileInputStream(f);
FileChannel fc = fis.getChannel()) {
// 获取文件的大小,然后将其映射到内存中
int sz = (int) fc.size();
MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
// 将文件解码到一个字符缓冲区
CharBuffer cb = decoder.decode(bb);
// 执行搜索
grep(f, cb);
}
}
public static void main(String[] args) {
if (args.length < 2) {
System.err.println("Usage: java Grep pattern file...");
return;
}
compile(args[0]);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
try {
grep(f);
} catch (IOException x) {
System.err.println(f + ": " + x);
}
}
}
}
1.2 Checksum NIO示例
这个例子计算一个文件列表的16位校验和。它使用nio映射的字节缓冲区来提高速度。
public class Sum {
// 计算给定字节缓冲区中所有剩余字节的16位校验和
private static int sum(ByteBuffer bb) {
int sum = 0;
while (bb.hasRemaining()) {
if ((sum & 1) != 0)
sum = (sum >> 1) + 0x8000;
else
sum >>= 1;
sum += bb.get() & 0xff;
sum &= 0xffff;
}
return sum;
}
// 计算并打印给定文件的校验和
private static void sum(File f) throws IOException {
// Open the file and then get a channel from the stream
try (
FileInputStream fis = new FileInputStream(f);
FileChannel fc = fis.getChannel()) {
// Get the file's size and then map it into memory
int sz = (int) fc.size();
MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
// Compute and print the checksum
int sum = sum(bb);
int kb = (sz + 1023) / 1024;
String s = Integer.toString(sum);
System.out.println(s + "\t" + kb + "\t" + f);
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java Sum file...");
return;
}
for (int i = 0; i < args.length; i++) {
File f = new File(args[i]);
try {
sum(f);
} catch (IOException e) {
System.err.println(f + ": " + e);
}
}
}
}
1.3 时间查询NIO示例
这个示例询问主机列表的时间。这是一个简单的阻塞程序,演示了NIO套接字通道(连接和读取)、缓冲区处理、字符集和正则表达式。
public class TimeQuery {
// 标准daytime端口
private static int DAYTIME_PORT = 13;
// 我们将实际使用的端口
private static int port = DAYTIME_PORT;
// US-ASCII的字符集和解码器
private static Charset charset = Charset.forName("US-ASCII");
private static CharsetDecoder decoder = charset.newDecoder();
// 直接读取字节缓冲区
private static ByteBuffer dbuf = ByteBuffer.allocateDirect(1024);
// 询问所给主机时间是多少
private static void query(String host) throws IOException {
try (SocketChannel sc = SocketChannel.open()) {
InetSocketAddress isa = new InetSocketAddress(
InetAddress.getByName(host), port);
// 连接
sc.connect(isa);
// 从远程主机读取时间。为了简单起见,我们假设时间以单个包的形式返回给我们,
// 因此我们只需要读取一次。
dbuf.clear();
sc.read(dbuf);
// 打印远程地址和接收时间
dbuf.flip();
CharBuffer cb = decoder.decode(dbuf);
System.out.print(isa + " : " + cb);
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java TimeQuery [port] host...");
return;
}
int firstArg = 0;
// 如果第一个参数是一个数字字符串,那么我们将其作为端口号
if (Pattern.matches("[0-9]+", args[0])) {
port = Integer.parseInt(args[0]);
firstArg = 1;
}
for (int i = firstArg; i < args.length; i++) {
String host = args[i];
try {
query(host);
} catch (IOException e) {
System.err.println(host + ": " + e);
e.printStackTrace();
}
}
}
}
1.4 时间服务器NIO示例
这个示例侦听连接并告诉调用者当前的时间。是一个简单的阻塞程序,它演示了NIO套接字通道(接受和写入)、缓冲区处理、字符集和正则表达式。
public class TimeServer {
// 我们不能使用普通的daytime端口
// (除非我们作为root运行,这是不可能的),所以我们使用这个端口
private static int PORT = 8013;
// 我们将实际使用的端口
private static int port = PORT;
// US-ASCII的字符集和编码器
private static Charset charset = Charset.forName("US-ASCII");
private static CharsetEncoder encoder = charset.newEncoder();
// 用于写入的直接字节缓冲区
private static ByteBuffer dbuf = ByteBuffer.allocateDirect(1024);
// 打开并绑定服务器-套接字通道
private static ServerSocketChannel setup() throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
InetSocketAddress isa = new InetSocketAddress(
InetAddress.getLocalHost(), port);
ssc.socket().bind(isa);
return ssc;
}
// 为给定通道上传入的下一个请求提供服务
private static void serve(ServerSocketChannel ssc) throws IOException {
try (SocketChannel sc = ssc.accept()) {
String now = new Date().toString();
System.out.println("now: " + now);
sc.write(encoder.encode(CharBuffer.wrap(now + "\r\n")));
System.out.println(sc.socket().getInetAddress() + " : " + now);
}
}
public static void main(String[] args) {
if (args.length > 1) {
System.err.println("Usage: java TimeServer [port]");
return;
}
// 如果第一个参数是一个数字字符串,那么我们将其作为端口号
if ((args.length == 1) && Pattern.matches("[0-9]+", args[0]))
port = Integer.parseInt(args[0]);
try {
ServerSocketChannel ssc = setup();
for (;;) {
serve(ssc);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.5 非阻塞时间服务器NIO示例
public class NBTimeServer {
private static final int DEFAULT_TIME_PORT = 8900;
// 不带参数的构造函数在默认端口上创建时间服务器。
public NBTimeServer() throws Exception {
acceptConnections(this.DEFAULT_TIME_PORT);
}
// 带port参数的构造函数在指定端口上创建时间服务器。
public NBTimeServer(int port) throws Exception {
acceptConnections(port);
}
// 接受当前时间的连接。抛出Lazy Exception。
private static void acceptConnections(int port) throws Exception {
// 传入时间请求的选择器
Selector acceptSelector = SelectorProvider.provider().openSelector();
// 创建新的服务器套接字并设置为非阻塞模式
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
// 将服务器套接字绑定到本地主机和端口
InetAddress lh = InetAddress.getLocalHost();
InetSocketAddress isa = new InetSocketAddress(lh, port);
ssc.socket().bind(isa);
// 使用selector选择器在服务器套接字上注册接收。
// 这一步告诉selector选择器,当接受操作发生时,套接字希望被放在就绪列表中,
// 因此允许发生多路非阻塞I/O。
SelectionKey acceptKey = ssc.register(acceptSelector,
SelectionKey.OP_ACCEPT);
int keysAdded = 0;
// 这里是发生的地方。
// 当上面注册的任何操作发生、线程被中断等情况下,select方法将返回。
while ((keysAdded = acceptSelector.select()) > 0) {
//某一个准备好了I/O, 获取 ready keys
Set<SelectionKey> readyKeys = acceptSelector.selectedKeys();
Iterator<SelectionKey> i = readyKeys.iterator();
// 遍历就绪键集合并处理日期请求。
while (i.hasNext()) {
SelectionKey sk = (SelectionKey) i.next();
i.remove();
// 键索引到选择器,这样您就可以检索准备好I/O的套接字
ServerSocketChannel nextReady = (ServerSocketChannel) sk
.channel();
// 接受日期请求并发回日期字符串
Socket s = nextReady.accept().socket();
// 将当前时间写入套接字
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
Date now = new Date();
out.println(now);
out.close();
}
}
}
// 入口
public static void main(String[] args) {
// 删除命令行参数并创建一个新的时间服务器(还没有参数)
try {
NBTimeServer nbt = new NBTimeServer();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.6 Internet协议和UNIX域套接字NIO示例
这个例子演示了如何在一个非阻塞的客户端/服务器单线程应用程序中将AF_UNIX和AF_INET/6通道与SocketChannel和ServerSocketChannel类混合。
这个示例模拟了socat命令行实用程序的一些功能。它可以创建监听器或客户机,并将它们连接到监听器,并执行各种不同类型的绑定。带-h选项的命令用于打印使用信息。
在初始化时,只有不同的地址类型需要特殊处理。对于服务器端,一旦创建了侦听器并将其绑定到一个地址,管理选择器的代码就可以相同地处理不同的地址族。
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import jdk.net.ExtendedSocketOptions;
import jdk.net.UnixDomainPrincipal;
import static java.net.StandardProtocolFamily.UNIX;
import static java.net.StandardProtocolFamily.INET;
import static java.net.StandardProtocolFamily.INET6;
public class Socat {
static void usage() {
String ustring = """
usage: java Socat -s <baddr>...
java Socat -c [-bind <baddr>] <daddr> N [delay]
java Socat -h
-s表示创建一个或多个绑定到地址<baddr>…</baddr>
然后接受所有传入的连接并显示接收到的数据(计数)。
如果提供多个<baddr>,然后创建多个通道,每个通道都是</baddr>绑定到提供的地址之一。
所有通道都是非阻塞的由一个选择器管理。
-c表示创建一个客户端,将其连接到<daddr>,并发送N个(16kb)缓冲区。
</daddr>的客户端可以选择绑定到给定地址<baddr>。</baddr>如果指定了延迟,
然后,程序每次暂停指定的毫秒数发送。发送后,客户端读取直到EOF,然后退出。
注意:默认情况下,AF_UNIX客户端套接字不绑定到地址。
因此,在服务器端看到的远程地址(以及客户机的本地地址)是一个空路径。
这与AF_INET/6套接字略有不同,如果用户没有选择本地端口,则会分配一个随机选择的端口。
-h表示打印此消息并退出。
<baddr>和<daddr>是指定如下的地址:
UNIX:{path}
INET:{host}:port
INET6:{host}:port
{path} 是用花括号括起来的套接字文件的名称,
{},当绑定表示随机选择的本地服务器时,该参数可以为空地址。
{host}:端口是由域名或IPv4/v6字面量组成的互联网地址用花括号括起来,
{},当绑定(表示任意本地地址)和端口号,绑定时端口号可以为零。
""";
System.out.println(ustring);
}
static boolean isClient;
static boolean initialized = false;
static final int BUFSIZE = 8 * 1024;
static int N; // Number of buffers to send
static int DELAY = 0; // Milliseconds to delay between sends
static List<AddressAndFamily> locals = new LinkedList<>();
static AddressAndFamily remote;
// family只在address为空的情况下才需要。
// 它可以是Record类型。
static class AddressAndFamily {
SocketAddress address;
ProtocolFamily family;
AddressAndFamily(ProtocolFamily family, SocketAddress address) {
this.address = address;
this.family = family;
}
}
static AddressAndFamily parseAddress(String addr) throws UnknownHostException {
char c = addr.charAt(0);
if (c != 'U' && c != 'I')
throw new IllegalArgumentException("invalid address");
String family = addr.substring(0, addr.indexOf(':')).toUpperCase();
return switch (family) {
case "UNIX" -> parseUnixAddress(addr);
case "INET" -> parseInetSocketAddress(INET, addr);
case "INET6" -> parseInetSocketAddress(INET6, addr);
default -> throw new IllegalArgumentException();
};
}
static AddressAndFamily parseUnixAddress(String token) {
String path = getPathDomain(token);
UnixDomainSocketAddress address;
if (path.isEmpty())
address = null;
else
address = UnixDomainSocketAddress.of(path);
return new AddressAndFamily(UNIX, address);
}
static AddressAndFamily parseInetSocketAddress(StandardProtocolFamily family, String token) throws UnknownHostException {
String domain = getPathDomain(token);
InetAddress address;
if (domain.isEmpty()) {
address = (family == StandardProtocolFamily.INET)
? InetAddress.getByName("0.0.0.0")
: InetAddress.getByName("::0");
} else {
address = InetAddress.getByName(domain);
}
int cp = token.lastIndexOf(':') + 1;
int port = Integer.parseInt(token.substring(cp));
var isa = new InetSocketAddress(address, port);
return new AddressAndFamily(family, isa);
}
// 返回花括号之间的令牌,即域名或UNIX路径。
static String getPathDomain(String s) {
int start = s.indexOf('{') + 1;
int end = s.indexOf('}');
if (start == -1 || end == -1 || (start > end))
throw new IllegalArgumentException(s);
return s.substring(start, end);
}
// 如果程序必须退出,则返回false。
static void parseArgs(String[] args) throws UnknownHostException {
if (args[0].equals("-h")) {
usage();
} else if (args[0].equals("-c")) {
isClient = true;
int nextArg;
AddressAndFamily local = null;
if (args[1].equals("-bind")) {
local = parseAddress(args[2]);
locals.add(local);
nextArg = 3;
} else {
nextArg = 1;
}
remote = parseAddress(args[nextArg++]);
N = Integer.parseInt(args[nextArg++]);
if (nextArg == args.length - 1) {
DELAY = Integer.parseInt(args[nextArg]);
}
initialized = true;
} else if (args[0].equals("-s")) {
isClient = false;
for (int i = 1; i < args.length; i++) {
locals.add(parseAddress(args[i]));
}
initialized = true;
} else
throw new IllegalArgumentException();
}
public static void main(String[] args) throws Exception {
try {
parseArgs(args);
} catch (Exception e) {
System.out.printf("\nInvalid arguments supplied. See the following for usage information\n");
usage();
}
if (!initialized)
return;
if (isClient) {
doClient();
} else {
doServer();
}
}
static Map<SocketChannel,Integer> byteCounter = new HashMap<>();
private static void initListener(AddressAndFamily aaf, Selector selector) {
try {
ProtocolFamily family = aaf.family;
SocketAddress address = aaf.address;
ServerSocketChannel server = ServerSocketChannel.open(family);
server.bind(address);
server.configureBlocking(false);
postBind(address);
server.register(selector, SelectionKey.OP_ACCEPT, null);
System.out.println("Server: Listening on " + server.getLocalAddress());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static void doServer() throws IOException {
ByteBuffer readBuf = ByteBuffer.allocate(64 * 1024);
final Selector selector = Selector.open();
locals.forEach(localAddress -> initListener(localAddress, selector));
int nextConnectionId = 1;
while (true) {
selector.select();
var keys = selector.selectedKeys();
for (SelectionKey key : keys) {
try {
SelectableChannel c = key.channel();
if (c instanceof ServerSocketChannel) {
var server = (ServerSocketChannel)c;
var ch = server.accept();
var userid = "";
if (server.getLocalAddress() instanceof UnixDomainSocketAddress) {
// UNIX通道的附加功能的说明;这不是必须的行为。
UnixDomainPrincipal pr = ch.getOption(ExtendedSocketOptions.SO_PEERCRED);
userid = "user: " + pr.user().toString() + " group: " +
pr.group().toString();
}
ch.configureBlocking(false);
byteCounter.put(ch, 0);
System.out.printf("Server: new connection\n\tfrom {%s}\n", ch.getRemoteAddress());
System.out.printf("\tConnection id: %s\n", nextConnectionId);
if (userid.length() > 0) {
System.out.printf("\tpeer credentials: %s\n", userid);
}
System.out.printf("\tConnection count: %d\n", byteCounter.size());
ch.register(selector, SelectionKey.OP_READ, nextConnectionId++);
} else {
var ch = (SocketChannel) c;
int id = (Integer)key.attachment();
int bytes = byteCounter.get(ch);
readBuf.clear();
int n = ch.read(readBuf);
if (n < 0) {
String remote = ch.getRemoteAddress().toString();
System.out.printf("Server: closing connection\n\tfrom: {%s} Id: %d\n", remote, id);
System.out.printf("\tBytes received: %d\n", bytes);
byteCounter.remove(ch);
ch.close();
} else {
readBuf.flip();
bytes += n;
byteCounter.put(ch, bytes);
display(ch, readBuf, id);
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
keys.clear();
}
}
private static void postBind(SocketAddress address) {
if (address instanceof UnixDomainSocketAddress) {
var usa = (UnixDomainSocketAddress)address;
usa.getPath().toFile().deleteOnExit();
}
}
private static void display(SocketChannel ch, ByteBuffer readBuf, int id)
throws IOException
{
System.out.printf("Server: received %d bytes from: {%s} Id: %d\n",
readBuf.remaining(), ch.getRemoteAddress(), id);
}
private static void doClient() throws Exception {
SocketChannel client;
if (locals.isEmpty())
client = SocketChannel.open(remote.address);
else {
AddressAndFamily aaf = locals.get(0);
client = SocketChannel.open(aaf.family);
client.bind(aaf.address);
postBind(aaf.address);
client.connect(remote.address);
}
ByteBuffer sendBuf = ByteBuffer.allocate(BUFSIZE);
for (int i=0; i<N; i++) {
fill(sendBuf);
client.write(sendBuf);
Thread.sleep(DELAY);
}
client.shutdownOutput();
ByteBuffer rxb = ByteBuffer.allocate(64 * 1024);
int c;
while ((c = client.read(rxb)) > 0) {
rxb.flip();
System.out.printf("Client: received %d bytes\n", rxb.remaining());
rxb.clear();
}
client.close();
}
private static void fill(ByteBuffer sendBuf) {
// 因为这个例子是出于演示目的,所以这个方法不会用数据填充ByteBuffer sendBuf。
// 它将sendBuf的限制设置为其容量并将其位置设置为零。
// 因此,当该示例写入sendBuf的内容时,它将写入在分配sendBuf时发生在内存中的所有内容。
sendBuf.limit(sendBuf.capacity());
sendBuf.position(0);
}
}
运行Socat实例
运行Socat示例的示例如下:
1. 在命令行shell中执行Socat命令:
$ java Socat -s UNIX:{/tmp/uds.sock}
Server: Listening on /tmp/uds.sock
2. 在另一个命令行shell中,按如下方式运行Socat:
$ java Socat -c UNIX:{/tmp/uds.sock} 1
在第一个命令行shell中,你会看到类似如下的输出:
Server: new connection
from {}
Connection id: 1
peer credentials: user: yourusername group: yourgroup
Connection count: 1
Server: received 8192 bytes from: {} Id: 1
Server: closing connection
from: {} Id: 1
Bytes received: 8192
如果你在创建UNIX域套接字时没有指定文件名,那么JVM会创建一个套接字文件,并自动将套接字绑定到它:
$ java Socat -s UNIX:{}
Server: Listening on /tmp/socket_837668026
这与调用ServerSocketChannel.bind(null)是相同的。通过设置系统属性jdk.net.unixdomain.tmpdir,可以更改JVM保存自动生成的套接字文件的默认目录。参见网络系统属性。
二、文件NIO示例
2.1 Chmod File NIO使用实例
这个例子编译了一个或多个符号模式表达式的列表,这些符号模式表达式可以以类似于UNIX chmod命令的方式更改一组文件权限。
symbol -mode-list形参是一个逗号分隔的表达式列表,其中每个表达式的形式如下:
who operator [permissions]
- who:以下字符中的一个或多个:u、g、o或a,分别表示所有者(用户)、组、其他人或所有(所有者、组和其他人)。
- operator:字符+,-,或=,表示如何更改权限:
+
: 权限增加-
: 权限移除=
: 绝对分配权限
如果在绝对分配权限(使用=操作符)时省略了权限,则清除所有者、组或由who标识的其他人的权限。如果省略权限,则忽略操作符+和-。
以下是symbolic-mode-list参数的示例:
u=rw
: 设置所有者的读写权限。ug+w
: 设置所有者写权限和组写权限。u+w,o-rwx
: 设置所有者的写权限,移除其他人的读权限、其他人的写权限和其他人的执行权限。o=
: 将其他权限设置为无(如果设置了其他读权限、其他写权限和其他执行权限,则删除这些权限)。
public class Chmod {
public static Changer compile(String exprs) {
// 最小值是谁和操作符(例如u=)
if (exprs.length() < 2)
throw new IllegalArgumentException("Invalid mode");
// 更改程序将添加或删除的权限
final Set<PosixFilePermission> toAdd = new HashSet<PosixFilePermission>();
final Set<PosixFilePermission> toRemove = new HashSet<PosixFilePermission>();
// 遍历每个表达式模式
for (String expr: exprs.split(",")) {
// minimum of who and operator
if (expr.length() < 2)
throw new IllegalArgumentException("Invalid mode");
int pos = 0;
// 谁
boolean u = false;
boolean g = false;
boolean o = false;
boolean done = false;
for (;;) {
switch (expr.charAt(pos)) {
case 'u' : u = true; break;
case 'g' : g = true; break;
case 'o' : o = true; break;
case 'a' : u = true; g = true; o = true; break;
default : done = true;
}
if (done)
break;
pos++;
}
if (!u && !g && !o)
throw new IllegalArgumentException("Invalid mode");
// 获取操作符和权限
char op = expr.charAt(pos++);
String mask = (expr.length() == pos) ? "" : expr.substring(pos);
// 操作符
boolean add = (op == '+');
boolean remove = (op == '-');
boolean assign = (op == '=');
if (!add && !remove && !assign)
throw new IllegalArgumentException("Invalid mode");
// who= 意味着删除所有
if (assign && mask.length() == 0) {
assign = false;
remove = true;
mask = "rwx";
}
// 权限
boolean r = false;
boolean w = false;
boolean x = false;
for (int i=0; i<mask.length(); i++) {
switch (mask.charAt(i)) {
case 'r' : r = true; break;
case 'w' : w = true; break;
case 'x' : x = true; break;
default:
throw new IllegalArgumentException("Invalid mode");
}
}
// 更新权限设置
if (add) {
if (u) {
if (r) toAdd.add(OWNER_READ);
if (w) toAdd.add(OWNER_WRITE);
if (x) toAdd.add(OWNER_EXECUTE);
}
if (g) {
if (r) toAdd.add(GROUP_READ);
if (w) toAdd.add(GROUP_WRITE);
if (x) toAdd.add(GROUP_EXECUTE);
}
if (o) {
if (r) toAdd.add(OTHERS_READ);
if (w) toAdd.add(OTHERS_WRITE);
if (x) toAdd.add(OTHERS_EXECUTE);
}
}
if (remove) {
if (u) {
if (r) toRemove.add(OWNER_READ);
if (w) toRemove.add(OWNER_WRITE);
if (x) toRemove.add(OWNER_EXECUTE);
}
if (g) {
if (r) toRemove.add(GROUP_READ);
if (w) toRemove.add(GROUP_WRITE);
if (x) toRemove.add(GROUP_EXECUTE);
}
if (o) {
if (r) toRemove.add(OTHERS_READ);
if (w) toRemove.add(OTHERS_WRITE);
if (x) toRemove.add(OTHERS_EXECUTE);
}
}
if (assign) {
if (u) {
if (r) toAdd.add(OWNER_READ);
else toRemove.add(OWNER_READ);
if (w) toAdd.add(OWNER_WRITE);
else toRemove.add(OWNER_WRITE);
if (x) toAdd.add(OWNER_EXECUTE);
else toRemove.add(OWNER_EXECUTE);
}
if (g) {
if (r) toAdd.add(GROUP_READ);
else toRemove.add(GROUP_READ);
if (w) toAdd.add(GROUP_WRITE);
else toRemove.add(GROUP_WRITE);
if (x) toAdd.add(GROUP_EXECUTE);
else toRemove.add(GROUP_EXECUTE);
}
if (o) {
if (r) toAdd.add(OTHERS_READ);
else toRemove.add(OTHERS_READ);
if (w) toAdd.add(OTHERS_WRITE);
else toRemove.add(OTHERS_WRITE);
if (x) toAdd.add(OTHERS_EXECUTE);
else toRemove.add(OTHERS_EXECUTE);
}
}
}
// 返回改变
return new Changer() {
@Override
public Set<PosixFilePermission> change(Set<PosixFilePermission> perms) {
perms.addAll(toAdd);
perms.removeAll(toRemove);
return perms;
}
};
}
/**
* A task that <i>changes</i> a set of {@link PosixFilePermission} elements.
*/
public interface Changer {
/**
* 将更改应用于给定的权限集。
*
* @param perms
* The set of permissions to change
*
* @return The {@code perms} parameter
*/
Set<PosixFilePermission> change(Set<PosixFilePermission> perms);
}
/**
* 使用给定的更改器更改文件的权限。
*/
static void chmod(Path file, Changer changer) {
try {
Set<PosixFilePermission> perms = Files
.getPosixFilePermissions(file);
Files.setPosixFilePermissions(file, changer.change(perms));
} catch (IOException x) {
System.err.println(x);
}
}
/**
* 修改所访问的每个文件和目录的权限
*/
static class TreeVisitor implements FileVisitor<Path> {
private final Changer changer;
TreeVisitor(Changer changer) {
this.changer = changer;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
chmod(dir, changer);
return CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
chmod(file, changer);
return CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
if (exc != null)
System.err.println("WARNING: " + exc);
return CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.err.println("WARNING: " + exc);
return CONTINUE;
}
}
static void usage() {
System.err.println("java Chmod [-R] symbolic-mode-list file...");
System.exit(-1);
}
public static void main(String[] args) throws IOException {
if (args.length < 2)
usage();
int argi = 0;
int maxDepth = 0;
if (args[argi].equals("-R")) {
if (args.length < 3)
usage();
argi++;
maxDepth = Integer.MAX_VALUE;
}
// 编译符号模式表达式
Changer changer = compile(args[argi++]);
TreeVisitor visitor = new TreeVisitor(changer);
Set<FileVisitOption> opts = Collections.emptySet();
while (argi < args.length) {
Path file = Paths.get(args[argi]);
Files.walkFileTree(file, opts, maxDepth, visitor);
argi++;
}
}
}
2.2 复制文件NIO示例
这个示例以类似于copy命令的方式复制文件。
public class Copy {
/**
* Returns {@code true} if okay to overwrite a file ("cp -i")
*/
static boolean okayToOverwrite(Path file) {
String answer = System.console().readLine("overwrite %s (yes/no)? ", file);
return (answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"));
}
/**
* Copy source file to target location. If {@code prompt} is true then
* prompt user to overwrite target if it exists. The {@code preserve}
* parameter determines if file attributes should be copied/preserved.
*/
static void copyFile(Path source, Path target, boolean prompt, boolean preserve) {
CopyOption[] options = (preserve) ?
new CopyOption[] { COPY_ATTRIBUTES, REPLACE_EXISTING } :
new CopyOption[] { REPLACE_EXISTING };
if (!prompt || Files.notExists(target) || okayToOverwrite(target)) {
try {
Files.copy(source, target, options);
} catch (IOException x) {
System.err.format("Unable to copy: %s: %s%n", source, x);
}
}
}
/**
* A {@code FileVisitor} that copies a file-tree ("cp -r")
*/
static class TreeCopier implements FileVisitor<Path> {
private final Path source;
private final Path target;
private final boolean prompt;
private final boolean preserve;
TreeCopier(Path source, Path target, boolean prompt, boolean preserve) {
this.source = source;
this.target = target;
this.prompt = prompt;
this.preserve = preserve;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
// before visiting entries in a directory we copy the directory
// (okay if directory already exists).
CopyOption[] options = (preserve) ?
new CopyOption[] { COPY_ATTRIBUTES } : new CopyOption[0];
Path newdir = target.resolve(source.relativize(dir));
try {
Files.copy(dir, newdir, options);
} catch (FileAlreadyExistsException x) {
// ignore
} catch (IOException x) {
System.err.format("Unable to create: %s: %s%n", newdir, x);
return SKIP_SUBTREE;
}
return CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
copyFile(file, target.resolve(source.relativize(file)),
prompt, preserve);
return CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
// fix up modification time of directory when done
if (exc == null && preserve) {
Path newdir = target.resolve(source.relativize(dir));
try {
FileTime time = Files.getLastModifiedTime(dir);
Files.setLastModifiedTime(newdir, time);
} catch (IOException x) {
System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
}
}
return CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
if (exc instanceof FileSystemLoopException) {
System.err.println("cycle detected: " + file);
} else {
System.err.format("Unable to copy: %s: %s%n", file, exc);
}
return CONTINUE;
}
}
static void usage() {
System.err.println("java Copy [-ip] source... target");
System.err.println("java Copy -r [-ip] source-dir... target");
System.exit(-1);
}
public static void main(String[] args) throws IOException {
boolean recursive = false;
boolean prompt = false;
boolean preserve = false;
// 处理操作
int argi = 0;
while (argi < args.length) {
String arg = args[argi];
if (!arg.startsWith("-"))
break;
if (arg.length() < 2)
usage();
for (int i=1; i<arg.length(); i++) {
char c = arg.charAt(i);
switch (c) {
case 'r' : recursive = true; break;
case 'i' : prompt = true; break;
case 'p' : preserve = true; break;
default : usage();
}
}
argi++;
}
// 剩下的参数是源文件和目标位置
int remaining = args.length - argi;
if (remaining < 2)
usage();
Path[] source = new Path[remaining-1];
int i=0;
while (remaining > 1) {
source[i++] = Paths.get(args[argi++]);
remaining--;
}
Path target = Paths.get(args[argi]);
// 检查target是否为一个目录
boolean isDir = Files.isDirectory(target);
// 复制每个source文件/目录到target
for (i=0; i<source.length; i++) {
Path dest = (isDir) ? target.resolve(source[i].getFileName()) : target;
if (recursive) {
// follow links when copying files
EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
TreeCopier tc = new TreeCopier(source[i], dest, prompt, preserve);
Files.walkFileTree(source[i], opts, Integer.MAX_VALUE, tc);
} else {
// 不是递归的,所以source不能是目录
if (Files.isDirectory(source[i])) {
System.err.format("%s: is a directory%n", source[i]);
continue;
}
copyFile(source[i], dest, prompt, preserve);
}
}
}
}
2.3 磁盘使用文件NIO示例
本例以类似于df命令的方式打印磁盘空间信息。
public class DiskUsage {
static final long K = 1024;
static void printFileStore(FileStore store) throws IOException {
long total = store.getTotalSpace() / K;
long used = (store.getTotalSpace() - store.getUnallocatedSpace()) / K;
long avail = store.getUsableSpace() / K;
String s = store.toString();
if (s.length() > 20) {
System.out.println(s);
s = "";
}
System.out.format("%-20s %12d %12d %12d\n", s, total, used, avail);
}
public static void main(String[] args) throws IOException {
System.out.format("%-20s %12s %12s %12s\n", "Filesystem", "kbytes", "used", "avail");
if (args.length == 0) {
FileSystem fs = FileSystems.getDefault();
for (FileStore store: fs.getFileStores()) {
printFileStore(store);
}
} else {
for (String file: args) {
FileStore store = Files.getFileStore(Paths.get(file));
printFileStore(store);
}
}
}
}
2.4 自定义文件属性文件NIO示例
public class Xdd {
static void usage() {
System.out.println("Usage: java Xdd <file>");
System.out.println(" java Xdd -set <name>=<value> <file>");
System.out.println(" java Xdd -get <name> <file>");
System.out.println(" java Xdd -del <name> <file>");
System.exit(-1);
}
public static void main(String[] args) throws IOException {
// 1个或3个入参
if (args.length != 1 && args.length != 3)
usage();
Path file = (args.length == 1) ? Paths.get(args[0])
: Paths.get(args[2]);
// 检查文件存储是否支持用户定义的属性
FileStore store = Files.getFileStore(file);
if (!store
.supportsFileAttributeView(UserDefinedFileAttributeView.class)) {
System.err.format(
"UserDefinedFileAttributeView not supported on %s\n", store);
System.exit(-1);
}
UserDefinedFileAttributeView view = Files.getFileAttributeView(file,
UserDefinedFileAttributeView.class);
// 列出用户定义的属性
if (args.length == 1) {
System.out.println(" Size Name");
System.out
.println("-------- --------------------------------------");
for (String name : view.list()) {
System.out.format("%8d %s\n", view.size(name), name);
}
return;
}
// 添加/替换文件的用户定义属性
if (args[0].equals("-set")) {
// name=value
String[] s = args[1].split("=");
if (s.length != 2)
usage();
String name = s[0];
String value = s[1];
view.write(name, Charset.defaultCharset().encode(value));
return;
}
// 打印文件的用户定义属性的值
if (args[0].equals("-get")) {
String name = args[1];
int size = view.size(name);
ByteBuffer buf = ByteBuffer.allocateDirect(size);
view.read(name, buf);
buf.flip();
System.out.println(Charset.defaultCharset().decode(buf).toString());
return;
}
// 删除文件的用户定义属性
if (args[0].equals("-del")) {
view.delete(args[1]);
return;
}
// option not recognized
usage();
}
}
三、Buffers
它们是特定原始类型的固定数据量的容器。详细内容,可以查看 java.nio 或 Table 9-1.
Table 9-1 Buffer Classes
Buffer Class | Description |
---|---|
Buffer | Base class for buffer classes. |
ByteBuffer | Buffer for bytes. |
MappedByteBuffer | Buffer for bytes that is mapped to a file. |
CharBuffer | Buffer for the char data type. |
DoubleBuffer | Buffer for the double data type. |
FloatBuffer | Buffer for the float data type. |
IntBuffer | Buffer for the int data type. |
LongBuffer | Buffer for the long data type. |
ShortBuffer | Buffer for the short data type. |
四、Charsets
它们是16位Unicode字符序列和字节序列之间的命名映射。
对字符集的支持包括解码器和编码器,它们在字节和Unicode字符之间进行转换。
详细内容,可以查看java.nio.charset 包和 Table 9-2.
Table 9-2 Charset Classes
Charset Class | Description |
---|---|
Charset | 字符和字节之间的命名映射,例如US-ASCII和UTF-8。 |
CharsetDecoder | 将字节解码为字符。 |
CharsetEncoder | 将字符编码为字节。 |
CoderResult | 描述解码器或编码器的结果状态。 |
CodingErrorAction | 描述检测到编码错误时要采取的操作。 |
五、Channels
它们表示对实体(如硬件设备、文件、网络套接字或能够执行一个或多个不同I/O操作(如读或写)的程序组件)的开放连接。详细内容,可以查看java.nio.channels package and Table 9-3.
Table 9-3 Channel Interfaces and Classes
Channel Interface or Class | Description |
---|---|
Channel | Channel接口和类的基本接口。 |
ReadableByteChannel | 可以读取字节的Channel。 |
ScatteringByteChannel | 一种可以将字节读入缓冲区序列的通道。在单个调用中,分散读操作将一个字节序列读入一个或多个给定的缓冲区序列。 |
WritableByteChannel | 可以写入字节的Channel。 |
GatheringByteChannel | 一种可以从一系列缓冲区写入字节的Channel。 收集写入操作在单个调用中从给定的缓冲区序列中的一个或多个写入字节序列。 |
ByteChannel | 一种可以读写字节的通道。它统一了ReadableByteChannel 和WritableByteChannel。 |
SeekableByteChannel | 一种字节通道,它保持当前位置并允许更改位置。一个可查找的字节通道连接到一个实体,通常是一个文件,它包含一个可读可写的可变长度字节序列。 |
AsynchronousChannel | 支持异步I/O操作的Channel。 |
AsynchronousByteChannel | 可以读写字节的异步Channel。 |
NetworkChannel | 连接网络套接字的Channel。 |
MulticastChannel | 一种支持IP (Internet Protocol)多播的网络Channel。 IP多播是指将IP数据报传输给一个由单一目的地址标识的0个或多个主机组成的组的成员。 |
FileChannel | 用于读取、写入、映射和操作文件的Channel。这是一个连接到文件的SeekableByteChannel。 |
SelectableChannel | 一种可以通过选择器多路复用的信道。 多路复用是在一个通道中处理多个I/O操作的能力。可选择的通道可以设置为阻塞模式或非阻塞模式。在阻塞模式下,通道上调用的每个I/O操作都会阻塞,直到它完成为止。在非阻塞模式下,I/O操作将永远不会阻塞,并且可能传输比请求更少的字节,或者可能根本没有字节。 |
DatagramChannel | 一种可选择的Channel,可以发送和接收UDP(用户数据报协议)数据包。 您可以创建具有不同协议族的数据报Channel:
|
Pipe.SinkChannel | 一种通道,表示管道的可写端。 Pipe是一对通道:一个可写的接收器通道和一个可读的源Channel。 |
Pipe.SourceChannel | 表示管道可读端的Channel。 |
ServerSocketChannel | 面向流监听套接字的可选Channel。 与数据报通道一样,您可以创建用于Internet Protocol套接字或Unix Domain套接字的服务器套接字Channel。 |
SocketChannel | 面向流连接套接字的可选Channel。 与数据报Channel一样,您可以为Internet Protocol套接字或Unix Domain套接字创建套接字Channel。 |
AsynchronousFileChannel | 用于读取、写入和操作文件的异步Channel。 |
AsynchronousSocketChannel | 面向流连接套接字的异步Channel。 |
AsynchronousServerSocketChannel | 面向流监听套接字的异步Channel。 |