1、流一旦打开,就必须关闭,使用close方法,
放入finally语句块中(finally语句块一定会执行)
多个流互相调用,只关闭最外层的流。
2、InputStreamReader的reader方法,返回所读取的字节的int类型(0-255),
read(byte[] data)将读取的字节,存储在这个数组里,返回传入数组参数个数
3、write(byte b[], int off, int len) 将指定字节传入数据源,off是第一个字节的位置,len是长度
4、JAVA序列化
是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。
实现serializable接口,使用ObjectOutPutStream类的writeObject()方法
2023/4/1
Java-NIO
则是JDK1.4
中新引入的API
,它在BIO
功能的基础上实现了非阻塞式的特性,其所有实现都位于java.nio
包下。NIO
是一种基于通道、面向缓冲区的IO
操作,相较BIO
而言,它能够更为高效的对数据进行读写操作,同时与原先的BIO
使用方式也大有不同。
Java-NIO
是基于多路复用模型实现的,其中存在三大核心理念:Buffer
(缓冲区)、Channel
(通道)、Selector
(选择器),与BIO
还有一点不同在于:由于BIO
模型中数据传输是阻塞式的,因此必须得有一条线程维护对应的Socket
连接,在此期间如若未读取到数据,该线程就会一直阻塞下去。而NIO
中则可以用一条线程来处理多个Socket
连接,不需要为每个连接都创建一条对应的线程维护。
Buffer缓冲区
缓冲区其实本质上就是一块支持读/写操作的内存,底层是由多个内存页组成的数组,我们可以将其称之为内存块,在Java中这块内存则被封装成了Buffer
对象,需要使用可直接通过已提供的API
对这块内存进行操作和管理。
// 缓冲区抽象类
public abstract class Buffer {
- // 标记位,与mark()、reset()方法配合使用,
- // 可通过mark()标记一个索引位置,后续可随时调用reset()恢复到该位置
- private int mark = -1;
- // 操作位,下一个要读取或写入的数据索引
- private int position = 0;
- // 限制位,表示缓冲区中可允许操作的容量,超出限制后的位置不能操作
- private int limit;
- // 缓冲区的容量,类似于声明数组时的容量
- private int capacity; long address;
- // 清空缓冲区数据并返回对缓冲区的引用指针
- // (其实调用该方法后缓冲区中的数据依然存在,只是处于不可访问状态)
- // 该方法还有个作用:就是调用该方法后会从读模式切换回写模式
- public final Buffer clear();
- // 调用该方法后会将缓冲区从写模式切换为读模式
- public final Buffer flip();
- // 获取缓冲区的容量大小
- public final int capacity();
- // 判断缓冲区中是否还有数据
- public final boolean hasRemaining();
- // 获取缓冲区的界限大小
- public final int limit();
- // 设置缓冲区的界限大小
- public final Buffer limit(int n);
- // 对缓冲区设置标记位
- public final Buffer mark();
- // 返回缓冲区当前的操作索引位置
- public final int position();
- // 更改缓冲区当前的操作索引位置
- public final Buffer position(int n);
- // 获取当前索引位与界限之间的元素数量
- public final int remaining();
- // 将当前索引转到之前标记的索引位置
- public final Buffer reset();
- // 重置操作索引位并清空之前的标记
- public final Buffer rewind();
- // 省略其他不常用的方法.....
}
对于Java中缓冲区的定义,首先要明白,当缓冲区被创建出来后,同一时刻只能处于读/写中的一个状态,同一时间内不存在即可读也可写的情况。理解这点后再来看看它的成员变量,重点理解下述三个成员:
pasition
:表示当前操作的索引位置(下一个要读/写数据的下标)。capacity
:表示当前缓冲区的容量大小。limit
:表示当前可允许操作的最大元素位置(不是下标,是正常数字)。
pasition
是变化的,每次都会记录着下一个要操作的索引下标,当发生模式切换时,操作位会置零,因为模式切换代表新的开始。
重点记住clear()、flip()
方法,这两个方法都可以让缓冲区发生模式转换,flip()
可以从写模式切换到读模式,而clear()
方法本质上是清空缓冲区的意思,但清空后就代表着缓冲区回归“初始化”了,因此也可以从读模式转换到最初的写模式。
不过要注意:
Buffer
类仅是一个抽象类,所以并不能直接使用,因此当我们需要使用缓冲区时,需要实例化它的子类,但它的子类有几十之多,但一般较为常用的子类就只有八大基本数据类型的缓冲区,如ByteBuffer、CharBuffer、IntBuffer......
Buffer缓冲区的使用方式
当需要使用缓冲区时,都是通过xxxBuffer.allocate(n)
的方式创建
ByteBuffer buffer = ByteBuffer.allocate(10);
表示创建一个容量为10
的ByteBuffer
缓冲区,当需要使用该缓冲区时,都是通过其提供的get/put
类方法进行操作,这也是所有Buffer
子类都会提供的两类方法
// 读取缓冲区中的单个元素(根据position决定读取哪个元素)
public abstract xxx get();
// 读取指定索引位置的字节(不会移动position)
public abstract xxx get(int index);
// 批量读取多个元素放入到dst数组中
public abstract xxxBuffer get(xxx[] dst);
// 根据指定的偏移量(起始下标)和长度,将对应的元素读取到dst数组中
public abstract xxxBuffer get(xxx[] dst, int offset, int length);// 将单个元素写入缓冲区中(根据position决定写入位置)
public abstract xxxBuffer put(xxx b);
// 将多个元素写入缓冲区中(根据position决定写入位置)
public abstract xxxBuffer put(xxx[] src);
// 将另一个缓冲区写入进当前缓冲区中(根据position决定写入位置)
public abstract xxxBuffer put(xxxBuffer src);
// 向缓冲区的指定位置写入单个元素(不会移动position)
public abstract xxxBuffer put(int index, xxx b);
// 根据指定的偏移量和长度,将多个元素写入缓冲区中
public abstract xxxBuffer put(xxx[] src, int offset, int length);
一般在使用缓冲区的时候都会遵循如下步骤:
- ①先创建对应类型的缓冲区
- ②通过
put
这类方法往缓冲区中写入数据 - ③调用
flip()
方法将缓冲区转换为读模式 - ④通过
get
这类方法从缓冲区中读取数据 - ⑤调用
clear()、compact()
方法清空缓冲区数据
Buffer缓冲区的分类
Java中的缓冲区也被分为了两大类:本地直接内存缓冲区与堆内存缓冲区,前面Buffer
类的所有子实现类xxxBuffer
本质上还是抽象类,每个子抽象类都会有DirectXxxBuffer、HeapXxxBuffer
两个具体实现类,这两者的主要区别在于:创建缓冲区的内存是位于堆空间之内还是之外。
一般情况下,直接内存缓冲区的性能会高于堆内存缓冲区,但申请后却需要自行手动管理,不像堆内存缓冲区由于处于堆空间中,会有GC
机制自动管理,所以直接内存缓冲区的安全风险要高一些。两者之间的工作原理如下:
由于堆缓冲区创建后是存在于堆空间中的,所以IO
数据必须要经过一次本地内存的“转发后”才能达到堆内存,因此效率自然会低一些,同时也会占用Java堆空间。所以如若追求更好的IO
性能,或IO
数据过于庞大时,可通过xxxBuffer.allocateDirect()
方法创建本地缓冲区使用,也可以通过isDirect()
方法来判断一个缓冲区是否基于本地内存创建。
参考文档:Javadoop_首页
ByteBuffer allocate = ByteBuffer.allocate(1024);
allocate.put((byte) 48);
allocate.put((byte) 49);
allocate.put((byte) 50);
allocate.put((byte) 51); //position=4;limit=1024
allocate.flip(); //写模式转换成读模式 position=0;limit=4
allocate.get(); //返回48 position变成1
allocate.get(); //返回49 position变成2
allocate.get(); //返回50 position变成3
allocate.array(); //返回数组 position没影响
//capacity一直1024