28 Java学习之NIO Buffer(二)(待补充)

一. Buffer介绍

Buffer,故名思意,缓冲区,实际上是一个容器,是一个连续数组。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer。具体看下面这张图就理解了:

  上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。

Buffer本质上就是一块内存区,可以用来写入数据,并在稍后读取出来。这块内存被NIO Buffer包裹起来,对外提供一系列的读写方便开发的接口。

  在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用的Buffer的子类有:

  • ByteBuffer
  • IntBuffer
  • CharBuffer
  • LongBuffer
  • DoubleBuffer
  • FloatBuffer
  • ShortBuffer

  如果是对于文件读写,上面几种Buffer都可能会用到。但是对于网络读写来说,用的最多的是ByteBuffer。

字节缓冲区和其他缓冲区类型最明显的不同在于,它们可能成为通道所执行I/O的源头或目标,如果对NIO有了解的朋友们一定知道,通道只接收ByteBuffer作为参数。如我们所知道的,操作系统在内存区域进行I/O操作,这些内存区域,就操作系统方面而言,是相连的字节序列。于是,毫无疑问,只有字节缓冲区有资格参与I/O操作

二. Buffer的属性

  • capacity(容量):buffer本质是一个数组,在初始化时有固定的大小,这个值就是容量。容量不可改变,一旦缓冲区满了,需要将其清空才能将继续进行读写操作。       
     ByteBuffer bf = ByteBuffer.allocate(10);  //创建了一个最大容量为10的字节缓冲区
  • position(位置):表示当前的位置,初始化时为0,当一个基本数据类型的数据写入buffer时,position会向前移动到下一个可插入数据的Buffer单元。position最大值可以是                              capacity-1。
  • limit(限制):在缓冲区写模式下,limit表示你最多能往Buffer里写多少数据,大小等于capacity;在缓冲区读模式下,limit表示能从缓冲区内读取到多少数据,因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。可以认为是缓冲区中实际元素的数量
  • mark(标记):一个备忘位置,调用mark()方法的话,mark值将存储当前position的值,等下次调用reset()方法时,会设定position的值为之前的标记值

 1 package com.test.a;
 2
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.nio.ByteBuffer;
 8 import java.nio.channels.FileChannel;
 9
10 public class Test {
11     public static void main(String[] args) throws IOException{
12         FileInputStream fileInputStream=new FileInputStream("C:\\Users\\hermioner\\Desktop\\in.txt");
13         FileChannel fileChannel=fileInputStream.getChannel();
14
15         FileOutputStream fileOutputStream=new FileOutputStream("C:\\Users\\hermioner\\Desktop\\out.txt");
16         FileChannel fileChannel2=fileOutputStream.getChannel();
17
18         //初始化缓冲区
19         ByteBuffer buffer=ByteBuffer.allocate(20);
20         System.out.println("通道文件的大小:"+fileChannel.size());
21         System.out.println("缓冲区初始化时当前位置:"+buffer.position());
22         System.out.println("缓冲区初始化时可写的限制:"+buffer.limit());
23         System.out.println("------------循环开始---------");
24
25         //判断通道内数据是否读取完成
26         while(-1 != fileChannel.read(buffer))
27         {
28             System.out.println("缓冲区写模式下当前位置:"+buffer.position());
29             System.out.println("缓冲区写模式下的限制:"+buffer.limit());
30
31             //将缓冲区从写模式切换到读模式
32             buffer.flip();
33             System.out.println("缓冲区读模式下当前位置:"+buffer.position());
34             System.out.println("缓冲区读模式下的限制:"+buffer.limit());
35
36             //判断缓冲区内是否还有数据可读取
37             while(buffer.hasRemaining()) {
38                 fileChannel2.write(buffer);
39             }
40             buffer.clear();
41         }
42         fileChannel.close();
43         fileChannel2.close();
44         fileOutputStream.close();
45         fileInputStream.close();
46
47     }
48 }

1 通道文件的大小:13
2 缓冲区初始化时当前位置:0
3 缓冲区初始化时可写的限制:20
4 ------------循环开始---------
5 缓冲区写模式下当前位置:13
6 缓冲区写模式下的限制:20
7 缓冲区读模式下当前位置:0
8 缓冲区读模式下的限制:13

