概念
NIO是jdk1.4之后引入的。IO是面向流(字符流、字节流)的。NIO是面向缓冲的。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。 参考大佬的详细总结
下面主要是代码的实现
IO
读操作
public static void read(String path) throws Exception{
BufferedReader reader = null;
reader= new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)),"UTF-8"),1024);;
int result = 0;
while((result = reader.read()) != -1) {
String readLine = reader.readLine();
System.out.println(readLine);
}
if(reader != null)
reader.close();
}
reader.readLine();这行代码是阻塞的,直到有返回值的时候。
写操作
public static void write(String path) throws Exception {
File file = new File(path);
if(!file.exists())
file.createNewFile();
BufferedWriter bWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"),1024);
for(int i=0; i<10; i++) {
String wString = "写入第"+ i+"行";
if(i!=0)
bWriter.newLine();
bWriter.write(wString);
}
bWriter.flush();
bWriter.close();
}
NIO
标准输入输出NIO
读操作
public static void readNIO(String pathname) {
FileInputStream fin = null;
try {
fin = new FileInputStream(new File(pathname));
FileChannel channel = fin.getChannel();
int capacity = 1000;// 字节
ByteBuffer cache = ByteBuffer.allocate(capacity);
int length = -1;
while ((length = channel.read(cache)) != -1) {
/*
* 注意,读取后,将位置置为0,将limit置为容量, 以备下次读入到字节缓冲中,从0开始存储
*/
cache.clear();
byte[] bytes = cache.array();
String str = new String(bytes, 0, length);
System.out.println(str);
}
channel.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fin != null) {
try {
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
写操作
public static void writeNIO(String filename) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File(filename));
FileChannel channel = fos.getChannel();
ByteBuffer cache = Charset.forName("utf8").encode("你好你好你好你好你好");
int length = 0;
while ((length = channel.write(cache)) != 0) {
System.out.println("写入长度:" + length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
边读边写
public static void testReadAndWriteNIO(String pathname, String filename) {
FileInputStream fin = null;
FileOutputStream fos = null;
try {
fin = new FileInputStream(new File(pathname));
FileChannel channel = fin.getChannel();
int capacity = 100;// 字节
ByteBuffer cache = ByteBuffer.allocate(capacity);
int length = -1;
fos = new FileOutputStream(new File(filename));
FileChannel outchannel = fos.getChannel();
while ((length = channel.read(cache)) != -1) {
cache.flip();
int outlength = 0;
while ((outlength = outchannel.write(cache)) != 0) {
System.out.println("读," + length + "写," + outlength);
}
cache.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fin != null) {
try {
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
网络编程NIO
选择器
选择器提供选择执行已经就绪的任务的能力,这使得多元I/O成为了可能,就绪执行和多元选择使得单线程能够有效地同时管理多个I/O通道。
某种程度上来说,理解选择器比理解缓冲区和通道类更困难一些和复杂一些,因为涉及了三个主要的类,它们都会同时参与到这整个过程中,这里先将选择器的执行分解为几条细节:
1、创建一个或者多个可选择的通道(SelectableChannel)
2、将这些创建的通道注册到选择器对象中
3、选择器会记住开发者关心的通道,它们也会追踪对应的通道是否已经就绪
4、开发者调用一个选择器对象的select()方法,当方法从阻塞状态返回时,选择键会被更新
5、获取选择键的集合,找到当时已经就绪的通道,通过遍历这些键,开发者可以选择对已就绪的通道要做的操作
用一个线程控制选择器,可以控制多个Channel,也就是控制着多个I/O通道。如下图。
服务端
public class SelectorServer{
private static int PORT = 1234;
public static void main(String[] args) throws Exception {
// 先确定端口号
int port = PORT;
if (args != null && args.length > 0){
port = Integer.parseInt(args[0]);
}
// 打开一个ServerSocketChannel
ServerSocketChannel channel = ServerSocketChannel.open();
// 获取ServerSocketChannel绑定的Socket
ServerSocket socket = channel.socket();
// 设置ServerSocket监听的端口
socket.bind(new InetSocketAddress(port));
// 设置ServerSocketChannel为非阻塞模式
channel.configureBlocking(false);
// 打开一个选择器
Selector selector = Selector.open();
// 将ServerSocketChannel注册到选择器上去并监听accept事件
channel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 这里会发生阻塞,等待就绪的通道
int n = selector.select();
// 没有就绪的通道则什么也不做
if (n == 0){
continue;
}
// 获取SelectionKeys上已经就绪的通道的集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 遍历每一个Key
while (iterator.hasNext()){
SelectionKey sk = iterator.next();
// 通道上是否有可接受的连接
if (sk.isAcceptable()){
ServerSocketChannel ssc1 = (ServerSocketChannel)sk.channel();
SocketChannel sc = ssc1.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
// 通道上是否有数据可读
else if (sk.isReadable()){
readDataFromSocket(sk);
}
iterator.remove();
}
}
}
//缓存区
private static ByteBuffer cache = ByteBuffer.allocate(1024);
// 从通道中读取数据
protected static void readDataFromSocket(SelectionKey sk) throws Exception{
SocketChannel sc = (SocketChannel)sk.channel();
cache.clear();
while (sc.read(cache) > 0){
cache.flip();
while (cache.hasRemaining())
{
System.out.print((char)cache.get());
}
System.out.println();
cache.clear();
}
}
}
客户端代码
public class SelectorClient {
private static final String STR = "Hello World!";
private static final String REMOTE_IP = "127.0.0.1";
private static final int THREAD_COUNT = 5;
private static class NonBlockingSocketThread extends Thread{
public void run() {
try {
int port = 1234;
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);//必须为非阻塞
channel.connect(new InetSocketAddress(REMOTE_IP, port));
while (!channel.finishConnect()) {
System.out.println("同" + REMOTE_IP + "的连接正在建立,请稍等!");
Thread.sleep(10);
}
System.out.println("连接已建立,待写入内容至指定ip+端口!时间为" + System.currentTimeMillis());
String writeStr = STR + this.getName();
ByteBuffer bb = ByteBuffer.allocate(writeStr.length());
bb.put(writeStr.getBytes());
bb.flip(); // 写缓冲区的数据之前一定要先反转(flip)
channel.write(bb);
bb.clear();
channel.close();
}
catch (IOException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
NonBlockingSocketThread[] nbsts = new NonBlockingSocketThread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++)
nbsts[i] = new NonBlockingSocketThread();
for (int i = 0; i < THREAD_COUNT; i++)
nbsts[i].start();
// 一定要join保证线程代码先于sc.close()运行,否则会有AsynchronousCloseException
for (int i = 0; i < THREAD_COUNT; i++)
nbsts[i].join();
}
}
测试
先运行服务端代码,在运行客户端代码。