NIO
NIO
Java在1.4版本开始,引入了NIO,称为Java New IO。又称老的阻塞IO为OIO(Old IO)。
NIO与OIO对比:
- 
OIO是面向流,操作的是字节。NIO引入了Buffer和Channel,操作的是缓冲区。 
- 
OIO是阻塞的,NIO是非阻塞的 
- 
OIO没有Selector的概念 
NIO的三大组件如下所示。
Buffer
数据存储的媒介。
Buffer其实是对数组的包装,定义了一些属性来保证同时读写。有ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer一些属性fer,ShortBuffer,MappedByteBuffer。使用最多的是ByteBuffer。Buffer的缓冲区没有定义在Buffer类中,而是定义在子类中。如ByteBuffer中定义了byte[]作为缓冲区。
为了记录读写的位置,Buffer类提供了一些重要属性。
- 
capacity 
 缓冲区的容量,表示Buffer类能存储的数据大小,一旦初始化就不能改变。不是表示字节数,而是表示能存储的数据对象数。比如CharBuffer的capacity是100,能100个char。
- 
position 
 Buffer类有两种模式:
- 
读模式:表示读取数据的位置。创建Buffer时为0,读取数据后移动到下一个位置,limit为读取的上限,当position的值为limit时,表示没有数据可读。 
- 
写模式:表示下一个可写的位置。创建Buffer时为0,写入数据后移动到下一个位置,capacity为写入的上限,当position的值为capacity时,表示Buffer没有空间可写入。 
读写模式怎么切换呢?刚创建Buffer时为写模式,写入数据后要想读取,必须切换为读模式,可以调用flip方法:此时会将limit设置为写模式的position值,表示可以读取的最大位置。position设为0,表示从头开始读取。
写模式:

读模式:

3.limit
- 
读模式:读取的最大位置 
- 
写模式:可写入的最大上限。 
4.mark
调用mark()方法将position设置到mark中,再调用reset()方法将mark的值恢复到position属性中。重新从position读取。
重要方法
allocate
allocate创建一个Buffer并分配空间:
@Test
    public void testAllocate() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }

可以看到创建缓冲区后处于写模式,初始position为0,limit和capacity为allocate传入的参数。
put
创建Buffer后,可以调用put往Buffer中填充数据,只要保证数据的类型和Buffer的类型保持一致。
    @Test
    public void testPut() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }

写入5个元素后,position变为5,指向第6个位置,而limit,capacity没有变化。
flip
写入数据后不能直接读取,而是需要调用flip转换为读模式。
    @Test
    public void testFlip() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        intBuffer.flip();
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }

可以看到,flip切换为读模式后,position设为0,limit的值是之前写入的position的值。同时清除mark标记。查看flip源码:
public Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
不然,切换为读取后,position的值已修改,不清除mark标记会导致position混乱。
那么,读取完成后怎么切换为写模式,继续写入呢?可以调用clear清空Buffer或compact压缩Buffer。
clear
    @Test
    public void testClear() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        intBuffer.flip();
        System.out.println("读取数据并处理");
        while (intBuffer.hasRemaining()) {
            int i = intBuffer.get();
            System.out.println(i);
        }
        System.out.println("读取数据完成");
        System.out.println();
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
        System.out.println();
        System.out.println("清空Buffer");
        intBuffer.clear();
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }

可以看到调用clear后,Buffer的状态和allocate时的状态一致。
compact
	    @Test
    public void testCompact() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        intBuffer.flip();
        System.out.println("读取数据并处理");
        for (int i = 0; i < 2; i++) {
            System.out.println(intBuffer.get());
        }
        System.out.println("读取数据完成");
        System.out.println();
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
        System.out.println();
        System.out.println("压缩Buffer");
        intBuffer.compact();
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }
看到调用compact后会将position到limit范围内(不包含limit位置)的元素往缓冲区的头部移动。上面的例子中读取后剩余3个元素,compact后position的值为3.
get
当调用flip切换为读模式后,就可以调用get获取数据了。
@Test
    public void testGet() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        intBuffer.flip();
        System.out.println("切换flip");
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
        System.out.println(intBuffer.get());
        System.out.println("调用get()后");
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
        System.out.println(intBuffer.get(1));
        System.out.println("调用get(i)后");
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }

