缓冲器的学习

摘要:
介绍缓冲区是新IO模型的最基本部分。因为新的IO模型需要缓冲所有IO操作。在新的IO模型中,数据不再写入输出流和从输出流中读取,而是从缓冲区中读取和写入。缓冲区可以是阵列或直接连接到硬件或存储器。从编程的角度来看,流和通道之间的关键区别在于流是基于字节的,而通道是基于块的。流被设计成按顺序逐字节传输数据。出于性能原因,可以传输字节数组。然而

导语

缓冲器的设计的是新IO模型中最基础的一部分。因为新IO模型中要求所有的IO操作都需要进行缓冲。在新的IO模型中,不再向输出流写入数据和从数据流中读取数据了,而是要从缓冲区中读写数据。缓冲区可是是数组,也可以是与硬件或内存直接连接。
从编程的角度来看,流和通道之间的关键区别子在于流是基于字节的,而通道是基于块的。流设计为按顺序一字节接一字节地传输数据。出于性能的考虑,可以传送字节数组。不过,基本的概念都是一次传输一个字节的数据。与之不同的是,通道会传输缓冲区中的数据块。

基本概念

缓冲区可以看作是固定大小的元素列表,这些元素为特定类型,一般是基本数据类型。除boolean外,java中所有基本数据类型都有特定的Buffer子类:ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer和DoubleBuffer。每个子类中的方法都有相应类型的返回值和参数列表。例如:DoubleBuffer类有设置和获取double的方法。不管缓冲区具体是什么类型,他们都有四个关键部分。

position

缓冲区中见读取或写入的位置,读取和写入时都从这个位置开始,其初始值为0,最大等于缓冲区的大小。可以用下面两个方法来获取和设置该值:

public final int position()
public final Buffer position(int newPosition)

capacity

缓冲区可以保存的元素最大数目。容量值在创建缓冲区时设置,此后不能改变,可以用下面的方式读取:

public final int capacity()

limit

缓冲区中可访问的末尾位置,读或写时只能到limit的前一个位置,其初始值等于capacity。通过下面两个方法来控制:

public final int limit()
public final Buffer limit(int newLimit)

mark

在对缓冲区进行读写的时候用户可以通过mark进行标记。调用mark()时,mark的值等于position的值。调用reset()可以将position的值设置为mark值。

public final Buffer mark()
public final Buffer reset()

对缓冲区的操作还有其他几个方法。clear()方法将位置设置为0,将限度设置为容量,从而将缓冲区"清空"。这样就可以重新填充缓冲区了。rewind()的方法将位置设置为0,这样就可以重新读取缓冲区了。flip()方法将限度设置为当前位置,位置设置为0。

创建缓冲区

分配

通过allocate()方法创建一个固定大小的缓冲区。该方法创建的缓冲区是基于Java数组实现的。

ByteBuffer buffer = ByteBuffer.allocate(100);

直接分配

ByteBuffer类(其他类没有)有另外一个方法是 allocateDirect()方法,这个方法不为缓冲区创建Java数组。JVM会对以太网卡,核心内存或其他位置上的缓冲区使用直接内存访问,这样可以提升IO操作的性能。不过在API角度来说,两者在使用上是没有区别的。创建直接缓冲区的代价比间接缓冲区要高,除非必须要使用直接缓冲区则去创建它。

包装

如果有了要输出的数据数组,则可以用缓冲区进行包装。例如:

byte[] data = "Some data".getBytes("UTF-8");
ByteBuffer buffer = ByteBuffer.wrap(data);

在这里,缓冲区直接将该数组作为后备数组,即该数组已经变为缓冲区了。所以对该数组的操作将直接反应到缓冲区,反之亦然。

填充和排空

缓冲区是为顺序访问而设计的。没有缓冲区都有一个当前位置,由position来标识。从缓冲区读取或写入一个元素时,position都将增1。例如:假设你想分配一个容量为12的CharBuffer,并在其中放置5个字符:

