IO流总结

IO流概念

流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出

IO流的分类

在这里插入图片描述
按流的流向不同分为:输入流,输出流

  1. 输入流:从别的地方获取资源 输入到 我们的程序中(只读)
  2. 输出流:从我们的程序中 输出到 别的地方(只写)。

按流的数据单位可以分为:字节流,字符流

  1. 字节流:以字节为单位读写数据,当传输的资源有中文时,可能会出现乱码。
  2. 字符流:以字符为单位读写数据,有中文时,使用该流就可以正确传输显示中文。

1.ASCII码和 Unicode 编码中,一个英文为一个字节,一个中文为两个字节。
2.UTF-8 编码中,一个英文字为一个字节,一个常用中文为三个字节。
3.Java的字符是Unicode的,所以字符流的一个字符占两个字节

按流的功能不同分为:节点流,处理流

  1. 节点流:程序用于直接操作目标设备所对应的类。
  2. 处理流:程序通过一个间接流类去连接和封装节点流,以达到更加灵活方便地读写各种类型的数据。

Io流的基类:

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter

基类中定义了一些基本共性功能方法:

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流

首先我们要明确你操作的是什么(磁盘:文件 ,内存:数组,字符串,网络:管道(进程间通信) )?需要使用字节流还是字符流,是读还是写。这样我们就可以确定要使用的节点流

字节
字符
文件File
字节/字符
读/写
XXXInputStream
XXXOutputStream
读/写
XXXReader
XXXWriter
数组Byte / CharArray
字符串String
管道Piped

确定节点流后,再明确是否需要额外功能(处理流封装节点流)

  • 需要转换—— 转换流 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浅析

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值