执行结果,out.txt文件中的内容和in.txt文件中的内容一样样,且为:

hello,

world

说明:可以看到,在缓冲区写模式下,limit的大小始终等于capacity;而在读模式下,limit等于模式切换前position的大小

position <= limit <= capacity

再举个例子,观察四个值得变化:

(1)创建一个容量大小为10的字节缓冲区

ByteBuffer bf = ByteBuffer.allocate(10);

此时:mark = -1; position = 0; limit =10 ; capacity = 10;

(2)往缓冲区中put五个字节

bf.put((byte)‘H‘).put((byte)‘e‘).put((byte)‘l‘).put((byte)‘l‘).put((byte)‘0‘);

注意这里一个字符是占用两个字节的,但是英文字符只占用一个字节,所以这样是可以实现存储效果的。

此时:此时:mark = -1; position = 5; limit = 10; capacity = 10;

(3)调用flip方法,切换为读就绪状态

bf.flip();

此时:mark = -1; position = 0; limit = 5; capacity = 10;

(4)读取两个元素

System.out.println("" + (char) bf.get() + (char) bf.get());

此时:mark = -1; position = 2; limit = 5; capacity = 10;

(5)标记此时的position位置

bf.mark();

此时:mark = 2; position = 2; limit = 5; capacity = 10;

(6)读取两个元素后,恢复到之前mark的位置处

System.out.println("" + (char) bf.get() + (char) bf.get());
bf.reset();

执行完第二行代码:mark = 2; position = 2; limit = 5; capacity = 10;

(7)调用compact方法,释放已读取数据的空间,准备重新填充缓存区

bf.compact();

此时:mark = 2; position = 3; limit = 10; capacity = 10;

注意观察数组中元素的变化,实际上进行了数组拷贝,抛弃了已读字节元素,保留了未读字节元素;

三. Buffer中的重要方法

3.1 flip

buffer.flip()该方法是用于将缓冲区从写模式切换到读模式,这是一种固定写法

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
    }

3.2 remaind方法

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
    }

将position的位置设置为0,表示可以重新读取Buffer中的所有数据,limit保持不变。

3.3 clear方法

 public final Buffer clear() {
  position = 0;
  limit = capacity;
   mark = -1;
    return this;
}
  • 一旦完成对buffer中数据的读取,需要让buffer做好再次被写入的准备,这时候可以调用clear方法来完成。
  • clear方法将position设置为0,limit设置为容量的值,也就意味着buffer被清空了,但是这个清空的概念是写入数据可以从缓冲区的指定位置开始,但buffer里面的数据并没有        删除。
  • 如果buffer里面还有数据没有被读取,这个时候调用clear方法会导致那些数据被“遗忘”,因为没有标记告诉你哪些是读取过哪些没有被读取。

3.4 向buffer中写入数据

  • 通过channel写入;
  • 通过buffer的put方法写入:
buffer.put("channel".getBytes());

3.5 读取buffer中的数据

  • 通过channel读取;
  • 通过buffer的get方法读取:
byte b = buffer.get();

四. 利用Buffer读写数据,通常遵循四个步骤

  • 把数据写入buffer;
  • 调用flip;
  • 从Buffer中读取数据;
  • 调用buffer.clear()或者buffer.compact()。

当写入数据到buffer中时,buffer会记录已经写入的数据大小。当需要读数据时,通过 flip() 方法把buffer从写模式调整为读模式;在读模式下,可以读取所有已经写入的数据。

当读取完数据后,需要清空buffer,以满足后续写入操作。清空buffer有两种方式:调用 clear()compact() 方法。clear会清空整个buffer,compact则只清空已读取的数据,未被读取的数据会被移动到buffer的开始位置,写入位置则近跟着未读数据之后。

参考文献:

https://www.cnblogs.com/dolphin0520/p/3919162.html

https://www.cnblogs.com/dongguacai/p/5812831.html

https://www.cnblogs.com/chenpi/p/6475510.html

原文地址:https://www.cnblogs.com/Hermioner/p/9788081.html

时间: 2024-08-29 19:27:29

28 Java学习之NIO Buffer(二)(待补充)的相关文章

