Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。ava IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
一.IO
Java的IO是面向流的,以输入输出流为基础,通过装饰器模式衍生处多种API,以适应复杂的上层应用需求。字节/字符流,文件/对象流等。进一步的,IO层面也提供了文件系统API,以及字节流和字符流的转换API。
public class Hello{
public static void main(String[] args){
try {
String hello= new String( "hello word!");
byte[] byteArray= hello.getBytes();
File file= new File( "/home/charlie/test.txt");
//字节流断点调试
OutputStream os= new FileOutputStream(file);
os.write( byteArray);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
java\io\FileOutputStream.java
class FileOutputStream extends OutputStream{
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, fdAccess.getAppend(fd));
}
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
}
jdk/src/java.base/unix/native/libjava/FileOutputStream_md.c
JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_writeBytes(JNIEnv *env,
jobject this, jbyteArray bytes, jint off, jint len, jboolean append) {
writeBytes(env, this, bytes, off, len, append, fos_fd);
}
在虚拟机层面HotSot提供了与各操作系统IO流读写接口,使得上层API不必考虑诸多细节。
二.NIO
Java的NIO是基于缓冲和通道的。相比于IO,NIO提供了各类channel和缓冲API来满足上层应用的复杂需求。此外还提供了charset字符编解码集。和基于NIO的文件系统API。
public class Hello{
public static void main(String[] args){
try {
//通道
RandomAccessFile aFile = aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//缓冲
ByteBuffer buf = ByteBuffer.allocate(48);
//读缓冲
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
java.nio提供了通道,文件系统,字符集SPI。
java\io\RandomAccessFile.java
public final FileChannel getChannel() {
FileChannel fc = this.channel;
this.channel = fc = FileChannelImpl.open(fd, path, true,
rw, false, this);
return fc;
}
sun\nio\ch\FileChannelImpl.java
public static FileChannel open(FileDescriptor fd, String path,
boolean readable, boolean writable,
boolean direct, Object parent)
{
return new FileChannelImpl(fd, path, readable, writable, direct, parent);
}
而sun.nio提供了channel,字符集,文件系统更详细的实现。如FileChannel 的实现 FileChannelImpl
sun\nio\ch\FileChannelImpl.java
public int read(ByteBuffer dst) throws IOException {
ensureOpen();
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return 0;
do {
n = IOUtil.read(fd, dst, -1, direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
sun\nio\ch\IOUtil.java
static int read(FileDescriptor fd, ByteBuffer dst, long position,
boolean directIO, int alignment, NativeDispatcher nd)
throws IOException
{
try {
int n = readIntoNativeBuffer(fd, bb, position, directIO, alignment,nd);
bb.flip();
if (n > 0)
dst.put(bb);
return n;
} finally {
Util.offerFirstTemporaryDirectBuffer(bb);
}
}
sun\nio\ch\IOUtil.java
private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,
long position, boolean directIO,
int alignment, NativeDispatcher nd)
throws IOException
{
int n = 0;
if (position != -1) {
n = nd.pread(fd, ((DirectBuffer)bb).address() + pos, rem, position);
} else {
n = nd.read(fd, ((DirectBuffer)bb).address() + pos, rem);
}
if (n > 0)
bb.position(pos + n);
return n;
}
jdk/src/java.base/unix/classes/sun/nio/ch/SocketDispatcher.java
class SocketDispatcher extends NativeDispatcher
{
int read(FileDescriptor fd, long address, int len) throws IOException {
return FileDispatcherImpl.read0(fd, address, len);
}
}
jdk/src/java.base/unix/native/libnio/ch/FileDispatcherImpl.c
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_read0(JNIEnv *env, jclass clazz,
jobject fdo, jlong address, jint len)
{
jint fd = fdval(env, fdo);
void *buf = (void *)jlong_to_ptr(address);
return convertReturnVal(env, read(fd, buf, len), JNI_TRUE);
}
除此之外,NIO的Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便,要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。