IO流概念
流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出
IO流的分类
按流的流向不同分为:输入流,输出流
- 输入流:从别的地方获取资源 输入到 我们的程序中(只读)
- 输出流:从我们的程序中 输出到 别的地方(只写)。
按流的数据单位可以分为:字节流,字符流
- 字节流:以字节为单位读写数据,当传输的资源有中文时,可能会出现乱码。
- 字符流:以字符为单位读写数据,有中文时,使用该流就可以正确传输显示中文。
1.ASCII码和 Unicode 编码中,一个英文为一个字节,一个中文为两个字节。
2.UTF-8 编码中,一个英文字为一个字节,一个常用中文为三个字节。
3.Java的字符是Unicode的,所以字符流的一个字符占两个字节
按流的功能不同分为:节点流,处理流
- 节点流:程序用于直接操作目标设备所对应的类。
- 处理流:程序通过一个间接流类去连接和封装节点流,以达到更加灵活方便地读写各种类型的数据。
Io流的基类:
输入流 | 输出流 | |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
基类中定义了一些基本共性功能方法:
InputStream的常用方法
1.abstract int read():从输入流读一个字节。 如果到达流的末尾,则返回-1 。抽象方法 子类必须提供这个方法的实现。
2.int read(byte[] b): 将输入流中最多 b.length个字节读入 b数组 ,返回实际读的字节数。如果到达流的末尾,则返回-1 。
3.int read(byte[] b, int off, int len): 从偏移量 off开始将输入流中最多 len 个字节读入 byte 数组 ,返回实际读的字节数。如果到达流的末尾,则返回-1 。
4.close():关闭此输入流并释放与该流关联的所有系统资源
OutputStream的常用方法
1.abstract void write(int b):将一个字节写入输出流。b对应.ASCII码的一个字节
2.void write(byte[] b):将最多 b.length 个字节从byte 数组写入输出流
3. void write(byte[] b, int off, int len):byte 数组从偏移量 off 开始将最多 len 个字节写入此输出流
4. void flush():刷新此输出流并强制写出所有缓冲的输出字节
5. void close(): 关闭此输出流并释放与此流有关的所有系统资源
Reader的常用方法
1.int read():从输入流读一个字符。 如果到达流的末尾,则返回-1 。
2.int read(char[] cbuf): 将输入流中最多 cbuf.length个字符读入 cbuf数组 ,如果到达流的末尾,返回-1 。
3.abstract int read(char[] cbuf, int off, int len): 从偏移量 off开始将输入流中最多 len 个字节读入cbuf数组 ,如果到达流的末尾,返回-1 。抽象方法子类实现。
4.abstract void close():关闭该流并释放与之关联的所有资源,抽象方法子类实现。
Writer的常用方法
1.void write(char[] cbuf)::将 cbuf数组写入输出流
2.abstract void write(char[] cbuf, int off, int len):cbuf数组从偏移量 off 开始将最多 len 个字符写入输出流
3.void write(int c):将一个字符写入输出流。
4.void write(String str):将一个字符串写入输出流。
5.void write(String str, int off, int len) 字符串从偏移量 off 开始将len 个字符写入输出流
6.Writer append(char c) :追加指定字符到此 writer
7.Writer append(CharSequence csq):追加指定字符序列到此 writer
8.Writer append(CharSequence csq, int start, int end):追加字符序列的子序列添加到此 writer
9.abstract void flush() :刷新该流的缓冲
10.abstract void close(): 关闭此流,但要先刷新它
如何选择IO流
首先我们要明确你操作的是什么(磁盘:文件 ,内存:数组,字符串,网络:管道(进程间通信) )?需要使用字节流还是字符流,是读还是写。这样我们就可以确定要使用的节点流
确定节点流后,再明确是否需要额外功能(处理流封装节点流)
- 需要转换—— 转换流 InputStreamReader 、OutputStreamWriter
- 需要高效—— 缓冲流Bufferedxxx
- 多个源—— 序列流 SequenceInputStream
- 对象序列化—— ObjectInputStream、ObjectOutputStream
- 保证数据的输出形式—— 打印流PrintStream 、Printwriter
- 基本数据,保证字节原样性——DataOutputStreamDataInputStream
如何使用IO流
第一步:创建对应的流,第二步:设置缓冲区(可以不设置,建议设置可以提高效率),第三步:读写操作,第四步:关闭流 。
IO流的应用
拷贝文件(图片,视频,音频等)
public static void main(String[] args) {
//文件路径
String path="C:\\Users\\Administrator\\Pictures\\Screenshots";
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
//第一步:创建一个文件输入流和输出流,为提高效率用BufferedInputStream封装。
bis= new BufferedInputStream(new FileInputStream(new File(path+"\\1.jpg")));
bos = new BufferedOutputStream( new FileOutputStream(new File(path+"\\2.jpg")));
//第二部:设置缓冲区
byte[] bytes=new byte[1024];
int len=0;
//第三步拷贝文件
while ((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//第四步:关闭资源
if (bis!=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos!=null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果是文本,为避免中文乱码问题,建议大家使用字符流,将上述代码的字节流,改为字符流即可。
将java对象存到磁盘再从磁盘中取出
public static void main(String[] args) {
String path="C:\\Users\\Administrator\\Pictures\\Screenshots";
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(new File(path+"\\obj.dat")));
ois = new ObjectInputStream(new FileInputStream(new File(path + "\\obj.dat")));
oos.writeObject(new Preson("路飞", 18,"男"));
oos.flush();
oos.writeObject(new Preson("娜美", 16,"女"));
oos.flush();
oos.writeObject(new Preson("乔巴", 10,"男"));
oos.flush();
Preson luFei = (Preson)ois.readObject();
Preson naMei = (Preson)ois.readObject();
Preson qiaoBa = (Preson)ois.readObject();
System.out.println(luFei);
System.out.println(naMei);
System.out.println(qiaoBa);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Preson类需要实现Serializable接口
public class Preson implements Serializable {
private String name;
private int age;
private String sex;
参考:史上最骚最全最详细的IO流教程,小白都能看懂!
参考:【Java基础-3】吃透Java IO:字节流、字符流、缓冲流
BIO和NIO
Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
BIO
同步阻塞I/O模式:因为socket.accept()、 socket.read()、 socket.write() 是同步阻塞的。举例:当服务器用read去客户端的的数据时,是无法预知对方是否已经发送数据的。因此在收到数据之前,当前线程会被挂起。无法做其他的工作。直到客户端把数据发过来。此时如果有另一个客户端要请求连接服务器,服务器必须开启另一个线程来处理客户端请求。每有一个客户端,服务器就要开启一个线程来处理。处理完成之后,线程才销毁。如果有大量的客户端连接请求,就要创建大量的线程,而线程的创建和销毁成本很高。并且可能会导致线程堆栈溢出、创建新线程失败等问题。不过可以通过线程池来优化,线程使用完后,不再是销毁而是放回线程池,供下一个请求使用。减少了线程的创建和销毁成本。线程池可以设置最大线程数量,因此它占用的资源是可控的。但也带来了一个问题,它的并发量有限,最大并发量为线程池的最大线程数量。
NIO
同步非阻塞的I/O模型:当服务器用read去客户端的的数据时,如果客户端没有发送数据,直接返回0,不会阻塞线程。因此在收到数据之前,该线程可以继续做其他的事情。 这段时间通常被用于执行其它通道上的IO操作,实现一个线程管理多个通道。
NIO有三大组件:Buffer(缓冲区),Channel(通道),Selector(选择器)
Buffer(缓冲区):NIO是面向缓冲区的。NIO中数据的读和写都必须经过Buffer。 Buffer可读可写,使用flip()方法切换读,使用clear()方法切换到写。若Buffer中有未读且后续还需要的数据,使用compact()方法切换到写(在未读的数据后继续写)。
Channel(通道):Channel和流非常相似,区别是:通道是双向的(可读可写),流是单向的(只能读或写)。NIO通道本身不存储数据,只是打开与IO设备之间的连接,所以通道必须要和缓冲区配合使用。NIO的强大功能部分来自于Channel的非阻塞特性,可以手动设置为非阻塞(注意:文件通道总是阻塞式的,因此不能设置为非阻塞)。
Selector(选择器)
Selector能够轮询检测多个注册的通道上是否有事件发生(事件:读就绪/写就绪/有新连接到来)。如果有事件发生,会通知Selector,然后针对每个事件进行相应的处理。实现一个线程管理多个通道。
客户端代码
public static void client(){
//设置缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
// 获取客户端的通道
socketChannel = SocketChannel.open();
// 手动设置为非阻塞
socketChannel.configureBlocking(false);
// 发起连接
socketChannel.connect(new InetSocketAddress("10.10.195.115",8080));
// 需要判断是否连接连接完成
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
//切换到写模式
buffer.clear();
//将数据写入缓冲区
buffer.put(info.getBytes());
//切换到读模式
buffer.flip();
//将缓冲区的数据写入通道。
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer);
}
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
finally{
//关闭资源
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
服务端代码
public class ServerConnect
{
private static final int BUF_SIZE=1024;
private static final int PORT = 8080;
private static final int TIMEOUT = 3000;
public static void main(String[] args)
{
selector();
}
public static void handleAccept(SelectionKey key) throws IOException{
//先从事件身上获取到通道
ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
//建立连接
SocketChannel sc = ssChannel.accept();
// 手动设置为非阻塞
sc.configureBlocking(false);
// 注册一个read事件
sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
}
public static void handleRead(SelectionKey key) throws IOException{
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer buf = (ByteBuffer)key.attachment();
long bytesRead = sc.read(buf);
while(bytesRead>0){
buf.flip();
while(buf.hasRemaining()){
System.out.print((char)buf.get());
}
System.out.println();
buf.clear();
bytesRead = sc.read(buf);
}
if(bytesRead == -1){
sc.close();
}
}
public static void handleWrite(SelectionKey key) throws IOException{
ByteBuffer buf = (ByteBuffer)key.attachment();
buf.flip();
SocketChannel sc = (SocketChannel) key.channel();
while(buf.hasRemaining()){
sc.write(buf);
}
buf.compact();
}
public static void selector() {
Selector selector = null;
ServerSocketChannel ssc = null;
try{
// 开启选择器
selector = Selector.open();
// 开启服务器端的通道
ssc= ServerSocketChannel.open();
// 绑定端口
ssc.socket().bind(new InetSocketAddress(PORT));
// 设置为非阻塞
ssc.configureBlocking(false);
// 将通道注册到选择器上
ssc.register(selector, SelectionKey.OP_ACCEPT);
//死循环
while(true){
//轮询获取选择器上已经“准备就绪”的事件。阻塞TIMEOUT时间,退出本次循环。
if(selector.select(TIMEOUT) == 0){
System.out.println("==");
continue;
}
//获取选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
// 获取单个事件
SelectionKey key = iter.next();
//可能是accept
if(key.isAcceptable()){
//处理事件
handleAccept(key);
}
// 可能是read
if(key.isReadable()){
handleRead(key);
}
// 可能是write
if(key.isWritable() && key.isValid()){
handleWrite(key);
}
//判断是否连接,没有移除事件。
if(key.isConnectable()){
System.out.println("isConnectable = true");
}
iter.remove();
}
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(selector!=null){
selector.close();
}
if(ssc!=null){
ssc.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}
代码来自:Java NIO?看这一篇就够了!
参考:Java NIO浅析