原 Java学习之Xml系列二:xml按条件查询、xml递归遍历所有元素和属性

xml中加入了几条,为了方便查询时作为示例. 话不多说见代码注释: DTD文件:SwordTypeDefinition.dtd <?xml version="1.0" encoding="UTF-8"?> <!ELEMENT SwordLibrary (Sword*)> <!ELEMENT Sword (SwordName,Price,Attack)> <!ELEMENT SwordName (#PCDATA)> &l

26 Java学习之NIO和IO得比较

转自:https://www.cnblogs.com/aspirant/p/8630283.html 一.概念 NIO即New IO(非阻塞IO),这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多.在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO. 二.NIO和IO的主要区别 下表总结了Java IO和NIO之间的主要区别: IO NIO 面向流 面向缓冲 阻塞IO

Java学习之路(二)--Thinking in Java

针对昨天Java学习之路(一)--Thinking in Java中的类的静态方法不能访问创建非静态类,给出了将内部类修改成为static静态类,操作方便简单.现在给出第二种不需要添加删除的另一种极为高效的方式.可以将内部类从你所创建的类中复制粘贴到类外,作为一个外部类,在static主方法中就可以创建类的引用了. 源代码如下: ? class test{ int s; char c; } public class test1 { public static class test{ int s;

JAVA学习--面向对象的特征二:继承性

* 一.面向对象的特征二:继承性 * 1.为什么要设计继承性?  *  * 2.通过"class A extends B"类实现类的继承.  *   子类:A  父类(或基类 SuperClass):B  *     * 3.子类继承父类以后,父类中声明的属性.方法,子类就可以获取到.  *    明确:当父类中有私有的属性或方法时,子类同样可以获取得到,只是由于封装性的设计,使得子类不可以直接  *        调用罢了.  *   子类除了通过继承,获取父类的结构之外,还可以定义

JAVA学习笔记(三十二)- 字符流 FileReader &amp; FileWriter

标题 import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream;

Java学习之路(二) Java命令

1.java.exe:2.javac.exe:编译的Java程序编写的后缀为.java的文件,利用javac编译后会生成执行文件.class文件3.javaw.exe:运行Javac编译后生成的可执行文件.class文件,在执行图形界面的java程序运行java命令时,会出现一个console窗口并一直保持,并通过System.out将程序中的信息在console窗口内输出,avaw 大多用来运行GUI程序,运行javaw,JVM首先会调用控制台程序启动主程序,调用完主程序后就会隐藏控制台:4.

JAVA学习第五十二课 — IO流(六)File对象

File类 用来给文件或者文件夹封装成对象 方便对文件与文件夹的属性信息进行操作 File对象可以作为参数传递给流的构造函数 一.构造函数和分隔符 public static void FileDemo() {//构造函数演示 //可以将一个已存在或不存在的文件或目录封装成File对象 File file = new File("d:\\a.txt"); File file2 = new File("d:","a.txt"); File file

Java学习笔记—第十二章 Java网络编程入门

第十二章  Java网络编程入门 Java提供的三大类网络功能: (1)URL和URLConnection:三大类中最高级的一种,通过URL网络资源表达方式,可以很容易确定网络上数据的位置.利用URL的表示和建立,Java程序可以直接读入网络上所放的数据,或把自己的数据传送到网络的另一端. (2)Socket:又称"套接字",用于描述IP地址和端口(在Internet中,网络中的每台主机都有一个唯一的IP地址,而每台主机又通过提供多个不同端口来提供多种服务).在客户/服务器网络中,当客

黑马程序员——JAVA学习笔记十三(高新技术二)

8,    注解 Annotation(注解)是JDK5.0及以后版本引入的. 注解是以 @注解名 的形式标识 注解不会影响程序语义,只作为标识 注解是新的类型(与接口很相似),它与类.接口.枚举是在同一个层次,它们都称作为java的一个类型(TYPE). 它可以声明在包.类.字段.方法.局部变量.方法参数等的前面,用来对这些元素进行说明,注释. 它的作用非常的多,例如:进行编译检查.生成说明文档.代码分析等 注释类型 是一种特殊的接口 用 @interface 声明如 [email prote