CharBuffer buffer = CharBuffer.allocate(12);
buffer.put('H');
buffer.put('e');
buffer.put('l');
buffer.put('l');
buffer.put('o');

缓冲区现在的位置为5。这称为填充缓冲区。如果现在试图使用get()从缓冲区获取数据,则会得到null字符。要想读取之前写入的数据需要调用flip()将缓冲区回绕。当然还有两个扩展方法可以指定位置来读或写。

public abstract byte get(int index)
public abstract ByteBuffer put(int index, byte b)

批量方法

即使使用缓冲区,操作数据块通常比一次填写一个元素要快。不同的缓冲区都有一些批量方法来填充或排空相应的数组。例如:

public ByteBuffer get(byte[] dst, int offset, int length);
public ByteBuffer get(byte[] dst)
public ByteBuffer put(byte[] array, int offset, int length)
public ByteBuffer put(byte[] array)

复制缓冲区

经常需要建立缓冲区的副本,从而将相同的信息分发到两个或多个通道。6中特定类型缓冲区类提供了duplicate()方法来完成这项工作:

public abstract ByteBuffer duplicate()
public abstract IntBuffer duplicate()
public abstract ShortBuffer duplicate()
public abstract FloatBuffer duplicate()
public abstract CharBuffer duplicate()
public abstract DoubleBuffre duplicate()

返回值是对原来缓冲区的复制,但不复制简介缓冲区(建立缓冲区时创建的用于存储数据的数组)。修改通过复制得到的缓冲区中的数据会直接反应到另一个缓冲区。这个方法主要用于只是读取缓冲区中的数据。虽然多个缓冲区共享数据,但是每个缓冲区都有独立的标记,限度和位置。希望多个通道大致并行地传输相同的数据时,这个方法特别有用。下面是改版后的单文件传输服务器:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Set;

public class NonblockingSingleFileHTTPServer {
	private ByteBuffer contentBuffer;
	private final byte[] header;
	private final byte[] discard;
	private int port;
	
	
	public NonblockingSingleFileHTTPServer(int _port, byte[] data,
			String encoding, String contentType) {
		port = _port;
		String respHeader = "HTTP/1.1 200 OK
"
                + "Server: OneFile
"
                + "Content-length: "+ data.length +"
"
                + "Content-Type: " + contentType + ";charset=" + encoding + "

";
		header = respHeader.getBytes();
		contentBuffer = ByteBuffer.allocate(header.length + data.length);
		contentBuffer.put(header);
		contentBuffer.put(data);
		contentBuffer.position(0);
		discard = new byte[4096];
	}
	
