实现文件高效读写机制--Channel通道

1. Channel概述

1).java.nio.channels.Channel(接口):Channel表示一个“通道”,它内部封装了IO流。从NIO开始,不用直接使用IO流,改用Channel通道 + Buffer来操作读写。它比IO流更加强大。
2).常用的几个通道:
1).FileChannel:文件通道,操作文件。
2).DatagramChannel:UDP协议的通道。
3).SocketChannel和ServerSocketChannel:TCP协议的通道。

2. FileChannel类的基本使用

1).使用FileChannel做一个简单的复制:
注意:FileChannel是一个“抽象类”不能直接创建对象。可以通过一些“流对象”的getChannel()方法来获取一个“通道对象”。
2).示例代码:

public static void main(String[] args) {
	Scanner sc = new Scanner(System.in);
    System.out.println("请输入要复制的文件:(加上绝对路径)");
    String fileName1 = sc.next();
    System.out.println("请输入目标文件:(加上绝对路径)");
    String fileName2 = sc.next();
    //1.定义输入、输出流的字节流
    try (FileInputStream in = new FileInputStream(fileName1);
         FileOutputStream out = new FileOutputStream(fileName2);
         //2.通过输入、输出流获取"通道"对象
         FileChannel inChannel = in.getChannel();
         FileChannel outChannel = out.getChannel();
    ) {
        //3.由于"通道"都是使用的Buffer存储数据,所以定义一个ByteBuffer用于临时存储数据
        ByteBuffer buf = ByteBuffer.allocate(1024 * 8);
        //4.循环读取
        int len = 0;
        while ((len = inChannel.read(buf)) != -1) {//读完一次后,buf的position应该在capacity的位置
            //1.先flip():为最后一次准备的
            buf.flip();//1.将limit设置为当前的position;2.将position设置为0;3.清除mark
            //2.输出
            outChannel.write(buf);//内部只输出:position 到 limit 之间的数据,它也会改变position
            //3.将buf设置为初始状态,准备下一次的读取:为最后一次之前的几次准备的
            buf.clear();//1.将position设置为0;2.将limit设置为capacity;3.清除mark
        }
        //打印
        System.out.println("复制完毕!");

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
3 FileChannel结合MappedByteBuffer实现高效读写

1).java.nio.MappedByteBuffer(抽象类):它是ByteBuffer的子类,它是自动创建的“系统内存空间”,使用这个对象作为缓存区,复制文件会非常快。
2).复制2G以下的文件:一次就成功
注意:限制2G的原因之一是:2G的大小刚好是int能取到的最大值。复制2G以上的文件中能用到,可看出。
示例代码:

public static void main(String[] args) {
    //1.创建两个流
    try (RandomAccessFile in = new RandomAccessFile("d:\\b.rar", "r");//输入流只需要"只读",所以设置为"r"-固定写法
         RandomAccessFile out = new RandomAccessFile("e:\\b.rar","rw");//输出流要"读、写",所以设置为"rw"-固定写法
         //2.获取两个通道
         FileChannel inChannel = in.getChannel();
         FileChannel outChannel = out.getChannel();

    ) {
        //3.获取文件大小
        long fileSize = inChannel.size();

        //4.调用FileChannel的map()方法,获取一个"系统内存的映射空间"
        //为什么最大只能复制2G内容?因为map方法的第三个参数内部会验证,最大只接受2G的字节数。
        MappedByteBuffer inBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
        MappedByteBuffer outBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);

        //5.开始复制
        for (int i = 0; i < fileSize; i++) {
            //一次从inBuf中读取一个字节
            byte b = inBuf.get(i);
            //将这个字节输出到outBuf中
            outBuf.put(i, b);
        }
        //打印
        System.out.println("复制完毕!");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3).复制2G以上的文件:分块复制

public static void main(String[] args) {
	Scanner sc = new Scanner(System.in);
    System.out.println("请输入要复制的文件:(加上绝对路径)");
    String fileName1 = sc.next();
    System.out.println("请输入目标文件:(加上绝对路径)");
    String fileName2 = sc.next();
    //1.创建两个流
    try (RandomAccessFile in = new RandomAccessFile(fileName1, "r");
        RandomAccessFile out = new RandomAccessFile(fileName2,"rw");
        //获取两个文件通道
         FileChannel inChannel = in.getChannel();
         FileChannel outChannel = out.getChannel();
    ) {
        //获取文件大小
        long fileSize = inChannel.size();
        //预设一些变量
        long everySize = 1024 * 1024 * 512;//每块大小:500M

        long count = 1;//复制的次数,初始为:1
        long everyCopySize = fileSize;//每次复制的大小,默认初始化为:文件大小

        //判断是否需要分块
        if (fileSize > everySize) {
            //计算要复制的块数
            count = fileSize % everySize == 0? fileSize / everySize:fileSize / everySize + 1;
            //更改每次复制的大小
            everyCopySize = everySize;

        }
        //分块复制
        long startIndex = 0;
        for (int i = 0; i < count; i++) {//控制复制的次数(块数)
            //获取每块的映射缓存空间
            MappedByteBuffer inBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, startIndex, everyCopySize);
            MappedByteBuffer outBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, startIndex, everyCopySize);

            //开始复制,这里就要求每次复制的大小小于int的取值的最大值2G。
            for (int j = 0; j < everyCopySize; j++) {
                byte b = inBuf.get(j);
                outBuf.put(j, b);
            }

            //更改startIndex
            startIndex += everyCopySize;
            //更改每块的大小,因为最后一块,不足500M
            everyCopySize = fileSize - startIndex > everySize ? everySize : fileSize - startIndex;

        }

        //打印
        System.out.println("复制完毕!");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