看出调用get()获取数据后会改变position的值,而调用get(int i)则不会改变position的值。
rewind
读取完所有数据后,可以重新读取吗?可以调用rewind。
@Test
public void testRewind() {
    IntBuffer intBuffer = IntBuffer.allocate(20);
    for (int i = 0; i < 5; i++) {
        intBuffer.put(i);
    }
    intBuffer.flip();
    System.out.println("切换flip");
    System.out.println("position:"+intBuffer.position());
    System.out.println("limit:"+intBuffer.limit());
    System.out.println("capacity:"+intBuffer.capacity());
    System.out.println("读取数据并处理");
    while (intBuffer.hasRemaining()) {
        System.out.println(intBuffer.get());
    }
    System.out.println("读取数据完成");
    System.out.println("position:"+intBuffer.position());
    System.out.println("limit:"+intBuffer.limit());
    System.out.println("capacity:"+intBuffer.capacity());
    System.out.println();
    intBuffer.rewind();
    System.out.println("调用rewind后");
    System.out.println("position:"+intBuffer.position());
    System.out.println("limit:"+intBuffer.limit());
    System.out.println("capacity:"+intBuffer.capacity());
}

可以看到调用rewind后会将position设置为0,limit不变。查看JDK源码:
    public Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }
发现会清除mark标记。
mark()和reset()
mark()将当前的position保存到mark属性中,reset()将mark属性设置到position,可以从position开始读取了。
@Test
    public void testMarkReset() {
        IntBuffer intBuffer = IntBuffer.allocate(20);
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i);
        }
        intBuffer.flip();
        System.out.println("切换flip");
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
        System.out.println("读取数据并处理");
        int j = 0;
        while (intBuffer.hasRemaining()) {
            if (j == 3) {
                intBuffer.mark();
            }
            System.out.println(intBuffer.get());
            j++;
        }
        System.out.println("读取数据完成");
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
        System.out.println();
        intBuffer.reset();
        System.out.println("调用reset后");
        System.out.println("position:"+intBuffer.position());
        System.out.println("limit:"+intBuffer.limit());
        System.out.println("capacity:"+intBuffer.capacity());
    }

