java--IO模型

1.IO模型的基本说明

IO模型就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能

(1)BIO

同步并阻塞(传统阻塞型),服务器实现模式为一个客户端连接一个线程,即客户端有连接请求时服务器端就需要
启动一个线程进行处理。当客户端不传送数据时,这个线程也需要等待,那么就会造成不必要的线程开销。

一般适用于客户端数量较少并且固定的架构,对服务器资源的要求比较高

(2)NIO

同步非阻塞,服务器实现模式为一个线程处理多个请求,即客户端发送的连接请求都会注册到多路复用器上,
多路复用器轮询到连接有IO请求就进行处理

适用于连接数目多且连接比较短的架构

(3)AIO

异步非阻塞,服务器实现模式为:一个有效请求一个线程,客户端的IO请求都是由OS先完成了再通知服务器
应用去启动线程进行处理

一般适用于连接数较多且连接时间较长的应用

2.BIO

(1)同步阻塞演示(客户端发送数据,服务端接收数据)

服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态
public class Server {

    public static void main(String[] args) {
        try {
            System.out.println("=======服务端启动=======");
            //1.定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //2.监听客户端的Socket连接请求
            Socket socket=ss.accept();
            //3.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个缓冲字符输入流
            BufferedReader br=new BufferedReader(new InputStreamReader(is));
            String msg;
            if((msg=br.readLine())!=null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}


public class Client {

    public static void main(String[] args) {
        try {
            //1.创建Socket对象,请求服务端的连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2.从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //3.把字节输出流包装成一个打印流
            PrintStream ps=new PrintStream(os);
            //4.把数据发送出去
            ps.println("hello 你好");
            ps.flush();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
        }
    }

}

(2)多发多收机制

public class Server {

    public static void main(String[] args) {
        try {
            System.out.println("=======服务端启动=======");
            //1.定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //2.监听客户端的Socket连接请求
            Socket socket=ss.accept();
            //3.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个缓冲字符输入流
            BufferedReader br=new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg=br.readLine())!=null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}


public class Client {

    public static void main(String[] args) {
        try {
            //1.创建Socket对象,请求服务端的连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2.从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //3.把字节输出流包装成一个打印流
            PrintStream ps=new PrintStream(os);
            Scanner sc=new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg=sc.nextLine();
                //4.把数据发送出去
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

(3)接收多个客户端

(1)每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能

(2)每个线程都会占用栈空间和CPU资源

(3)并不是每个socket都进行IO操作,无意义的线程处理

(4)客户端的并发访问增加时,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,
	线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务
public class ServerThreadReader extends Thread{

    private Socket socket;

    public ServerThreadReader(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        try {
            //从socket对象中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br=new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg=br.readLine())!=null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}


public class Server {

    public static void main(String[] args) {
        try {
            System.out.println("=======服务端启动=======");
            //1.定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //2.定义一个死循环,负责不断的接收客户端的socket连接请求
            while (true){
                Socket socket=ss.accept();
                //3.创建一个独立的线程来处理与这个客户端的socket通信需求
                new ServerThreadReader(socket).start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

public class Client1 {

    public static void main(String[] args) {
        CreateClient createClient = new CreateClient();
        createClient.client();
    }

}

public class Client2 {

    public static void main(String[] args) {
        CreateClient createClient = new CreateClient();
        createClient.client();
    }

}

public class CreateClient {

    public void client(){
        try {
            //1.创建Socket对象,请求服务端的连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2.从socket对象中获取一个字节输出流,将其包装为打印流
            PrintStream ps=new PrintStream(socket.getOutputStream());
            //3.使用循环不断地发送消息给服务端接收
            Scanner sc=new Scanner(System.in);
            while (true){
                System.out.print("请输入要发送给服务端的数据: ");
                String msg=sc.nextLine();
                //4.把数据发送出去
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

(4)伪异步IO

采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现Runnable线程
任务接口)交给后端的线程池中进行处理。由于线程池可以设置任务队列的大小和最大线程数,因此,它占用的
资源是可控的,无论多少个客户端并发访问,都不会导致资源地耗尽和宕机。

public class Server {

    public static void main(String[] args) {
        try {
            //1.注册端口
            ServerSocket ss=new ServerSocket(9999);
            //初始化一个线程池对象
            HandlerSocketServerPool pool=new HandlerSocketServerPool(3,5);
            //2.定义一个循环接收客户端的Socket连接请求
            while (true){
                Socket socket=ss.accept();
                //3.把socket对象交给一个线程池进行处理
                //把socket封装成一个任务对象交给线程池处理
                Runnable target=new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

public class ServerRunnableTarget implements Runnable{
    private Socket socket;

    public ServerRunnableTarget(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        //处理接收到客户端Socket通信需求
        try {
            //从socket对象中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br=new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg=br.readLine())!=null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

public class HandlerSocketServerPool {

    //1.创建一个线程池的成员变量,用于存储一个线程池对象
    //ExecutorService是一个线程池接口,主要负责任务执行管理
    private ExecutorService executorService;

    //2.初始化线程池对象
    //参数列表:核心线程数 最大线程数 非核心线程存活时间 时间单位 阻塞队列
    public HandlerSocketServerPool(int maxThreadNums,int queueSize){
        executorService=new ThreadPoolExecutor(3, maxThreadNums,
                120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSize));

    }

    //3.提供一个方法来提交任务给线程池的任务队列来暂存,等着线程池来处理
    public void execute(Runnable target){
        executorService.execute(target);
    }
}

public class Client {

    public static void main(String[] args) {
        CreateClient createClient = new CreateClient();
        createClient.client();
    }

}

public class CreateClient {

    public void client(){
        try {
            //1.创建Socket对象,请求服务端的连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2.从socket对象中获取一个字节输出流,将其包装为打印流
            PrintStream ps=new PrintStream(socket.getOutputStream());
            //3.使用循环不断地发送消息给服务端接收
            Scanner sc=new Scanner(System.in);
            while (true){
                System.out.print("请输入要发送给服务端的数据: ");
                String msg=sc.nextLine();
                //4.把数据发送出去
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

(5)文件上传

public class Service {

    public static void main(String[] args) {

        try{
            ServerSocket ss=new ServerSocket(9999);
            while (true){
                Socket socket=ss.accept();
                //交给一个独立的线程来处理与这个客户端的文件通信需求
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }

}

public class ServerReaderThread extends Thread{

    private Socket socket;

    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        OutputStream os=null;
        try {
            //1.得到一个数据输入流负责读取客户端发送过来的数据
            DataInputStream dis=new DataInputStream(socket.getInputStream());
            //2.读取客户端发送过来的文件类型
            String suffix=dis.readUTF();
            System.out.println("服务端已经接收到了文件类型");
            //3.定义一个字节输出管道,负责把客户端发来的文件数据写出去
            os=new FileOutputStream("D:\\io\\111\\"+ UUID.randomUUID().toString()+suffix);
            //4.从数据输入流中读取文件数据,写出到字节输出流中去
            byte[] buffer=new byte[1024];
            int len;
            while ((len=dis.read(buffer))!=-1){
                os.write(buffer,0,len);
            }
            System.out.println("服务端接收文件保存成功");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}


public class Client {

    public static void main(String[] args) {

        try(InputStream is=new FileInputStream("D:\\img\\1.jpg");)
        {
            //1.请求与服务端的Socket连接
            Socket socket=new Socket("127.0.0.1",9999);
            //2.把字节输出流包装成数据输出流
            DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
            //3.先发送上传文件的后缀给服务端
            dos.writeUTF(".jpg");
            //4.把文件数据发送给服务端进行接收
            byte[] buffer=new byte[1024];
            int len;
            while ((len=is.read(buffer))!=-1){
                dos.write(buffer,0,len);
            }
            dos.flush();
            socket.shutdownOutput();//通知服务端数据已经发送完毕
        }catch (Exception e){
            e.printStackTrace();
        }

    }

}

(6)端口转发

服务端将一个客户端的消息发送给其它客户端

/**
 * BIO模式下的端口转发思想-服务端实现
 *
 * 1.注册端口
 * 2.接收客户端的socket连接,交给一个独立的线程来处理
 * 3.把当前连接的客户端socket存入到一个所谓的在线socket集合中保存
 * 4.接收客户端的消息,然后推送给当前所有在线的socket接收
 */
public class Server {

    //定义一个静态集合
    public static List<Socket> allSocketOnline=new ArrayList<>();

    public static void main(String[] args) {
        try {
            //注册端口
            ServerSocket ss=new ServerSocket(9999);
            while (true){//可以不断地接收新的客户端
                //接收socket
                Socket socket=ss.accept();
                //把登录的客户端socket存入到一个在线集合中
                allSocketOnline.add(socket);
                //为当前登录成功的socket分配一个独立的线程来处理与之通信
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}


public class ServerReaderThread extends Thread{

    private Socket socket;

    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        try {
            //1.从socket中去获取当前客户端的输入流
            BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg=br.readLine())!=null){
                //2.服务端接收到客户端的消息之后,需要推送给当前所有的在线socket
                sendMsgToAllClient(msg);
            }
        }catch (Exception e){
            System.out.println("当前有人下线");
            //从在线socket集合中移除本socket
            Server.allSocketOnline.remove(socket);
        }
    }

    //把当前客户端发来的消息推送给全部在线的socket
    private void sendMsgToAllClient(String msg) throws IOException {
        for (Socket sock : Server.allSocketOnline) {
            PrintStream ps=new PrintStream(sock.getOutputStream());
            ps.println(msg);//打印
            ps.flush();//将消息刷出去
        }
    }

}

(7)即时通讯项目

解决客户端到客户端的通信
public class ServerChat {

    //定义一个集合存放所有在线的socket
    //key:socket value:这个socket的名称
    public static Map<Socket,String> onLineSockets=new HashMap<>();

    public static void main(String[] args) {
        try {
            //注册端口号
            ServerSocket ss=new ServerSocket(9999);
            //循环一致等待所有可能的客户端连接
            while (true){
                Socket socket=ss.accept();
                //把客户端的socket管道单独配置一个线程来处理
                new ServerReader(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}


public class ServerReader extends Thread{

    private Socket socket;

    public ServerReader(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        DataInputStream dis=null;
        try {
            dis=new DataInputStream(socket.getInputStream());
            //循环一直等待客户端的消息
            while (true){
                //读取消息类型 登录/群发/私聊/@消息
                int flag=dis.readInt();
                if (flag==1){
                    //将当前登录的客户端socket存到在线人数集合中
                    String name= dis.readUTF();
                    System.out.println(name+"---->"+socket.getRemoteSocketAddress());
                    ServerChat.onLineSockets.put(socket,name);
                }
                writeMsg(flag,dis);
            }
        }catch (Exception e){
            System.out.println("---有人下线了---");
            //从在线人数中将当前socket移除
            ServerChat.onLineSockets.remove(socket);
            try {
                writeMsg(1,dis);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }finally {
            try {
                dis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void writeMsg(int flag, DataInputStream dis) throws IOException {
        //定义一个变量存放最终的消息形式
        String msg=null;
        if (flag==1){
            //读取所有在线人数发给所有客户端去更新自己的在线人数列表
            StringBuilder rs=new StringBuilder();
            Collection<String> onlineNames=ServerChat.onLineSockets.values();
            //判断是否存在在线人数
            if (onlineNames!=null&&onlineNames.size()>0){
                for (String name:onlineNames){
                    rs.append(name+ Constants.SPILIT);
                }
                //去掉最后一个分隔符
                msg=rs.substring(0,rs.lastIndexOf(Constants.SPILIT));
                //将消息发送给所有的客户端
                sendMsgToAll(flag,msg);
            }
        } else if (flag==2||flag==3) {
            //读到 群发的 或者 @消息
            String newMsg= dis.readUTF();//消息
            //得到发件人
            String sendName=ServerChat.onLineSockets.get(socket);

            StringBuilder msgFinal=new StringBuilder();
            //时间
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
            if (flag==2){
                msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis()*2)).append("\r\n");
                msgFinal.append("   ").append(newMsg).append("\r\n");
                sendMsgToAll(flag,msgFinal.toString());
            } else if (flag==3) {
                msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis()*2)).append("对您私发\r\n");
                msgFinal.append("   ").append(newMsg).append("\r\n");
                //私发
                //得到给谁发送
                String destName= dis.readUTF();
                sendMsgToOne(destName,msgFinal.toString());
            }
        }
    }

    private void sendMsgToOne(String destName, String msg) throws IOException {
        //拿到所有的在线socket管道 给这些管道写出消息
        Set<Socket> allOnlineSockets=ServerChat.onLineSockets.keySet();
        for (Socket sk:allOnlineSockets){
            //得到当前需要私发的socket
            //只对这个名字对应的socket私发消息
            if (ServerChat.onLineSockets.get(sk).trim().equals(destName)){
                DataOutputStream dos=new DataOutputStream(sk.getOutputStream());
                dos.writeInt(2);//消息类型
                dos.writeUTF(msg);
                dos.flush();
            }
        }
    }

    private void sendMsgToAll(int flag, String msg) throws IOException {
        //拿到所有的在线socket管道 给这些管道写出消息
        Set<Socket> allOnlineSockets=ServerChat.onLineSockets.keySet();
        for (Socket sk:allOnlineSockets){
            DataOutputStream dos=new DataOutputStream(sk.getOutputStream());
            dos.writeInt(flag);
            dos.writeUTF(msg);
            dos.flush();
        }
    }

}


public class Constants {

    public static final int PORT=7778;

    //协议分隔符
    public static final String SPILIT="$%&12&";

}

3.NIO

(1)NIO支持面向缓冲区的、基于通道的IO操作,以更加高效的方式进行文件的读写操作。

(2)可以理解为非阻塞IO

(3)以块的方式处理数据,效率高

(1)三大核心(通道、缓冲区、选择器)

(1)缓冲区:本质是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer
	 对象,并提供了一组方法,用来方便访问该块内存。

(2)通道:既可以从通道中读取数据,又可以写数据到通道。通道可以非阻塞读取和写入通道,通道可以
	支持读取或写入缓冲区,也支持异步的读写

(3)选择器:可以检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。一个单独的
	线程可以管理多个通道,从而管理多个网络连接,提高效率。

(2)缓冲区(Buffer)

(1)保存基本数据类型

基本属性

(1)容量(capacity):作为一个内存块,Buffer具有一定的固定大小,也称为容量,缓冲区容量
	不能为负,并且创建后不能更改

(2)限制(limit):表示缓冲区中可以操作数据的大小(limit后的数据不能进行读写)。缓冲区的限制
	不能为负,并且不能大于其容量。写入模式:限制等于Buffer的容量;读取模式:limit等于写入
	的数据量

(3)位置(position):下一个要读取或写入的数据的索引,位置不能为负并且不能大于其限制

(4)标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()指定Buffer中一个
	特定的position,之后可以通过调用reset()方法恢复到这个position

0 <= mark <= position <= limit <= capacity

常用API

(1)XxxBuffer.allocate(10):分配一个Xxx类型的缓冲区,容量设置为10
	XxxBuffer可以是ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、
				  LongBuffer、 ShortBuffer、MappedByteBuffer
(1)clear():清空缓冲区并返回对缓冲区的引用

(2)flip():将缓冲区的界限设置为当前位置,并将当前位置设置为0

(3)capacity():返回缓冲区的大小

(4)hasRemaining():判断缓冲区中是否还有元素

(5)limit():返回缓冲区的界限位置

(6)mark():对缓冲区设置标记

(7)position():返回缓冲区的当前位置

(8)position(int n):设置缓冲区的当前位置为n,并返回修改后的Buffer对象

(9)remaining():返回position和limit之间的元素个数

(10)reset():将位置position转到以前设置mark所在位置

(11)rewind():将位置设为0,取消设置的mark

(12)get():获取缓冲区中的数据
	get():读取单个字节
	get(byte[] dst):批量读取多个字节到dst中
	get(int index):读取指定索引位置的字节(不会移动position)

(13)put():放入数据到缓冲区中
	put(byte b):将给定单个字节写入缓冲区的当前位置
	put(byte[] src):将src中的字节写入缓冲区的当前位置
	put(int index,byte b):将指定字节写入缓冲区的索引位置(不会移动position)

直接与非直接缓冲区

(1)直接内存缓冲区:使用 XxxBuffer.allocateDirect(1024) 来分配的缓冲区就在直接内存
	可以使用 isDirect() 来确定是否是直接缓冲区, 如果返回true的话就是直接缓冲区

(2)直接缓冲区:基于直接内存(非堆内存),JVM将在IO操作上具有更高的性能,因为它直接作用于
			  本地系统的IO操作。
			  本地IO -> 直接内存 -> 本地IO

(3)非直接缓冲区:基于堆内存,如果要做IO操作,会先从本进程内存复制到直接内存,再利用本地
			   IO处理
			  本地IO -> 直接内存 -> 堆内存 -> 直接内存 -> 本地IO 

(4)在做IO处理时,比如网络发送大量数据时,直接内存具有更高的效率。但是它比申请普通的堆内存
	需要耗费更高的性能。所以当有很大的数据需要缓存,并且生命周期又很长,或者需要频繁的IO操作
	(比如网络并发场景)那么就比较适合使用直接内存

(3)通道(channel)

(1)channel本身不能直接访问数据,channel只能与Buffer进行交互

(2)通道可以同时进行读写,而流只能读或者只能写

(3)通道可以实现异步读写数据

(4)通道可以从缓冲区读数据,也可以写数据到缓冲

(5)channel在NIO中是一个接口,需要使用它的实现类
	FileChannel:用于读取、写入、映射和操作文件的通道
	DatagramChannel:通过UDP读写网络中的数据通道
	SocketChannel:通过TCP读写网络中的数据
	ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个
						 ServerSocketChannel

FileChannel的常用方法

(1)read(ByteBuffer dst):从channel中读取数据到ByteBuffer

(2)read(ByteBuffer[] dsts):将channel中的数据分散到ByteBuffer[]

(3)write(ByteBuffer src):将ByteBuffer 中的数据写入到channel中

(4)write(ByteBuffer[] srcs):将ByteBuffer[] 中的数据聚集到channel

(5)position():返回此通道的文件位置

(6)FileChannel position(long p):设置此通道的文件位置

(7)size():返回此通道的文件的当前大小

(8)FileChannel truncate(long s):将此通道的文件截取为给定大小

(9)force(boolean metaData):强制将所有对此通道的文件更新写入到存储设备中

(10)transferFrom(原通道,开始位置,大小):从哪个通道复制数据
	 transferTo(开始位置,大小,目标通道):将数据写入哪个通道
public class Test1 {

    //写数据到文件中
    @Test
    public void write(){

        try {
            //1.定义一个字节输出流通向目标文件
            FileOutputStream os=new FileOutputStream("hello.txt");
            //2.得到字节输出流对应的channel
            FileChannel channel = os.getChannel();
            //3.分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //4.将数据写入缓冲区
            buffer.put("hello,welcome to China".getBytes());
            //5.把缓冲区切换为写出模式
            buffer.flip();
            //6.将缓冲区的数据写入到通道中
            channel.write(buffer);
            channel.close();
            System.out.println("写入成功");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    //从文件中读数据
    @Test
    public void read(){

        try {
            //1.定义一个文件输入流与源文件接通
            FileInputStream is=new FileInputStream("hello.txt");
            //2.得到字节输入流对应的channel
            FileChannel channel = is.getChannel();
            //3.分配一个缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //4.将数据读入缓冲区
            channel.read(buffer);
            buffer.flip();//将缓冲区的界限设置为当前位置,并将当前位置设置为0
            //5.读取缓冲区的数据
            String s=new String(buffer.array(),0,buffer.remaining());
            System.out.println(s);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    //完成文件的复制
    @Test
    public void copy() throws Exception {
        //源文件
        File fileIn=new File("hello.txt");//源文件
        File fileOut=new File("hi.txt");//目的地
        //定义字节输入流和字节输出流
        FileInputStream is=new FileInputStream(fileIn);
        FileOutputStream os=new FileOutputStream(fileOut);
        //得到文件通道
        FileChannel isChannel = is.getChannel();
        FileChannel osChannel = os.getChannel();
        //分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true){
            //开始读取一次数据
            int flag = isChannel.read(buffer);//把数据读入缓冲区
            if (flag==-1){
                break;
            }
        }
        //已经读取了数据,把缓冲区的模式切换成可读模式
        buffer.flip();
        //从缓冲区中写出数据
        osChannel.write(buffer);
        isChannel.close();
        osChannel.close();
        System.out.println("复制成功");
    }

    /**
     * 分散读取:把通道中的数据读入到多个缓冲区去
     * 聚集写入:将多个缓冲区的数据聚集到通道中
     */
    @Test
    public void more() throws Exception {
        //定义字节输入流和输出流
        FileInputStream is=new FileInputStream("hello.txt");
        FileOutputStream os=new FileOutputStream("hi.txt");
        //得到输入管道和输出管道
        FileChannel isChannel = is.getChannel();
        FileChannel osChannel = os.getChannel();
        //定义多个缓冲区做数据分散
        ByteBuffer buffer1 = ByteBuffer.allocate(4);
        ByteBuffer buffer2 = ByteBuffer.allocate(1024);
        ByteBuffer[] buffers={buffer1,buffer2};
        //从通道中读取数据分散到各个缓冲区
        isChannel.read(buffers);
        //从每个缓冲区中查询是否有数据读取到了
        for (ByteBuffer buffer:buffers){
            buffer.flip();
            System.out.println(new String(buffer.array(),0,buffer.remaining()));
        }
        //聚集写入到通道
        osChannel.write(buffers);
        isChannel.close();
        osChannel.close();
        System.out.println("success");
    }

}

(4)选择器(Selector)

(1)选择器是SelectableChannel对象的多路复用器,Selector可以同时监控多个SelectableChannel
	的IO状况,利用selector可使一个单独的线程管理多个通道,是非阻塞IO的核心

(2)只有在通道真正有读写事件发生时,才会进行读写,就大大的减少了系统开销,并且不必为每个
	连接都创建一个线程,不用去维护这个线程

(3)避免了多线程之间的上下文切换导致的开销

选择器的应用

//1.创建一个选择器
Selector selector=Selector.open();

//2.向选择器注册通道
//2.1获取通道
ServerSocketChannel ssChannel=ServerSocketChannel.open();
//2.2切换非阻塞模式
ssChannel.configureBlocking(false);
//2.3绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//2.4获取选择器
Selector selector=Selector.open();
//2.5将通道注册到选择器上并且指定监听接收事件
ssChannel.register(selector,SelectionKey.OP_ACCEPT);
当调用register将通道注册到选择器上时,选择器对通道的监听事件需要第二个参数指定
可以监听的事件类型:
(1)读:SelectionKey.OP_READ
(2)写:SelectionKey.OP_WRITE
(3)连接:SelectionKey.OP_CONNECT
(4)接收:SelectionKey.OP_ACCEPT
若注册时不止监听一个事件,则可以使用"位或"操作符连接

4.AIO

(1)异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由OS先完成了再通知
服务器应用去启动线程进行处理

(2)与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可,这两种方法均为异步
的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区;对于写操作而言,
当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cw旧巷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值