`
dyllove98
  • 浏览: 1405683 次
  • 性别: Icon_minigender_1
  • 来自: 济南
博客专栏
73a48ce3-d397-3b94-9f5d-49eb2ab017ab
Eclipse Rcp/R...
浏览量:39063
4322ac12-0ba9-3ac3-a3cf-b2f587fdfd3f
项目管理checkList...
浏览量:80075
4fb6ad91-52a6-307a-9e4f-816b4a7ce416
哲理故事与管理之道
浏览量:133168
社区版块
存档分类
最新评论

Java NIO基础

 
阅读更多

我们到底能走多远系列(17)

扯淡:

长期接触所谓web框架的企业级应用的开发者们,不知道你们有没有这样的感受,几年之后,发现:路越走越窄,学的东西大多是表层的,编程的技巧没有太大的改变,接触大量的所谓框架也写不出一个核心的模块,学习了框架的原理后也不会很好的设计,大量调用别人的库函数感觉看得懂业务的人都可以写。

我觉得作为从事编码行业的我们,埋头苦干是必备的素质,但是抬头思考却是核心的竞争力,因为前者是进步的一个条件,而后者则是进步的原动力。

这里我斗胆提出一个自己想法:假如你没有进入一个类似金饭碗的公司,那么在你工作5年内至少以1年的频率进行面试经历,我不是指一年平率的跳槽,是面试,面试可以让你知道目前其他公司使用的技术方向,让你知道你的水平,以及你期望的岗位距离有多远,总之残酷的面试可以让你认清自己,从而不断重构自己,不断更新自己,让今天的自己优于昨天的自己。

假如还有学习的动力,这样的方式是不错的选择,还有一个前提是:你必须是一个有勇气推翻重来的人。

主题:

关于NIO的基础知识博客园的文章:直达车

1,关于缓冲区:

NIO (No-blocking I/O)从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供非阻塞I/O操作的API被引入。

为什么说是基于缓冲区呢?因为NIO中使用的数据类型都是有缓冲功能的。

数据类型如下:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

不知道有没有朋友对缓冲这个有点疑惑呢?所谓的缓冲的就是:比如一百个箱子要从A搬到B只能一个个般,缓冲区就相当于来了辆三轮车,从A搬10个盒子到三轮车,运到B,在把10个盒子搬给B。就是这么个过程。如果你接触过BufferedInputStream的源码,那么就比较直观的看到所谓的Buffer,就是一个new出来的byte数组,了解详细请 猛击

上面7个数据类型其实就是对各自的基本类型数组的封装,比如ByteBuffer就是对Byte[]的封装,CharBuffer就是Char[]的封装,封装后对外提供才做他们的方法。

ByteBuffer为例,来看下它的源码:

我们通过allocate方法取得ByteBuffer的:

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
        //调用HeapByteBuffer的构造函数
    return new HeapByteBuffer(capacity, capacity);
    }

HeapByteBuffer的构造函数:

 HeapByteBuffer(int cap, int lim) {        // package-private
        // HeapByteBuffer的super又是ByteBuffer
        // new  byte[] 在这呢,这就所谓开辟的缓存区啦
    super(-1, 0, lim, cap, new byte[cap], 0);
    }

ByteBuffer的构造函数:

    ByteBuffer(int mark, int pos, int lim, int cap,    // package-private
         byte[] hb, int offset)
    {
        // ByteBuffer的super是Buffer类
    super(mark, pos, lim, cap);
        // hb就是new出来的byte数组,从此 ByteBuffer就由维护这个数组了
    this.hb = hb;
    this.offset = offset;
    }

Buffer类的构造函数做什么呢?

这个构造函数的作用是初始化Buffer的状态变量(后面会了解到),因为上面7个基本类型的Buffer数据类型都继承Buffer类,他们的状态变量统一归Buffer类管理,继承带来的好处啊。

    Buffer(int mark, int pos, int lim, int cap) {    // package-private
    if (cap < 0)
        throw new IllegalArgumentException();
    // 设置capacity
    this.capacity = cap;
    //设置limit
    limit(lim);
    //设置position
    position(pos);
    if (mark >= 0) {
        if (mark > pos)
        throw new IllegalArgumentException();
        this.mark = mark;
    }
    }

至此,我想我们应该对所谓的缓冲区没有什么疑惑了吧。

2,阻塞IO和非阻塞IO

网上比较好解释如下:

Java 传统的IO操作都是阻塞式的(blocking I/O),如果有socket的编程基础,你会接触过堵塞socket和非堵塞socket,堵塞socket就是在accept、read、write等IO操作的的时候,如果没有可用符合条件的资源,不马上返回,一直等待直到有资源为止。而非堵塞socket则是在执行select的时候,当没有资源的时候堵塞,当有符合资源的时候,返回一个信号,然后程序就可以执行accept、read、write等操作,一般来说,如果使用堵塞socket,通常我们通常开一个线程accept socket,当读完这次socket请求的时候,开一个单独的线程处理这个socket请求;如果使用非堵塞socket,通常是只有一个线程,一开始是select状,当有信号的时候可以通过 可以通过多路复用(Multiplexing)技术传递给一个指定的线程池来处理请求,然后原来的线程继续select状态。 最简单的多路复用技术可以通过java管道(Pipe)来实现。换句话说,如果客户端的并发请求很大的时候,我们可以使用少于客户端并发请求的线程数来处理这些请求,而这些来不及立即处理的请求会被阻塞在java管道或者队列里面,等待线程池的处理。请求 听起来很复杂,在这个架构当道的java 世界里,现在已经有很多优秀的NIO的架构方便开发者使用,比如Grizzly,Apache Mina等等,如果你对如何编写高性能的网络服务器有兴趣,你可以研读这些源代码。

3,结合tomcat使用NIO来理解

在web服务器上阻塞IO(BIO)与NIO一个比较重要的不同是,我们使用BIO的时候往往会为每一个web请求引入多线程,每个web请求一个单独的线程,所以并发量一旦上去了,线程数就上去了,CPU就忙着线程切换,所以BIO不合适高吞吐量、高可伸缩的web服务器;而NIO则是使用单线程(单个CPU)或者只使用少量的多线程(多CPU)来接受Socket,而由线程池来处理堵塞在pipe或者队列里的请求.这样的话,只要OS可以接受TCP的连接,web服务器就可以处理该请求。大大提高了web服务器的可伸缩性。

从Tomcat6.0以后, Java开发者很容易就可以是用NIO的技术来提升tomcat的并发处理能力。

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

修改成:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
               connectionTimeout="20000"
               redirectPort="8443" />

然后启动服务器,你会看到org.apache.coyote.http11.Http11NioProtocol start的信息,表示NIO已经启动

从tomcat使用NIO这一点来看,如果我们从事java服务器的开发,肯定是离不开NIO的。

4,状态变量

状态变量的解释在最前面推荐的博文中已经有非常好的解释了,这里是学习下源码:

三个值指定缓冲区的状态:

  • position 它指定了下一个字节将放到数组的哪一个元素中。
  • limit 表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
  • capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的层数组的容量。

Position settingflip()函数源码:

    public final Buffer flip() {
    // limit 置成position位置
    limit = position;
    // position 置0
    position = 0;
    mark = -1;
    return this;
    }

flip()执行后的图:

Position advanced to 5, limit unchanged

 

clear()源码:

  // 注意这里的妙处是我们对数组的处理知识改变状态变量,而没有真正去clear数组中的内容,
    // 因为真的去删数组中的内容,效率会很慢,而且也没有必要这样做
    public final Buffer clear() {
    // position置为0
    position = 0;
    // limit调成最大
    limit = capacity;
    mark = -1;
    return this;
    }

我们可以通过下面的方式取得或设置状态变量:

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);  
buffer.position();
buffer.limit();
buffer.capacity();
buffer.mark();
buffer.position( 3 );
buffer.limit( 7 );

5,基础例子

1,copy文件例子:

package code.stu.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
// 拷贝文件例子
public class CopyFileByNio {
    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream("");
        FileOutputStream out = new FileOutputStream("");
        // 得到缓冲区
        ByteBuffer bb = ByteBuffer.allocate(1024);
        // 通道
        FileChannel fi = in.getChannel();
        FileChannel fo = out.getChannel();
        while(fi.read(bb) != -1){
            //System.out.println(bb.position());
            // 写之前先调flip
            bb.flip();
            fo.write(bb); 
            // 写之后再调clear
            bb.clear();
        }
        in.close();
        out.close();
    }
}

2,网上传输文件的例子:

a,服务器

package code.stu.nio.socket;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
  
public class MyServer {  
  
      
    public static void main(String[] args) {  
        Selector selector = null;  
        ServerSocketChannel serverSocketChannel = null;  
          
        try {  
            // Selector for incoming time requests  
            selector = Selector.open();  
  
            // Create a new server socket and set to non blocking mode  
            serverSocketChannel = ServerSocketChannel.open();  
            serverSocketChannel.configureBlocking(false);  
              
            // Bind the server socket to the local host and port  
            serverSocketChannel.socket().setReuseAddress(true);  
            serverSocketChannel.socket().bind(new InetSocketAddress(10000));  
              
            // Register accepts on the server socket with the selector. This  
            // step tells the selector that the socket wants to be put on the  
            // ready list when accept operations occur, so allowing multiplexed  
            // non-blocking I/O to take place.  
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
      
            // Here's where everything happens. The select method will  
            // return when any operations registered above have occurred, the  
            // thread has been interrupted, etc.  
            while (selector.select() > 0) {  
                // Someone is ready for I/O, get the ready keys  
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();  
      
                // Walk through the ready keys collection and process date requests.  
                while (it.hasNext()) {  
                    SelectionKey readyKey = it.next();  
                    it.remove();  
                      
                    // The key indexes into the selector so you  
                    // can retrieve the socket that's ready for I/O  
                    doit((ServerSocketChannel) readyKey.channel());  
                }  
            }  
        } catch (ClosedChannelException ex) {  
            System.out.println(ex);
        } catch (IOException ex) {  
             System.out.println(ex);
        } finally {  
            try {  
                selector.close();  
            } catch(Exception ex) {}  
            try {  
                serverSocketChannel.close();  
            } catch(Exception ex) {}  
        }  
    }  
  
    private static void doit(final ServerSocketChannel serverSocketChannel) throws IOException {  
        SocketChannel socketChannel = null;  
        try {  
            socketChannel = serverSocketChannel.accept();  
            // 做一个接文件的操作
            receiveFile(socketChannel, new File("E:\\test\\test.txt"));  
        } finally {  
            try {  
                socketChannel.close();  
            } catch(Exception ex) {}  
        }  
          
    }  
      
    private static void receiveFile(SocketChannel socketChannel, File file) throws IOException {  
        FileOutputStream fos = null;  
        FileChannel channel = null;  
          
        try {  
            // 用于把数据从管道上拿下来变成文件
            fos = new FileOutputStream(file);  
            channel = fos.getChannel();  
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);  
            int size = 0;
            while ((size = socketChannel.read(buffer)) != -1) {  
                buffer.flip();  
                if (size > 0) {  
                    buffer.limit(size);  
                    channel.write(buffer);  
                    buffer.clear();  
                }  
            }  
        } finally {  
            try {  
                channel.close();  
            } catch(Exception ex) {}  
            try {  
                fos.close();  
            } catch(Exception ex) {}  
        }  
    }  
}  

客户端:

package code.stu.nio.socket;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
  
public class MyClient {  
  
      
    public static void main(String[] args) throws Exception {  
        new Thread(new MyRunnable()).start(); // 建立个线程来处理
    }  
      
    private static final class MyRunnable implements Runnable {  
        public void run() {  
            SocketChannel socketChannel = null;  
            try {  
                socketChannel = SocketChannel.open();  
                SocketAddress socketAddress = new InetSocketAddress("localhost", 10000);  
                socketChannel.connect(socketAddress);  
                // 发送数据
                sendFile(socketChannel, new File("D:\\我的文档\\test.txt"));  
            } catch (Exception ex) {  
                System.out.println(ex); 
            } finally {  
                try {  
                    socketChannel.close();  
                } catch(Exception ex) {}  
            }  
        }  
  
        private void sendFile(SocketChannel socketChannel, File file) throws IOException {  
            FileInputStream fis = null;  
            FileChannel channel = null;  
            try {  
                // 把文件编程流再方法通道上去
                fis = new FileInputStream(file);  
                channel = fis.getChannel();  
                ByteBuffer buffer = ByteBuffer.allocateDirect(1024);  
                int size = 0;  
                while ((size = channel.read(buffer)) != -1) {  
                    buffer.rewind();  
                    buffer.limit(size);  
                    socketChannel.write(buffer);  
                    buffer.clear();  
                }  
                socketChannel.socket().shutdownOutput();  
            } finally {  
                try {  
                    channel.close();  
                } catch(Exception ex) {}  
                try {  
                    fis.close();  
                } catch(Exception ex) {}  
            }  
        }  
    }  
}  

总结:

1,就目前来看原IO的操作已经可以被NIO代替,在java服务器端的开发中使用多线程技术结合NIO,是一个比较好的解决方案。
2,大家也看到了在处理new出来的缓存是,不移动删除内容,只需用标记的方式来实现读写操作,大大提高了效率,印象深刻

 

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。
共勉。

分享到:
评论

相关推荐

    java NIO技巧及原理

    **Java NIO基础概念:** 1. **通道(Channel)**:类似于流,但可以双向传输数据,如FileChannel、SocketChannel等。 2. **缓冲区(Buffer)**:用于在通道和应用程序之间存储数据,提供了更高效的访问方式。 3. **...

    java NIO推送实例

    1. **Java NIO基础** - **通道(Channels)**:Java NIO 提供了多种通道,如文件通道、套接字通道等,它们代表不同类型的I/O操作。 - **缓冲区(Buffers)**:数据在通道和应用程序之间传输时会存储在缓冲区中,...

    java nio 聊天室源码

    1. **Java NIO基础** - **通道(Channel)**:在NIO中,数据是通过通道进行传输的,如SocketChannel、ServerSocketChannel、FileChannel等。它们是双向的,可以读也可以写。 - **缓冲区(Buffer)**:NIO的核心组件,...

    Java NIO非阻塞服务端与客户端相互通信

    1. **Java NIO基础** - **通道(Channels)**:NIO中的通道类似于传统IO的流,但它们可以同时读写,并且支持非阻塞操作。 - **缓冲区(Buffers)**:NIO中的数据操作都在缓冲区上进行,这是NIO的主要特性之一,...

    JavaNIO服务器实例Java开发Java经验技巧共6页

    本资料"JavaNIO服务器实例Java开发Java经验技巧共6页"可能是某个Java开发者或讲师分享的一份关于如何在Java中构建NIO服务器的教程,涵盖了6个关键页面的内容。尽管具体的细节无法在此直接提供,但我们可以根据Java ...

    chacha.rar_NIO soclet java_java nio

    1. **Java NIO基础**: - **通道(Channels)**:NIO的核心组件之一,它连接到数据源或目的地,如文件、套接字、管道等。 - **缓冲区(Buffers)**:用于存储和传输数据,它们可以被多个通道共享。 - **选择器...

    Java语言基础教程-Java NIO流篇2

    Java NIO(New IO)是Java 1.4版本引入的一个新模块,是对传统IO模型的补充和扩展...通过本教程的学习,开发者不仅可以掌握NIO的基本概念,还能了解其在实际开发中的应用,为编写高效、灵活的Java程序打下坚实的基础。

    基于Java NIO实现五子棋游戏.zip

    项目涵盖了Java NIO基础、五子棋游戏规则与逻辑、Java GUI设计及多线程编程等知识点。学习者可通过实践掌握Java NIO中的Channel、Buffer和Selector的使用,理解非阻塞IO的优势。同时,通过实现五子棋的人机对战和...

    java_nio.rar_java nio

    一、Java NIO基础 1. **通道(Channel)**:在NIO中,数据读写是通过通道完成的。通道类似于流,但不同的是,通道可以同时进行读写,并且可以保持打开状态以便后续操作。常见的通道类有FileChannel、SocketChannel、...

    Large-File-Processing-master_javanio_java大文件处理_

    1. **Java NIO基础**:NIO的核心组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。通道类似于流,但支持双向传输数据;缓冲区用于存储数据,可以进行读写操作;选择器则允许单线程管理多个通道,...

    Java.NIO资源下载资源下载

    根据提供的文件信息,我们可以提取并总结出关于Java NIO(New Input/Output)的重要知识点。 ### Java NIO 概述 Java NIO 是 Java 平台的一个重要特性,首次出现在 Java 1.4 版本中。它为 Java 开发者提供了一套...

    java NIO详细教程

    - **缓冲区(Buffer)**:缓冲区是Java NIO中数据读写的基础单元。根据数据类型的不同,Java NIO 提供了多种缓冲区: - **ByteBuffer**:用于基本字节数据的缓冲。 - **CharBuffer**:用于字符数据的缓冲。 - **...

    java nio im(server+client)

    1. **NIO基础概念** - **通道(Channel)**:在NIO中,数据是通过通道进行传输的。通道类似于流,但可以同时进行读写操作。 - **缓冲区(Buffer)**:数据在传输前会先存储在缓冲区中,缓冲区提供了一种方式来管理...

    Java语言基础教程-Java NIO流篇1

    理解并熟练掌握这些内容,将为后续深入学习NIO的其他高级特性,如选择器、多路复用等打下坚实的基础。在实际开发中,NIO可以帮助我们构建更加高效、灵活的网络服务,尤其在处理大量并发连接时,NIO的优势更为明显。

    java nio基础使用示例

    以上就是Java NIO的基础使用示例。通过这种方式,服务器可以高效地处理来自多个客户端的连接请求,而不需要为每个客户端创建单独的线程,降低了系统的资源消耗。值得注意的是,Java NIO的API相对复杂,使用时需要...

Global site tag (gtag.js) - Google Analytics