Java网络编程入门(三)

前言:这是本人在慕课网上学习 一站式学习Java网络编程 全面理解BIO/NIO/AIO 时所做的笔记,供本人复习之用,本文主要讲述NIO的概念,并比较下不同类型IO(阻塞式、非阻塞式等)的速度,不保证一定准确。

目录

第一章 NIO概述

1.1 由来

1.2 基本概念

第二章 Buffer

2.1 写模式

2.2 读模式

第三章 Channel

第四章 多方法实现本地文件拷贝

第五章 Selector

5.1 channel的状态

5.2 基本操作


第一章 NIO概述

1.1 由来

BIO中的阻塞,由于流的读写是阻塞式调用的,所以多人聊天室为了同时支持多个人互相发送消息,我们就没有办法使用一个线程来处理多个客户端流的输入和输出,因为如果有一个用户长时间无法通过这个流进行读和写,那么这个流就会阻塞整个线程。

ServerSocket.accept()
InputStream.read(),OutputStream.write()

为了避免一个阻塞所有人,我们在BIO中不得不开启多条线程,所以我们就考虑找一个不会阻塞的IO,也就是NIO。

1.2 基本概念

在Nio中不再使用Stream这些类,使用Channel进行替代,Stream是有方向性的,输入流只能写入数据,输出流只能输出数据,可以Channel是双向的,既可以写入数据,也可以读取数据。而且流的读写都是阻塞式的,Channel既提供了阻塞式的,也提供了非阻塞式的读写。

使用Selector来监控多条Channel,每条Channel的读写是非阻塞式的,所以我们需要经常检查Channel的状态,就是看Channel里面有没有数据,然后进行读取或者输出操作,如果自己查询太麻烦了,所以使用Selector来监视其状态。

所以使用NIO的话我们就可以在一个线程里处理多个Channel I/O,如果线程数量超过了处理器的数量,就一定会出现如线程上下文切换的东西,每次切换线程要保存线程的状态,过一段时间再加载线程的状态,这个占用系统资源和花费时间,而且每个线程也占用一定的内存,线程多了也会有一定的负担。

第二章 Buffer

向Channel读或者写数据必须通过Buffer完成,当向Channel中写数据的时候,必须要写到Buffer里面,当要向Channel里读数据的时候,也一定是要从Buffer中读出数据,所以所有对应的操作背后都离不开Buffer。

Buffer也是支持双向操作的,同一个Buffer可以向里面写,也可以向里面读。

2.1 写模式

capacity代表buffer最大的容量,position表示目前所在的位置,limit指向的是和capacity指向的相同的位置。

 当写入一部分数据后,会变成这样

2.2 读模式

当写入部分数据后,我们要将数据读取出来,需要调用flip()函数,在flip()函数中,会做如下操作,将position移动会初始位置,将limit指向刚才position的位置,现在postion和limit之间就是刚才写入的数据了,我们也就可以读取数据了,当然最多读到limit。

读取也分两种情况,第一种是全部读完,读完之后我们要继续向其中写入数据了,那我们要调用clear函数,clrear函数会将postion移动到初始位置,然后将limit移动到capacity位置,我们也就正式将读模式切换到了写模式。值得注意是虽然名字叫clear(),但它并没有真的将数据清除,而是仅仅移动了指针。

还有一种情况是没有读完,只读取了一部分,来不及读完,就要写入新的数据了,希望写入后还能读到刚才的数据,这时需要调用compact()函数,compact函数会将还没读取的数据拷贝到 buffer最开始的位置,拷贝完成后将position指针指向刚刚超过未读部分的数据,然后就可以正常的写入数据了,等再反转后就可以再都出来了。

 

 

 

 

第三章 Channel

Channel不止可以和Buffer进行串数,还可以和Channel进行传输,每个Channel都可以向其它Channel传输或者接收数据。

 Channel有以下几种,File是和文件操作相关,ServerSocket和Socket对应于之前的服务端和客户端的Socket。

第四章 多方法实现本地文件拷贝

用四种不同的方法拷贝文件测试效率,分别是:

直接输入输出流拷贝(一个一个字节拷贝,其实不用缓冲也可以每次读1024个字节,这里老师是为了演示不同IO实现)

用带缓存的输入输出流拷贝

用上面说到的nio拷贝,需要buffer

用nio拷贝,不要buffer

package bio.demo;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @author ZhangChen
 **/

interface FileCopyRunner{
    void copyFile(File source,File target);
}
public class FileCopyDemo {
    private static final int ROUNDS = 5;

