Java IO
- 普通 IO ,面向流,同步阻塞线程。
- NIO,面向缓冲区,同步非阻塞。
BIO、NIO、AIO的概述
首先,传统的 java.io包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
java.io包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。
很多时候,人们也把 java.net下面提供的部分网络 API,比如 Socket、ServerSocket、HttpURLConnection 也归类到同步阻塞 IO 类库,因为网络通信同样是 IO 行为。
第二,在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。
第三,在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
IO 流
IO流简单来说就是input和output流,IO流主要是用来处理设备之间的数据传输,Java IO对于数据的操作都是通过流实现的,而java用于操作流的对象都在IO包中。
IO流的本质是数据传输,并且流是单向的
img
分类
-
按照操作数据类型分类
- 字节流
InputStream
、OutputStream
- 字符流
Reader
、Writer
区别
-
单位不同:字节流单位8bit长,也就是1byte;字符流单位根据不同编码而变化,
a、A、中、+、*、の......均表示一个字符, 一般 utf-8 编码下,一个汉字 字符 占用 3 个 字节; 一般 gbk 编码下,一个汉字 字符 占用 2 个 字节;Java 默认编码是uincode
-
处理对象不同:字节流可以处理任意类型数据,字符流只能处理字符相关数据
- 字节流
-
按照流向分类
- 输入流
Reader
、InputStream
- 输出流
Writer
、OutputStream
- 输入流
-
按照流的用途分类3
-
实体流
直接连接数据源的流类,如
FileInputStream
-
装饰流
以其他流对象(实体流或装饰流)为基础建立的流类,如
DataInputStream
/DataOutputStream
和BufferedReader
/BufferedWriter
-
InputStream
ByteArrayInputStream
-
StringBufferInputStream
// 已经过时 FileInputStream
PipedInputStream
PipedInputStream 内部维护了一个字节数组 buffer 默认大小为1024
PipedOutputStream 内部指向了一个 PipedInputStream借助于PipedInputStream 内部的循环数组进行数据缓存
read和write会向上抛出异常,不会返回-1,所以需要用
available()
方法来进行访问
-
ObjectInputStream
也是装饰流,读取对象
如何判断结束
- 把对象全部放入一个对象中,读的时候,读一个就可以了
- 捕获EOF异常,对异常进行处理
- 写文件的时候规定最后一个为null,或者特殊的字符,来约定结束
-
FilterInputStream
和他的子类都是装饰流DataInputStream
BufferedInputStream
-
SequenceInputStream
序列流,作用是合并多个流变成一个流
public class learnIO {
public static void main(String[] args) throws Exception {
System.out.println("---------------------------byte输入流测试--------------------------------");
byte[] bytes=new byte[26];
byte n=0;
for (int i = 0; i < bytes.length; i++) {
bytes[i]=n++;
}
int c;
ByteArrayInputStream bIn=new ByteArrayInputStream(bytes);
while ((c=bIn.read())!=-1){
System.out.print(c+" ");
}
System.out.println("\n---------------------------StringBuffer输入流测试--------------------------------");
StringBuffer stringBuffer=new StringBuffer("123456789456");
StringBufferInputStream stringBufferInputStream=new StringBufferInputStream(stringBuffer.toString());
while ((c=stringBufferInputStream.read())!=-1){
System.out.print((char)c+" ");
}
System.out.println("\n---------------------------FileInputStream输入流测试--------------------------------");
FileInputStream fileInputStream=new FileInputStream("src/testfile.txt");
while ((c=fileInputStream.read())!=-1){
System.out.print((char) c+" ");
}
System.out.println("\n---------------------------PipedInputStream 输入流测试--------------------------------");
PipedInputStream pipedInputStream=new PipedInputStream();
PipedOutputStream pipedOutputStream=new PipedOutputStream();
pipedOutputStream.connect(pipedInputStream);
Thread writeThread= new Thread(
new Runnable() {
@Override
public void run() {
try {
System.out.println("写了");
for (int i=0;i<10;i++) {
System.out.print(" "+i);
pipedOutputStream.write(i);
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println();
}
}
);
Thread readThread= new Thread(
new Runnable() {
@Override
public void run() {
int r;
System.out.println("读了");
while (true){
try {
if (pipedInputStream.available()==0) break;//read和write会向上抛出异常,不会返回-1,所以需要用 `available()`方法来进行访问
r=pipedInputStream.read();
System.out.print(" "+r);
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println();
}
}
);
writeThread.start();
Thread.sleep(50);
readThread.start();
Thread.sleep(50);
System.out.println("\n---------------------------ObjectInputStream 输入流测试--------------------------------");
// 首先写输入
FileOutputStream fileOutputStream=new FileOutputStream("src/Obj.txt");
ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);
for (int i=0;i<1;i++) objectOutputStream.writeObject(new String("你好,写个对象"));
//开始读
fileInputStream=new FileInputStream("src/Obj.txt");
ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);
//
Object res;
while (true){
// 有三种方法判断结束
/*
1. 把对象全部放入一个对象中,读的时候,读一个就可以了
2. 捕获EOF异常,对异常进行处理
3. 写文件的时候规定最后一个为null,或者特殊的字符,来约定结束
*/
try {
res=objectInputStream.readObject();
System.out.println("读了 "+(String) res);
}catch (Exception e){
break;
}
}
System.out.println("\n---------------------------FilterInputStream 输入流测试--------------------------------");
System.out.println("\n---------------------------DataInputStream 输入流测试--------------------------------");
fileOutputStream=new FileOutputStream("src/testfile.txt");
DataOutputStream dataOutputStream=new DataOutputStream(fileOutputStream);
dataOutputStream.writeUTF("123456");
dataOutputStream.writeInt(12);
fileInputStream=new FileInputStream("src/testfile.txt");
DataInputStream dataInputStream=new DataInputStream(fileInputStream);
System.out.println(dataInputStream.readUTF());
System.out.println(dataInputStream.readInt());
System.out.println("\n---------------------------BufferedInputStream 输入流测试--------------------------------");
fileInputStream=new FileInputStream("src/testfile.txt");
BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);
while ((c=bufferedInputStream.read())!=-1){
System.out.print(c+" ");
}
System.out.println("\n---------------------------SequenceInputStream 输入流测试--------------------------------");
FileInputStream fileInputStream1=new FileInputStream("src/Obj.txt");
fileInputStream=new FileInputStream("src/testfile.txt");
SequenceInputStream sequenceInputStream=new SequenceInputStream(fileInputStream,fileInputStream1);
while ((c=sequenceInputStream.read())!=-1){
System.out.print(c+" ");
}
}
System.out.println("\n---------------------------pushBack 输入流测试--------------------------------");
String str = "www.baidu.com" ; // 定义字符串
PushbackInputStream push = null ; // 定义回退流对象
ByteArrayInputStream bai = null ; // 定义内存输入流
bai = new ByteArrayInputStream(str.getBytes()) ; // 实例化内存输入流
push = new PushbackInputStream(bai) ; // 从内存中读取数据
int temp = 0 ;
push.unread('a');
while((temp=push.read())!=-1){ // 读取内容
if(temp=='.'){ // 判断是否读取到了“.”
push.unread(temp) ; // 放回到缓冲区之中
temp = push.read() ; // 再读一遍
System.out.print("(退回"+(char)temp+")") ;
}else{
System.out.print((char)temp) ; // 输出内容
}
}
}
Reader
FileReader
BufferReader
StringReader
用法大同小异,只不过读取的单位一个是字节,一个字符
同一个文件读取的对比如下,默认是UTF8解码
[图片上传失败...(image-8d87ad-1630396189504)]
NIO
三个组成部分
Channel
Channel是客户端连接的一个抽象,当每个客户端连接到服务器时,服务器都会为其生成一个Channel对象;
Buffer
Buffer从字面上讲是一个缓存,本质上其是一个字节数组,通过Buffer,可以从Channel上读取数据,然后交由下层的处理器进行处理。这里的Buffer的优点在于其封装了一套非常简单的用于读取和写入数据Api。
Selector
Selector则是Java NIO实现高性能的关键,其本质上使用了IO多路复用的原理,通过一个线程不断的监听多个Channel连接来实现多所有这些Channel事件进行处理,这样的优点在于只需要一个线程就可以处理大量的客户端连接,当有客户端事件到达时,再将其分发出去交由其它线程处理;


实现:
Channel:
FileChannel:从文件中读写数据
public static void main(String[] args) throws Exception {
RandomAccessFile file=new RandomAccessFile("src/com/learn/test.txt","rw");
FileChannel inChannel=file.getChannel();
ByteBuffer buf=ByteBuffer.allocate(48);;
int bytesRead = inChannel.read(buf);//返回读了多少个数
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//buf.flip();// 确认现在缓存区可读内容的大小,即改变指针poison,和最大范围limit
byte[] buffer = new byte[50];
int index=0;
while(buf.hasRemaining()){
buffer[index++]=buf.get();
}
System.out.println(new String(buffer,0,index));// 防止乱码
buf.clear();// 重置指针poison,和最大范围limit
bytesRead = inChannel.read(buf);
}
file.close();
}
DatagramChannel:能通过UDP读写网络中的数据
datagramChannel = DatagramChannel.open();//获取channel datagramChannel.socket().bind(new InetSocketAddress("127.0.0.1",9999));//绑定自身端口号 ByteBuffer buffer = ByteBuffer.allocate(50);//需要缓存区 datagramChannel.send(buffer, new InetSocketAddress("127.0.0.1",8989));//发送到目标地址
public class learnNIO {
public static void main(String[] args) throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress("127.0.0.1",8887));
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始监听");
ByteBuffer buffer = ByteBuffer.allocate(50);
DatagramChannel datagramChannel = null;
byte[] b;
try {
datagramChannel = DatagramChannel.open();
datagramChannel.socket().bind(new InetSocketAddress("127.0.0.1",8989));
} catch (IOException e) {
e.printStackTrace();
}
while (true){
// 清空Buffer
buffer.clear();
// 接受客户端发送数据
SocketAddress socketAddress = null;
try {
socketAddress = datagramChannel.receive(buffer);
} catch (IOException e) {
e.printStackTrace();
}
if (socketAddress != null) {
int position = buffer.position();
b = new byte[position];
buffer.flip();
for(int i=0; i<position; ++i) {
b[i] = buffer.get();
}
try {
System.out.println("receive remote " + socketAddress.toString() + ":" + new String(b, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
}).start();
ByteBuffer buf = ByteBuffer.allocate(50);
buf.clear();
String newData = "New String to write to file..." + System.currentTimeMillis();
buf.clear();
buf.put(newData.getBytes());
buf.flip();
System.out.println("开始发送");
int bytesSent = channel.send(buf, new InetSocketAddress("127.0.0.1",8989));
}
}
SocketChannel:能通过TCP读写网络中的数据
https://www.cnblogs.com/lxyit/p/9209407.html
ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
public class learnSocketChannel {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind( new InetSocketAddress("127.0.0.1", 8888));
System.out.println(serverSocketChannel.getLocalAddress());
new Thread(
new Runnable() {
@Override
public void run() {
while (true){
try {
SocketChannel socketChannel=serverSocketChannel.accept();
if (socketChannel!=null){
System.out.println(socketChannel);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
).start();
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("127.0.0.1", 8888));
socketChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("test end!");
}
}