调用reset后将position重置为mark属性了。
Channel
数据传输的媒介。一个Channel表示一个Socket连接。更通用的来说,一个Channel表示一个文件描述符,可以表示文件,网络连接,硬件设备等。最重要的Channel有四种:FileChannel,SocketChannel,ServerSocketChannel,DatagramChannel。
FileChannel
文件通道,用于文件的读写。
读取
首先通过FileInputStream.getChannel()获取FileChannel,然后调用int read(ByteBuffer dst)方法读取数据到ByteBuffer中并返回读到的字节数。
@Test
public void testGetFileChannel() {
    try(FileInputStream fileInputStream = new FileInputStream(filePath)) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        FileChannel channel = fileInputStream.getChannel();
        int read = channel.read(byteBuffer);
        if (read == -1) {
            return;
        }
        System.out.println("读到"+read+"字节");
        byteBuffer.flip();
        byte[] bytes = new byte[read];
        byteBuffer.get(bytes);
        System.out.println("数据是:" + new String(bytes));
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

写入
首先通过FileInputStream.getChannel()获取FileChannel,然后调用int read(ByteBuffer dst)方法读取数据到ByteBuffer中并返回读到的字节数。
  @Test
    public void testWrite() {
        try(FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("你好啊".getBytes(StandardCharsets.UTF_8));
            FileChannel channel = fileOutputStream.getChannel();
            byteBuffer.flip();
            int len;
            while ((len = channel.write(byteBuffer)) >0) {
                System.out.println("写入"+len+"字节");
            }
            channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
写入成功后,查看文件内容:

RandomAccessFile
首先通过RandomAccessFile.getChannel()获取FileChannel,然后调用int read(ByteBuffer dst)方法读取数据到ByteBuffer中并返回读到的字节数。
@Test
public void testRandomAccessFile() {
    try(RandomAccessFile randomAccessFile = new RandomAccessFile(filePath,"r")) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        FileChannel channel = randomAccessFile.getChannel();
        int read = channel.read(byteBuffer);
        if (read == -1) {
            return;
        }
        System.out.println("读到"+read+"字节");
        byteBuffer.flip();
        byte[] bytes = new byte[read];
        byteBuffer.get(bytes);
        System.out.println("数据是:" + new String(bytes));
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

关闭
使用完Channel后调用close方法关闭通道。
强制刷新到磁盘
通过write写入数据后,数据先保存到缓冲区中,要保证数据写入磁盘,必须调用channel.force将数据刷新到磁盘。
例子
看个简单例子,通过Channel复制文件。
@Test
public void testCopyFile() {
    String srcFilePath = "/home/shigp/图片/1.jpg";
    String destFilePath = "/home/shigp/图片/2.jpg";
    try (FileInputStream fileInputStream = new FileInputStream(srcFilePath);
        FileOutputStream fileOutputStream = new FileOutputStream(destFilePath)) {
        Path path = Path.of(destFilePath);
        if (!Files.exists(path)) {
            Files.createFile(path);
        }
        FileChannel srcChannel = fileInputStream.getChannel();
        FileChannel destChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
        int len;
        while((len = srcChannel.read(byteBuffer)) > 0) {
            byteBuffer.flip();
            int size;
            while((size = destChannel.write(byteBuffer)) > 0) {
            }
            byteBuffer.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
除了Channel的操作外,还需注意Buffer的读写状态切换。以上的效率不是很高,可以使用FileChannel.transferTo方法。
@Test
public void testTransferTo() {
    String srcFilePath = "/home/shigp/图片/1.jpg";
    String destFilePath = "/home/shigp/图片/2.jpg";
    try (FileInputStream fileInputStream = new FileInputStream(srcFilePath);
         FileOutputStream fileOutputStream = new FileOutputStream(destFilePath)) {
        Path path = Path.of(destFilePath);
        if (!Files.exists(path)) {
            Files.createFile(path);
        }
        File file = new File(srcFilePath);
        FileChannel srcChannel = fileInputStream.getChannel();
        FileChannel destChannel = fileOutputStream.getChannel();
        for (long count = srcChannel.size(); count > 0; ) {
            long transfer = srcChannel.transferTo(srcChannel.position(), count, destChannel);
            count  -= transfer;
        }
       destChannel.force(true);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
SocketChannel,ServerSocketChannel
涉及网络连接的通道有两个,一个是SocketChannel,负责数据的传输,对应OIO中的Socket; 一个是ServerSocketChannel,负责监听连接,对应OIO中的ServerSocket。
SocketChannel和ServerSocketChannel都支持阻塞和非阻塞两种模式。都是通过调用configureBlocking方法实现的,参数false表示设置为非阻塞模式,参数true表示设置为阻塞模式。
SocketChannel通过read从Channel中读取数据到Buffer。通过write将Buffer中的数据写入到Channel中。在关闭Channel前,可以通过调用shutdownOutput终止输出,发送一个输出结束标志。在调用close关闭通道。
DatagramChannel
DatagramChannel也可以通过调用configureBlocking(false)将Channel设置为非阻塞模式。
DatagramChannel调用receive从Channel中读取数据到Buffer中。通过send将Buffer中的数据发送到Channel中。但是要指定接收数据的IP和端口,因为UDP是面向无连接的。调用close关闭Channel。
Selector
一个线程管理多个Socket连接。Selector的作用是完成IO多路复用,通道的注册,监听,事件查询。通道通过register的方式注册到Selector中。register方法有两个参数:第一个参数是要注册到的Selector,第二个参数是Channel感兴趣的IO事件类型:
- SelectionKey.OP_READ:可读
- OP_WRITE:可写
- OP_CONNECT:建立连接
- OP_ACCEPT:接收
如果要注册多个事件类型,每个事件类型用或运算符(|)连接。
那是不是什么类型的Channel都可以注册到Selector中?并不是。只有继承了SelectableChannel的通道才能被注册到Selector中,且Channel不能被设置为阻塞。
Channel注册到Selector后,可以调用Selector的select方法获取已被Selector监控且IO已就绪的IO事件及相应的通道,也就是SelectionKey。通过SelectionKey可以获取到已就绪的IO事件类型,发生的Channel。然后根据IO事件类型做不同的处理。处理完后将SelectionKey移除,避免下次重复调用。
SElector的select方法有多个重载版本:
- select():阻塞调用,直到有一个Channel发生了感兴趣的IO事件
- select(long timeout):和select() 一样,但是最长阻塞时间为timeout毫秒
- selectNow():非阻塞,不管是否发生感兴趣的IO事件都立刻返回。
例子,实现一个Discard服务器和客户端
Discard服务器仅接收客户端通道的数据并直接丢弃,然后关闭客户端。
public class DiscardServer {
    public static void startServer() throws Exception{
        // TCP服务端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",10880));
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.configureBlocking(false);
        datagramChannel.bind(new InetSocketAddress("127.0.0.1", 10890));
        Selector selector = Selector.open();
        // 将通道注册到Selector中,并设置感兴趣的IO事件为accept,也就是接收客户端连接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        datagramChannel.register(selector, SelectionKey.OP_READ);
        // 发生了注册的感兴趣的IO事件
        while (selector.select() > 0) {
            // 获取感兴趣的IO事件集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                   //连接客户端
                    ServerSocketChannel sChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel channel = sChannel.accept();
                    // 设置为非阻塞
                    channel.configureBlocking(false);
                    // 将客户端通道注册到Selector中,设置感兴趣的IO事件为read
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    // 从客户端通道中读取
                    SelectableChannel selectableChannel = selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    if (selectableChannel instanceof SocketChannel) {
                        SocketChannel channel = (SocketChannel) selectableChannel;
                        int len;
                        while ((len = channel.read(byteBuffer)) > 0) {
                            byteBuffer.flip();
                            System.out.println("读取的数据是:" + new String(byteBuffer.array(), 0 ,byteBuffer.limit()));
                            byteBuffer.clear();
                        }
                        // 关闭客户端
                        channel.close();
                    } else if (selectableChannel instanceof DatagramChannel) {
                        DatagramChannel channel = (DatagramChannel) selectableChannel;
                        SocketAddress receive = channel.receive(byteBuffer);
                        byteBuffer.flip();
                        System.out.println("从"+ receive +"读取的数据是:" + new String(byteBuffer.array(), 0 ,byteBuffer.limit()));
                        byteBuffer.clear();
                        // 关闭客户端
                        channel.close();
                    }
                }
                // 移除selectionKey
                iterator.remove();
            }
        }
        selector.close();
        serverSocketChannel.close();
    }
    public static void main(String[] args) {
        try {
            startServer();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
TCP客户端:
 public class DiscardTCPClient {
    public static void startClient() throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        // 连接Discard服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 10880));
        while(!socketChannel.finishConnect()) {
        }
        System.out.println("已经与服务器建立连接");
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("你好啊".getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        socketChannel.shutdownOutput();
        socketChannel.close();
    }
    public static void main(String[] args) {
        try {
            startClient();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
UDP客户端:
  public class DiscardUDPClient {
    public static void startClient() throws Exception {
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.configureBlocking(false);
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("发送UDP".getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 10890));
        datagramChannel.close();
    }
    public static void main(String[] args) {
        try {
            startClient();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
例子,实现传输文件的服务器和客户端
服务器
public class SendFileServer {
    public final static String QUERY_FILE_PATH = "/home/shigp/图片";
    public static void main(String[] args) {
        try {
            startServer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void startServer() throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress("localhost",10900));
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (selector.select() > 0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel channel = serverSocketChannel1.accept();
                    channel.configureBlocking(false);
                    System.out.println("接受连接");
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    System.out.println("可读");
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    if (byteBuffer == null) {
                        byteBuffer = ByteBuffer.allocate(2048);
                    } else {
                        System.out.println("start,position="+byteBuffer.position());
                    }
                    int len;
                    int tmpPosition = 0;
                    int tmpLimit = 0;
                    int sum = 0;
                    while ((len = channel.read(byteBuffer)) > 0) {
                        sum += len;
                        System.out.println("1.读取字节数:"+len+",position="+byteBuffer.position());
                        if (byteBuffer.position() < 4) {
                            continue;
                        }
                        byteBuffer.flip();
                        tmpPosition = byteBuffer.position();
                        tmpLimit = byteBuffer.limit();
                        System.out.println("while,limit="+byteBuffer.limit()+",position="+byteBuffer.position());
                        int tmpFileNameSize = byteBuffer.getInt();
                        System.out.println("while,limit="+byteBuffer.limit()+",position="+byteBuffer.position());
                        byteBuffer.position(tmpPosition);
                        byteBuffer.limit(tmpLimit);
                        byteBuffer.compact();
                        if (tmpLimit >= tmpFileNameSize + 4){
                            System.out.println("2.读取字节数:"+len+",position="+byteBuffer.position());
                            break;
                        }
                        System.out.println("3.读取字节数:"+len+",position="+byteBuffer.position());
                    }
                    byteBuffer.flip();
                    System.out.println("1234,limit="+byteBuffer.limit()+",position="+byteBuffer.position());
                    tmpPosition = byteBuffer.position();
                    tmpLimit = byteBuffer.limit();
                    int tmpFileNameSize = byteBuffer.getInt();
                    byteBuffer.position(tmpPosition);
                    byteBuffer.limit(tmpLimit);
                    if (tmpLimit >= tmpFileNameSize + 4){
                        System.out.println("1.读取到的文件名是:" + new String(byteBuffer.array(), 4 , tmpFileNameSize));
                        channel.register(selector, SelectionKey.OP_WRITE);
                        selectionKey.attach(new String(byteBuffer.array(), 4 , tmpFileNameSize));
                    } else {
                        System.out.println("position="+byteBuffer.position());
                        byteBuffer.compact();
                        selectionKey.attach(byteBuffer);
                    }
                } else if (selectionKey.isWritable()) {
                    System.out.println("可写");
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    String fileName = (String) selectionKey.attachment();
                    System.out.println(fileName);
                    File file = traverseFolder(new File(QUERY_FILE_PATH), fileName);
                    ByteBuffer allocate = ByteBuffer.allocate(8);
                    if (file == null) {
                        System.out.println("没有找到文件:" + fileName);
                        allocate.putLong(-1L);
                        allocate.flip();
                        while (channel.write(allocate) > 0) {
                        }
                    } else {
                        allocate.putLong(file.length());
                        allocate.flip();
                        while (channel.write(allocate) > 0) {
                        }
                        System.out.println("写入文件大小成功,"+file.length());
                        FileChannel channel1 = new FileInputStream(file).getChannel();
                        for (long count = file.length(); count > 0;) {
                            long transfer = channel1.transferTo(channel1.position(), count, channel);
                            count -= transfer;
                        }
                        System.out.println("写入文件内容成功");
                        channel1.close();
                    }
                    channel.close();
                }
                iterator.remove();
            }
        }
    }
    public static File traverseFolder(File folder,String fileName) {
        if (!folder.exists()) {
            return null;
        }
        if (folder.isDirectory()) {
            for (File file : folder.listFiles()) {
                if (file.isDirectory()) {
                    traverseFolder(file,fileName);
                } else if (fileName.equals(file.getName())){
                    return file;
                }
            }
        }
        return null;
    }
}
客户端
public class SendFileClient {
public static void main(String[] args) {
    try {
        receive();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static void receive() throws Exception {
    String fileName = "Spark教程.docx";
    String destFileName = "/home/shigp/文档/client";
    File file1 = new File(destFileName);
    if (!file1.exists()) {
        file1.mkdirs();
    }
    File file = new File(destFileName + File.separator + fileName);
//        if (file.exists()) {
//            file.createNewFile();
//        }
    FileChannel fileChannel = new FileOutputStream(destFileName + File.separator + fileName).getChannel();
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
    socketChannel.connect(new InetSocketAddress("localhost",10900));
    while (!socketChannel.finishConnect()) {
    }
    byte[] bytes = fileName.getBytes(StandardCharsets.UTF_8);
    ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
    byteBuffer.putInt(bytes.length);
    byteBuffer.flip();
    while (socketChannel.write(byteBuffer) >0) {
    }
    byteBuffer.clear();
    byteBuffer.put(bytes);
    byteBuffer.flip();
    while (socketChannel.write(byteBuffer) >0) {
    }
    System.out.println("写入成功");
    System.out.println("开始接收文件");
    byteBuffer.clear();
    int len;
    long fileSize = 0L;
    int tpmPosition = 0;
    int tmpLimit = 0;
    long readFileSize = 0L;
    boolean isReadFileSize = true;
    int sum  =0;
    int writeBytes = 0;
    while ((len = socketChannel.read(byteBuffer)) >= 0) {
        if (len == 0){
            continue;
        }
        sum += len;
        byteBuffer.flip();
        tmpLimit=byteBuffer.limit();
        tpmPosition = byteBuffer.position();
        if (isReadFileSize) {
            if (tmpLimit >= 8) {
                fileSize = byteBuffer.getLong();
                byteBuffer.position(8);
                isReadFileSize=false;
                readFileSize += (tmpLimit-8);
            } else {
                byteBuffer.position(tpmPosition);
            }
        } else {
            readFileSize += len;
            if (readFileSize>=fileSize) {
                byteBuffer.limit(tmpLimit);
                break;
            }
        }
        byteBuffer.limit(tmpLimit);
        boolean hasRemaining = byteBuffer.hasRemaining();
        int size = 0;
        if (!isReadFileSize) {
            while (hasRemaining && (size = fileChannel.write(byteBuffer)) > 0) {
                hasRemaining = byteBuffer.hasRemaining();
                writeBytes += size;
            }
            byteBuffer.clear();
        }
    }
    if (byteBuffer.hasRemaining()) {
        int size = 0;
        while ((size=fileChannel.write(byteBuffer)) > 0) {
            writeBytes += size;
        }
    }
    System.out.println("sum="+sum);
    System.out.println("readFileSize="+readFileSize+",fileSize="+fileSize);
    System.out.println("写入字节="+writeBytes);
    if (fileSize >=0) {
        if (readFileSize>=fileSize) {
            System.out.println("接收文件成功");
        } else {
            System.out.println("接收文件失败");
        }
    } else {
        System.out.println("文件不存在");
    }
    fileChannel.force(true);
    fileChannel.close();
    socketChannel.close();
}
}
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号