4. SocketChannel和ServerSocketChannel实现连接

1.服务器端:ServerSocketChannel类用于连接的服务器端,它相当于:ServerSocket
1). 调用ServerSocketChannel的静态方法open():打开一个通道,新频道的套接字最初未绑定; 必 须通过其套接字的bind方法将其绑定到特定地址,才能接受连接。

ServerSocketChannel serverChannel = ServerSocketChannel.open()

2). 调用ServerSocketChannel的实例方法bind(SocketAddress add):绑定本机监听端口,准备接 受连接。 注:java.net.SocketAddress(抽象类):代表一个Socket地址。 我们可以使用它的子类:java.net.InetSocketAddress(类) 构造方法:InetSocketAddress(int port):指定本机监听端口。

serverChannel.bind(new InetSocketAddress(8888));

3). 调用ServerSocketChannel的实例方法accept():等待连接。

SocketChannel accept = serverChannel.accept(); 

我们可以通过ServerSocketChannel的configureBlocking(boolean b)方法设置accept()是否 阻塞

public class Server {
    public static void main(String[] args){
        try (ServerSocketChannel serverChannel = ServerSocketChannel.open())
        {
            serverChannel.bind(new InetSocketAddress(8888));
            System.out.println("【服务器】等待客户端连接...");
            //   serverChannel.configureBlocking(true);//默认--阻塞            
            serverChannel.configureBlocking(false);//非阻塞
            SocketChannel accept = serverChannel.accept();
            System.out.println("后续代码......");
            //......      
            //......      
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端:SocketChannel类用于连接的客户端,它相当于:Socket。
1). 先调用SocketChannel的open()方法打开通道:

ServerSocketChannel serverChannel = ServerSocketChannel.open()

2). 调用SocketChannel的实例方法connect(SocketAddress add)连接服务器:

socket.connect(new InetSocketAddress("localhost", 8888));

示例:客户端连接服务器

public class Client {
    public static void main(String[] args) {
        try (SocketChannel socket = SocketChannel.open())
        {
            socket.connect(new InetSocketAddress("localhost", 8888));
            System.out.println("后续代码......");
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("客户端完毕!");
    }
}
5. SocketChannel和ServerSocketChannel实现收发信息

创建服务器端如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Server {
    public static void main(String[] args) {
        try (ServerSocketChannel serverChannel = ServerSocketChannel.open()){

            serverChannel.bind(new InetSocketAddress(8888));
            System.out.println("【服务器】等待客户端链接...");
            //serverChannel.configureBlocking(true);//默认--阻塞
            //serverChannel.configureBlocking(false);//非阻塞
            SocketChannel accept = serverChannel.accept();
            System.out.println("【服务器】有连接到达...");

            ByteBuffer outBuf = ByteBuffer.allocate(100);
            outBuf.put("你好客户端,我是服务器".getBytes());
            outBuf.flip();
            accept.write(outBuf);

            ByteBuffer inBuf = ByteBuffer.allocate(100);
            accept.read(inBuf);
            inBuf.flip();
            String message = new String(inBuf.array(),0,inBuf.limit());
            System.out.println("【服务器】收到消息:"+message);

            accept.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

创建客户端如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {
    public static void main(String[] args) {
        try (SocketChannel socket = SocketChannel.open()){
            socket.connect(new InetSocketAddress("localhost",8888));

            ByteBuffer outBuf = ByteBuffer.allocate(100);
            outBuf.put("你好服务器,我是客户端".getBytes());
            outBuf.flip();
            socket.write(outBuf);

            ByteBuffer inBuf = ByteBuffer.allocate(100);
            socket.read(inBuf);
            inBuf.flip();
            String message = new String(inBuf.array(),0,inBuf.limit());
            System.out.println("【客户端】收到消息:"+message);

            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("客户端完毕!");
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值