NIO

news/2024/9/28 11:20:24

NIO

Java在1.4版本开始,引入了NIO,称为Java New IO。又称老的阻塞IO为OIO(Old IO)。

NIO与OIO对比:

  1. OIO是面向流,操作的是字节。NIO引入了Buffer和Channel,操作的是缓冲区。

  2. OIO是阻塞的,NIO是非阻塞的

  3. OIO没有Selector的概念

NIO的三大组件如下所示。

Buffer

数据存储的媒介。

Buffer其实是对数组的包装,定义了一些属性来保证同时读写。有ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer一些属性fer,ShortBuffer,MappedByteBuffer。使用最多的是ByteBuffer。Buffer的缓冲区没有定义在Buffer类中,而是定义在子类中。如ByteBuffer中定义了byte[]作为缓冲区。

为了记录读写的位置,Buffer类提供了一些重要属性。

  1. capacity
    缓冲区的容量,表示Buffer类能存储的数据大小,一旦初始化就不能改变。不是表示字节数,而是表示能存储的数据对象数。比如CharBuffer的capacity是100,能100个char。

  2. 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并分配空间:

@Testpublic 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的类型保持一致。

    @Testpublic 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转换为读模式。

    @Testpublic 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
    @Testpublic 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
	    @Testpublic 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获取数据了。

@Testpublic 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开始读取了。

@Testpublic 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中并返回读到的字节数。

  @Testpublic 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事件为readchannel.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();}}// 移除selectionKeyiterator.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();
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/65710.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

京东面试:RR隔离mysql如何实现?什么情况RR不能解决幻读?

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,…

固态硬盘接入电脑没有反应

当固态硬盘(SSD)接入电脑后没有反应时,可能由多种原因造成。以下是一些常见的原因及其解决方法: 一、物理连接问题 检查接口连接: 确保SSD的SATA接口或M.2接口(视SSD类型而定)与主板连接牢固,没有松动或错位。 检查SATA数据线或M.2插槽是否损坏,如有必要,更换新的数据…

一些超好用的 GitHub 插件和技巧

聊聊我平时使用 GitHub 时学到的一些插件、技巧。聊聊我平时使用 GitHub 时学到的一些插件、技巧。 ‍ ‍ 浏览器插件 在我的另一篇博客 浏览器插件推荐 里提到过跟 GitHub 相关的一些插件,这里重复下:Sourcegraph:在线打开项目,方便阅读,将 GitHub 变得和 IDE 一般,集成…

java第一次正式课程课后习题

s和t并非引用同一对象,不同的值引用不同对象,相同值引用相同对象。 枚举类型并非原始数据类型,而是引用数据类型。 采用.velueof和.从枚举类型中赋值效果相同。java中的数采用补码形式表示。由示例可知,局部变量与全局变量重名时会在局部屏蔽全局变量,采用局部变量。java中…

橡胶 在经历大C浪的反弹

下跌部分 开启大ABC的反弹:

九月二十八

以下代码的输出结果是什么? int X=100; int Y=200; System.out.println("X+Y="+X+Y); System.out.println(X+Y+"=X+Y"); 为什么会有这样的输出结果? 输出结果是: X+Y=100200 100200=X+Y 出现这样的输出结果是因为在Java中,当多个值连接在一起时,会根据…

九月二十七2

当需要处理非常大或非常小的数值时,应选择float或double类型。 当需要处理字符或需要较大范围的无符号整数时,应选择char类型。 当需要在内存和处理速度之间做出权衡时,可以根据需要选择适当的整数类型(byte, short, int, long)。 对于需要精确计算的场景,应避免使用浮点…

PARTVI-Oracle数据库管理与开发-数据库管理员和开发人员的主题

17.数据库管理员和开发人员的主题 17.1. 数据库安全概述 通常情况下,数据库安全涉及用户认证、加密、访问控制和监控。 17.1.1. 用户账户 每个Oracle数据库都有一个有效数据库用户的列表。数据库包含几个默认账户,包括默认的管理员账户SYSTEM(参见第2-5页的“SYS和SYSTEM模式…