    public  static void close(Closeable closeable){
        if(closeable!=null){
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private static void benchmark(FileCopyRunner test,File source,File target){
        long elapsed = 0L;
        for(int i=0;i<ROUNDS;i++){
            long startTime = System.currentTimeMillis();
            test.copyFile(source,target);
            elapsed+= System.currentTimeMillis()-startTime;
            target.delete();
        }
        System.out.println(test + ": "+elapsed/ROUNDS);

    }
    public static void main(String[] args) {
        FileCopyRunner noBufferStreamCopy =new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                InputStream fin = null;
                OutputStream fout = null;
                try {
                    fin = new FileInputStream(source);
                    fout = new FileOutputStream(target);
                    int result ;
                    while((result=fin.read())!=-1){
                        fout.write(result);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }

            @Override
            public String toString() {
                return "noBufferStreamCopy";
            }
        };
        FileCopyRunner bufferStreamCopy;
        bufferStreamCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                InputStream fin = null;
                OutputStream fout = null;
                try {
                    fin = new BufferedInputStream(new FileInputStream(source));
                    fout = new BufferedOutputStream(new FileOutputStream(target));
                    //将内容读入缓冲区
                    byte[] buffer = new byte[1024];
                    int result ;
                    while((result=fin.read(buffer))!=-1){
                        fout.write(buffer,0,result);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }

            @Override
            public String toString() {
                return "bufferStreamCopy";
            }
        };

        FileCopyRunner nioBufferCopy;
        nioBufferCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                FileChannel fin = null;
                FileChannel fout = null;
                try {
                    fin = new FileInputStream(source).getChannel();
                    fout = new FileOutputStream(source).getChannel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    while(fin.read(buffer)!=-1){
                        buffer.flip();
                        //不一定会把数据都读取出来write进去,要判断是否有剩余,如果有则一直写
                        while(buffer.hasRemaining()){
                            fout.write(buffer);
                        }
                        buffer.clear();
                    }

                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }

            }

            @Override
            public String toString() {
                return "nioBufferCopy";
            }
        };


        FileCopyRunner nioTransferCopy;

        nioTransferCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                FileChannel fin = null;
                FileChannel fout = null;
                try {
                    fin = new FileInputStream(source).getChannel();
                    fout = new FileOutputStream(source).getChannel();
                    //同样,tranfer也不能保证一次全部写完,要做字节统计
                    long transferred = 0;
                    long size = fin.size();
                    while(transferred!=size){
                        transferred+=fin.transferTo(0,size,fout);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }

            @Override
            public String toString() {
                return "nioTransferCopy";
            }
        };

        File smallFile = new File("C:\\Users\\61037\\Desktop\\test.jpg");
        File smallFileCopy = new File("C:\\Users\\61037\\Desktop\\new.jpg");
        System.out.println("---copy small file---");
        benchmark(noBufferStreamCopy,smallFile,smallFileCopy);
        benchmark(bufferStreamCopy,smallFile,smallFileCopy);
        benchmark(nioBufferCopy,smallFile,smallFileCopy);
        benchmark(nioTransferCopy,smallFile,smallFileCopy);
    }
}

最后的效率问题我就没在本地测试,借用一下视频老师的测试结果

 

400KB

10或11个 (单位没听清,感觉是MB)

500个 

 

总结:可以看出来速度差异不大,这是因为Java后面用nio或者更有效率的实现方法去优化了之前的这些IO操作,所以速度提升了很多,所以看不到传统IO和NIO的太大的区别。

 

第五章 Selector

5.1 channel的状态

我们可以选择Channel进行非阻塞性的读写,但是却要不停的访问Channel看看有没有数据,能不能进行读写,自己实现比较繁琐,所以Java提供了Selector,帮我们监控多个通道的状态。

首先我们要把通道注册在Selector上面,注册后我们只需询问selector就可以得到答案了。

每个通道在不同的时间会处于不同的状态,这个状态不是一成不变的,各种外部事件的发生都会导致通道的状态发生变化。

客户端的SocketChannel和远端建立了连接,这就使SocketChannel处于了connect状态,对应的在服务器端ServerSocketChannel接受了一个客户端的连接,它就处于accept状态。

channel中有可读取的数据后,channel处于read状态,当channel处于可以向其中写入数据的状态,这就是write状态。

channel也可以不处于任何状态,没什么操作可以操作channel。

5.2 基本操作

当把channel注册在selector上后,我么们会得到一个SelectionKey对象,SelectionKey类似于一个ID,每一个在selector上注册的channel,都对应于一个SelectionKey。

通过SelectionKey,我们可以调用interestOps(),获取我们注册在selecor上的状态。我们注册时候可以选择我们感兴趣的状态,如只选择read状态,也就是只有channel可读的时候才提醒我们。

通过SelectionKey,我们可以调用readyOps(),返回的是对于这个SelectionKey,它有哪些监听的状态是准备好的,即处于哪些可操作的状态下。

通过SelectionKey,我们可以调用channel,即注册到selector上的channel。

通过SelectionKey,我们可以调用selector,即channel注册的是哪个selector。

通过SelectionKey,我们可以调用attachment,对于每一个注册在selector上的channel对象,我们可以再attach上一个对象,这个对象可以是任何你认为有意义的对象,任何对于channel的操作会带来帮助或者是必须的对象。

如下图所示,我们再selector上注册了3个Channel,当没有一个处于我们感兴趣的状态时调用select返回0。

当有一个channel处于我们感兴趣状态时,再调用select状态时,select就会返回1,我们就可以调用其它的selector函数得到这个channel的SelectionKey,我们可以通过这个SelectionKey得到channel对象等等。

当操作完channel后,selector不会将channel置为不可操作状态,需要手动把channel重置为不可操作状态,如果后面有两个channel进入状态,那么我们调用select就会返回2,然后可以通过SelectionKey获取channel对象再进行其它操作。

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值