1. 什么是NIO
NIO是New I/O的简称,与旧式的基于流的I/O方法相对,从名字看,它表示新的一套Java I/O标 准。它是在Java 1.4中被纳入到JDK中的,并具有以下特性:
- NIO是基于块(Block)的,它以块为基本单位处理数据 (硬盘上存储的单位也是按Block来存储,这样性能上比基于流的方式要好一些)
- 为所有的原始类型提供(Buffer)缓存支持
- 增加通道(Channel)对象,作为新的原始 I/O 抽象
- 支持锁(我们在平时使用时经常能看到会出现一些.lock的文件,这说明有线程正在使用这把锁,当线程释放锁时,会把这个文件删除掉,这样其他线程才能继续拿到这把锁)和内存映射文件的文件访问接口
- 提供了基于Selector的异步网络I/O
java中,同步就是说多线程去访问某个方法的时候,当访问到某些共享数据是同一时刻只能有一个线程去访问
阻塞就是说当前线程被挂起,例如调用了Thread.sleep() ;
Java NIO和IO的主要区别 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。 IO NIO 面向流 面向缓冲(块) 同步阻塞IO 同步 非阻塞IO 无 选择器 面向流与面向缓冲 Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。 阻塞与非阻塞IO Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
package entryNIO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class BufferAndChannel {
public static void main(String[] args) {
String string="C:\\Users\\hp\\Desktop";
long startTime=System.currentTimeMillis();
nioCopyFile(string+"\\FileChannelImpl.java",string+"\\1.java");
System.out.println("用时:"+(System.currentTimeMillis()-startTime)+"毫秒");
System.out.println("读取完毕");
}
public static void nioCopyFile(String resource, String destination) {
ByteBuffer buffer = null;//字节缓冲区
try(//JDK 1.7 新特性 try-with-resource ,凡是实现了Closeable接口就可以用这种写法,而无需关闭流
FileInputStream fis = new FileInputStream(resource);
FileOutputStream fos = new FileOutputStream(destination);
// Used by FileInputStream.getChannel() and RandomAccessFile.getChannel()
FileChannel readChannel = fis.getChannel(); // 读文件通道
FileChannel writeChannel = fos.getChannel(); // 写文件通道
){
buffer=ByteBuffer.allocate(1024*1024); // 为字节缓冲区分配空间
int len=0;
while ((len = readChannel.read(buffer))!=-1) {
/*
反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。
在一系列通道读取或放置 操作之后,调用此方法为一系列通道写入或相对获取 操作做好准备。例如:
当将数据从一个地方传输到另一个地方时,经常将此方法与 compact 方法一起使用。
*/
buffer.flip();//反转此缓冲区
writeChannel.write(buffer); // 写入文件
buffer.clear();//清空缓冲区
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//理论上会比使用BufferedReader等快一点
package entryNIO;
import java.nio.ByteBuffer;
public class BufferAndChannel {
public static void main(String[] args) {
long startTime=System.currentTimeMillis();
try {
Ha.main(args);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("用时:"+(System.currentTimeMillis()-startTime)+"毫秒");
System.out.println("读取完毕");
}
}
class Ha{
public static void main(String[] args) throws Exception {
ByteBuffer b = ByteBuffer.allocate(15); // 15个字节大小的缓冲区
sop(b);//打印
for (int i = 0; i < 10; i++) {
// 存入10个字节数据
b.put((byte) i);
}
sop(b);
/*
该操作会重置position,通常,将buffer从写模式转换为读 模式时需要执行 flip()方法
该方法不仅重置了当前的position为0,还将limit设置到当前position的位置 。
limit的意义在于,来确定哪些数据是有意义的,换句话说,从position到limit之间的数据才是有意义的数据,
因为是上次操作的数据。所以flip操作往往是读写转换的意思。
*/
b.flip(); // 重置position,一般会重置为0
sop(b);
for (int i = 0; i < 5; i++) {
System.out.print(b.get());
}
System.out.println();
sop(b);
b.flip();
sop(b);
}
private static void sop(ByteBuffer b) {
//这里要区别下容量和上限,比如一个Buffer有15KB,那么15KB就是容量,我将5KB的文件读到Buffer中,那么上限就是5KB。
System.out.println("limit=" + b.limit() + " capacity=" + b.capacity()
+ " position=" + b.position());
}
}
将文件映射到内存
package entryNIO;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class BufferAndChannel {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
try (RandomAccessFile raf = new RandomAccessFile("C:\\FileChannelImpl.java", "rw");
FileChannel fc = raf.getChannel();) {
// 将文件映射到内存中
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, raf.length());
//byte[] byteArray=new byte[1024*1024];
while (mbb.hasRemaining()) {// between the current position and the limit.在这范围内是否还有
System.out.print((char) mbb.get());//mbb.get(dst, offset, length)
}
mbb.put(0, (byte) 98); // 修改文件
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
System.out.println("读取完毕");
}
}