区别点:IO是面向流,NIO是面向缓冲区
https://www.cnblogs.com/androidsuperman/p/7082066.html 缓冲区buffer中的数据存取
缓冲区buffer的四个核心属性
position
:位置,表示缓冲区中正在操作数据的位置limit
:界限,表示缓冲区可以操作的数据的大小(limit之后的数据都不能进行读写)capacity
:容量mark
:把position做一个标记点,如果reset方法执行后position可以回到此标记点
缓冲区buffer的两个核心方法
put()
:存入数据到缓冲区中get()
:获取缓冲区中的数据flip()
:在flip()方法之后才可以用get方法获取数据
代码演示:
//获取缓存区的几个属性值
System.out.println("===========allocate方法=========");
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//0 1024 1024
System.out.println("============put方法==============");
//用put方法存入数据
String a = "qiuyiping";
buf.put(a.getBytes());
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//9 1024 1024
System.out.println("==================flip方法==================");
buf.flip();
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//0 9 1024
//用get方法获取数据
System.out.println("==============get方法=============");
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length));
//qiuyiping
//用rewind方法可重复读取数据
System.out.println("================rewind方法==================");
buf.rewind();
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//0 9 1024
//用clear方法可以清空缓冲区,但是里面的数据依然存在
System.out.println("================clear方法=================");
buf.clear();
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//第一项存在,证明里面的数据存在
System.out.println((char) buf.get());
//0 1024 1024
//测试mark方法和reset方法-把position恢复到mark标记处
ByteBuffer buf = ByteBuffer.allocate(1024);
String s = new String("qiuyping");
buf.put(s.getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2);
System.out.println(new String(dst, 0, 2));
buf.mark();
buf.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println("此时的position值是:" + buf.position());
//4
buf.reset();
System.out.println("reset之后的回到mark标记处的position值是:" + buf.position());
//2
直接缓冲区和非直接缓冲区
allocate(1024)
:获取非直接缓冲区allocateDirect(1024)
:获取直接缓冲区isDirect()
:判断是否为直接缓冲区
代码演示:
ByteBuffer buf = ByteBuffer.allocate(1024);
ByteBuffer bufDirect = ByteBuffer.allocateDirect(1024);
System.out.println(buf.isDirect());
System.out.println(bufDirect.isDirect());
非直接缓冲区代码演示:
@Test
//利用通道完成文件复制(非直接缓冲区)
public void nonDirectBuffer() throws IOException {
long startTime = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("c://动画.jpg");
FileOutputStream fos = new FileOutputStream("c://动画1.jpg");
// ①获取到通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
// ②分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
while (inChannel.read(buf) != -1) {
buf.flip();// 切换到读取模式
outChannel.write(buf);
buf.clear();// 清空缓冲区
}
// 关闭连接
outChannel.close();
inChannel.close();
fos.close();
fis.close();
long endTime = System.currentTimeMillis();
System.out.println("非缓冲区:" + (endTime - startTime));
}
直接缓冲区代码演示:
@Test
//使用直接缓冲区完成文件的复制(内存映射文件)
public void DirectBuffer() throws IOException {
long startTime = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("c://动画.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("c://动画2.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
// 映射文件
MappedByteBuffer inMapperBuff = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMapperBuff = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓冲区进行数据读写操作
byte[] dst = new byte[inMapperBuff.limit()];
inMapperBuff.get(dst);
outMapperBuff.put(dst);
outChannel.close();
inChannel.close();
long endTime = System.currentTimeMillis();
System.out.println("内存映射文件耗时:"+(endTime-startTime));
}
通道之间的数据传输
transferFrom()
:FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中transferTo()
:transferTo()方法将数据从FileChannel传输到其他的channel中
代码演示:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
分散读取和聚集写入
分散读取:把缓冲区分成多份,通道可以从多个缓冲区中读取数据
聚集写入:把多个缓冲区中的数据通过通道读取出来
@Test
public void bufferScatterRead() throws IOException {
//1、获取通道
RandomAccessFile raf = new RandomAccessFile("c://wen.txt", "rw");
FileChannel channel = raf.getChannel();
//2、分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(200);
ByteBuffer buf1 = ByteBuffer.allocate(1024);
//3、分散读取
ByteBuffer[] bufs = {buf, buf1};
channel.read(bufs);
for (ByteBuffer b : bufs) {
b.flip();
}
System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
//聚集写入
RandomAccessFile raf1 = new RandomAccessFile("c://wen1.txt", "rw");
FileChannel channel1 = raf1.getChannel();
channel1.write(bufs);
}
字符集和字符编码
字符集类Charset
编码:字符串 -> 字节数组
解码:字节数组 -> 字符串
代码演示:
@Test
//显示所有可获取的编码方式
public void getAllCharset() {
SortedMap<String, Charset> map = Charset.availableCharsets();
for (String alias : map.keySet()) {
System.out.println(alias);
}
}
@Test
//把字符串以某种编码方式转换成字符数组-编码
public void transferByteAndChar(){
//1.创建简体中文对应的charset Charset.forName(...)
Charset charset = Charset.forName("GBK");
//2.获取charset对象对应的编码器 charset.newEncoder()
CharsetEncoder ce = charset.newEncoder();
//3.创建一个CharBuffer对象
CharBuffer cb = CharBuffer.allocate(30);
//4.往CharBuffer对象中放入值
cb.put("CSDN-IT专业社区");
//5.开始读取CharBuffer值
cb.flip();
//6.将CharBuffer中的字符序列转换成字节序列 charset.encode(...)
ByteBuffer bf = charset.encode(cb);
//7.循环访问byteBuffer中的每个字节
for (int i = 0; i < bf.limit(); i++) {
System.out.print(bf.get(i)+" ");
}
}
@Test
//把字节数组以某种方式解码成字符串-解码
public void transformByteBufferToCharBuffer() throws UnsupportedEncodingException, CharacterCodingException {
//1.创建简体中文对应的charset charset.forName(...)
Charset charset = Charset.forName("UTF-8");
//2.获取charset对象对应的解码 charset.newDecoder();
CharsetDecoder cd = charset.newDecoder();
//3.创建一个ByteBuffer对象,并存入值且准备读取
ByteBuffer bf = ByteBuffer.allocate(30);
bf.put("CSDN-IT专业社区".getBytes("UTF-8"));
bf.flip();
//4.将ByteBuffer的数据解码成字符序列 (...).decode(...)
System.out.println(cd.decode(bf));
}
NIO网络通信的阻塞与非阻塞式
阻塞式
阻塞式是服务器不能判断客户端什么时候发送数据过来,所以在不确定的过程中一直等待,此时服务器不能做什么事,因此处于阻塞状态,消耗了CPU的性能。传统IO就是这个过程,解决方法是在服务器端开启多个线程处理多个客户端的请求,即使有线程被阻塞了,其它线程还是可以运行,但CPU效率依然有损耗
非阻塞式
非阻塞式采用了selector选择器做中间人,其中注册了多个Channel,而channel中存放了buffer,只要Channel中的数据准备就绪了,才会把该channel和服务器端的某一个线程做对接,这样服务器不会处于等待过程,就不会有阻塞
使用 NIO 完成网络通信的三个核心
- 通道(Channel):负责连接
java.nio.channels.Channel 接口:
|–SelectableChannel
|–SocketChannel
|–ServerSocketChannel
|–DatagramChannel
|–Pipe.SinkChannel
|–Pipe.SourceChannel
- 缓冲区(Buffer):负责数据的存取
- 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
代码演示:
https://blog.csdn.net/xiaosong_2016/article/details/78767931(阻塞式和非阻塞式)
管道方式传输数据
代码演示:
@Test
public void testPipeChannel() throws IOException {
//1.获取管道
Pipe pipe = Pipe.open();
//2.通过管道获取sinkChannel,将缓冲区中的数据写入sinkChannel
Pipe.SinkChannel sinkChannel = pipe.sink();
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put("数据通过pipe通道传输".getBytes());
buf.flip();
sinkChannOel.write(buf);
//3.通过管道获取sourceChannel,通过sourceChannel读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
buf.flip();
int len = sourceChannel.read(buf);
System.out.println(new String(buf.array(), 0, len));
//4.关闭所有通道
sourceChannel.close();
sinkChannel.close();
}