	private void start() {
		Selector selector;
		try {
			ServerSocketChannel server = ServerSocketChannel.open();
			server.configureBlocking(false);
			server.bind(new InetSocketAddress(port));
			selector = Selector.open();
			server.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
		
		while (true) {
			try {
				selector.select();
			} catch (IOException e) {
				e.printStackTrace();
				break;
			}
			
			Set<SelectionKey> readykeys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = readykeys.iterator();
			ByteBuffer buffer = ByteBuffer.wrap(discard);
			while (iterator.hasNext()) {
				SelectionKey key = iterator.next();
				iterator.remove();
				try {
					if (key.isAcceptable()) {
						ServerSocketChannel server = (ServerSocketChannel) key.channel();
						SocketChannel client = server.accept();
						client.configureBlocking(false);
						SelectionKey key2 = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
						key2.attach(contentBuffer.duplicate());
					} else if (key.isReadable()) {
						SocketChannel client = (SocketChannel) key.channel();
						buffer.clear();
						client.read(buffer);
					} else if (key.isWritable()) {
						SocketChannel client = (SocketChannel) key.channel();
						ByteBuffer duplication = (ByteBuffer) key.attachment();
						if (duplication.hasRemaining()) 
							client.write(duplication);
						else {
							client.close();
							key.cancel();
						}
					}
				} catch (IOException e) {
					if (key.isAcceptable())
						e.printStackTrace();
					else {
						key.cancel();
						try {
							key.channel().close();
						} catch (IOException e1) {}
					}
				}
			}
		}
	}
	
	public static void main(String[] args) {
		int port;
		String encoding = "utf-8";
		
		if (args.length == 0) {
			System.out.println("Usage: java NonblockingSingleFileHTTPServer file port encoding");
			return;
		}
		
		if (args.length > 2) 
			encoding = args[2];
		
		try {
			port = Integer.parseInt(args[1]);
		} catch (NumberFormatException e) {
			port = 80;
		}
		
		Path path = Paths.get(args[0]).toAbsolutePath();
		String contentType = URLConnection.getFileNameMap().getContentTypeFor(path.toString());
		
		try {
			byte[] data = Files.readAllBytes(path);
			NonblockingSingleFileHTTPServer server = new NonblockingSingleFileHTTPServer(port, data, encoding, contentType);
			server.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

免责声明:文章转载自《缓冲器的学习》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Spring的学习(四、Spring事务管理)numpy中的数组之间进行集合运算下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

重温CSS:Border属性

边界是众所周知的,有什么新的东西吗?好吧,我敢打赌,在这篇文章中,有很多你不看永远不知道的东西! 不仅可以用CSS3来创建圆角,使用原有CSS一样可以显示自定义图形。这是正确的(有待考究);在过去,没发现这种技术之前,我们可能使用背景图像定位来显示一个园或箭头。幸运的是,我们能放下PS图象处理软件了。 基础 你可能很熟悉边的最基本用法。 1 borde...

Delphi下使用指针的简单总结(指针的赋值,数组和指针的转换,函数指针的使用)

由于最近公司太忙,好久没有更新我的BLOG了。原来想着写写关于HOOK驱动的文章,可是最后想想好久已经没有做驱动的东西了,怕写出来有错误,于是作罢。开发游戏也有一段时间了,发现使用DELPHI来开发网络游戏不了解DELPHI下指针的使用是完全不行的。所以今天我简单总结以下我使用DELPHI指针的心得。希望对大家有所帮助。 记得在大学学习C语言的时候在谭浩强...

第三节:Vue3向下兼容2(v-for、数组方法、v-model、计算属性、监听器)

一. 基本指令  1. v-for 数据准备 data() { return { userInfo: { name: 'ypf', age: 18, school: '北大' }, movies: ["星际穿越",...

笔试题算法系列(八)百度有趣的排序

[编程题] 有趣的排序 时间限制:1秒 空间限制:32768K 度度熊有一个N个数的数组,他想将数组从小到大 排好序,但是萌萌的度度熊只会下面这个操作:任取数组中的一个数然后将它放置在数组的最后一个位置。问最少操作多少次可以使得数组从小到大有序? 输入描述: 首先输入一个正整数N,接下来的一行输入N个整数。(N <= 50, 每个数的绝对值小...

C# 数据类型

C#的数据类型可以分为3类:数值类型,引用类型,指针类型.指针类型仅在不安全代码中使用.值类型包括简单类型(如字符型,浮点型和整数型等),集合类型和结构型.引用类型包括类类型,接口类型,代表类型和数组类型.值类型和引用类型的不同之处是值类型的变量值直接包含数据,而引用类型的变量把它们的引用存储在对象中.对于引用类型的变量,完全有可能让两个不同的变量引用同一...

iOS ---不一样的NSLog打印(精准打印)

  在iOS开发过程中,调试是很重要的过程,而除了各种断点调试(普通断点、条件断点、全局断点)之外,似乎NSLog是我们调试最常用的方法,当然,也是最简单朴素的寻debug方法。   在项目中,我们常使用的NSLog的语句无外乎以下一种:    NSLog(@"打印字符串:%@",name); NSLog(@"打印整形:%i",number);//或...