IO/NIO/AIO

目录

目录.png

IO

对类的存储和恢复实例

 public void restore() throws ClassNotFoundException, IOException {
        ObjectInputStream ois = null;
        File file = new File(this.getPersistenceDir(), this.getPersistenceFileName());
        if (file.exists()) {
            try {
                ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
                int count = 0;

                while (true) {
                    try {
                        Object e = ois.readObject();
                        this.queue.offer(e);
                        ++count;
                    } catch (EOFException e) {
                        this.logger.info("队列已从{}中恢复{}个消息.", new Object[]{file.getAbsolutePath(), Integer.valueOf(count)});
                        break;
                    }
                }
            } finally {
                if (ois != null) {
                    ois.close();
                }
            }
            file.delete();
        } else {
            this.logger.debug("队列的持久化文件{}不存在,不需要恢复", file.getAbsolutePath());
        }

    }

    //加个钩子 结束时调用
     Runtime.getRuntime().addShutdownHook(new Thread(new AsyncAppenderBase.ShutdownHook()));

    //钩子内容
    class ShutdownHook implements Runnable {
        ShutdownHook() {
        }

        public void run() {
            try {
                AsyncAppenderBase.this.stop();
            } catch (IOException e) {
                AsyncAppenderBase.this.logger.error(e.getMessage(), e);
            }

        }
    }

    public void stop() throws IOException {
        try {
            this.executor.shutdownNow();
            this.executor.awaitTermination((long) this.shutdownWait, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            this.logger.debug("awaitTermination被中断", e);
        }

        if (this.persistence) {
            synchronized (this.persistenceLock) {
                this.backup();
            }
        }

    }

    public void backup() throws IOException {
        ArrayList list = new ArrayList();
        this.queue.drainTo(list);
        if (!list.isEmpty()) {
            ObjectOutputStream oos = null;

            try {
                File file = new File(this.getPersistenceDir(), this.getPersistenceFileName());
                oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
                Iterator iterator = list.iterator();

                while (iterator.hasNext()) {
                    Object message = iterator.next();
                    oos.writeObject(message);
                }

                this.logger.info("队列已持久化{}个消息到{}", new Object[]{Integer.valueOf(list.size()), file.getAbsolutePath()});
            } finally {
                if (oos != null) {
                    oos.close();
                }
            }
        } else {
            this.logger.debug("队列为空,不需要持久化 .");
        }

    }

IO流图

IO流.jpg

字节流与字符流的区别

  • 字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点;所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列 字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
  • 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以;字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的 但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的

字节流与字符流的各自应用场景

字节流
  • Hessian客户端发送请求到Hessian服务端时,会从HessianConnection获取到PosterOutputStream,并使用HessianOutPut对参数进行序列化到PosterOutputStream。而PosterOutputStream继承自ByteArrayInputStream,只是他都线程安全的(方法都加了synchronized)。 所以Hessian是使用二进制格式传输,因为他不确定是否有文件图片之类的传输。
字符流
  • HttpServletResponse的response.getWriter()获取的是PrintWrite, 还有从System输入也可以用字符流,具体代码, 第二处的应用中InputStreamReader, OutputStreamWrite是字节流到字符流的转换
1.
response.getWriter().print("");
2.
 BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
 String sysIn = bufferRead.readLine();

字节流字符流各自类的应用

InputStream
  • ByteArrayInputStream -- 把内存中的一个缓冲区作为 InputStream 使用, ByteArrayInputStream(byte[]) 创建一个新字节数组输入流( ByteArrayInputStream ),它从指定字节数组中读取数据( 使用 byte 作为其缓冲区数组)
  • StringBufferInputStream -- 把一个 String 对象作为 InputStream .StringBufferInputStream(String) 据指定串创建一个读取数据的输入流串。
  • FileInputStream -- 把一个文件作为 InputStream ,实现对文件的读取操作,FileInputStream(File name) 创建一个输入文件流,从指定的 File 对象读取数据
  • RandomAccessFile: 随机读取,比如多线程读取文件,每个线程读取一段就合适。
  • PipedInputStream :实现了 pipe 的概念,主要在线程中使用 . 管道输入流是指一个通讯管道的接收端。
  • SequenceInputStream :把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来,并且使它们像单个输入流一样出现。每个输入流依次被读取,直到到达该流的末尾。
OutputStream
  • ByteArrayOutputStream : 把信息存入内存中的一个缓冲区中 . 该类实现一个以字节数组形式写入数据的输出流。当数据写入缓冲区时,它自动扩大。用 toByteArray() 和 toString() 能检索数据。
  • FileOutputStream: 文件输出流是向 File 或 FileDescriptor 输出数据的一个输出流。
  • PipedOutputStream: 管道输出流是指一个通讯管道的发送端。
Reader
  • CharArrayReader :与 ByteArrayInputStream 对应此类实现一个可用作字符输入流的字符缓冲区
  • StringReader : 与 StringBufferInputStream 对应其源为一个字符串的字符流。
  • FileReader : 与 FileInputStream 对应
  • PipedWrite :与 PipedOutputStream 对应
Java IO 的一般使用原则
  • 要缓冲: BufferedInputStream(继承自FileInStream所以与ByteArrayInputStream有明显区别), BufferedOutputStream(继承自FileOutStream所以与ByteArrayOutputStream有明显区别),( 字节流 ) BufferedReader, BufferedWriter( 字符流 )
  • 特殊需要
    1 、从 Stream 到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter
    2 、对象输入输出: ObjectInputStream, ObjectOutputStream
    3 、进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter
    4 、合并输入: SequenceInputStream
    5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader
参考文章

Java IO学习整理
理解Java中字符流与字节流的区别
Java I/O PrintWriter
字符流和字节流的区别,使用场景,相关类

基础总结

  1. 与输入有关系的类都继承自 InputStream, 输出OutputStream
  2. InputStream 或 Reader 派生而来的类都含有名为 read() 的基本方法,用于读取单个字节或者字节数组, OutputStream 或 Writer 派生而来的类都含有名为 write() 的基本方法,用于写单个字节或者字节数组
  3. 几乎所有原始的 Java I/O 流类都有相应的 Reader 和 Writer 类来提供原生的 Unicode 操作。但是在某些场合,面向字节的 InputStream 和 OutputStream 才是正确的解决方案。特别是 java.util.zip 类库就是面向字节而不是面向字符的。因此,最明智的做法是尽量尝试使用 Reader 和 Writer,一旦代码没法成功编译,你就会发现此时应该使用面向字节的类库了。(适配器:InputStreamReader)
  4. RandomAccessFile 适用于由大小已知的记录组成的文件, 所以我们可以使用 seek() 将文件指针从一条记录移动到另一条记录,然后对记录进行读取和修改,RandomAccessFile 的大多数功能(但不是全部)都被 nio 中的内存映射文件(mmap)取代
  5. 缓存输入文件
public static String read(String filename) {
    try (
            BufferedReader in = new BufferedReader(new FileReader(filename))
    ) {
        return in.lines().collect(Collectors.joining("\n"));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

public static void write() throws IOException {
    try (
        BufferedWriter writer = new BufferedWriter(new FileWriter("/Users/seegerlin/Desktop/bosun搭建Copy"));
    ){
        writer.write("contentTest");
    } catch (Exception ignored){

    }
}
  1. DataInputStream extends FilterInputStream

NIO

  • Hessian源码用的就是BIO,请求会阻塞,虽然每个请求都分配了对应线程,但操作系统本身也对线程的总数有一定的限制。
  • demo地址Java NIO的简单demo
  • 任何流类,都可以通过调用 getChannel( ) 方法生成一个 FileChannel(文件通道)。FileChannel 的操作相当基础:操作 ByteBuffer 来用于读写,并独占式访问和锁定文件区域(稍后将对此进行描述)。
public static void nioReadWrite() {
    try(
            FileChannel in = new FileInputStream(
                    "args").getChannel();
            FileChannel out = new FileOutputStream(
                    "args").getChannel()
    ) {
        // ByteBuffer 只包含字节
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(in.read(buffer) != -1) {
            // 准备写入 调用缓冲区上的 flip() 方法来准备好提取字节
            buffer.flip(); 
            out.write(buffer);
            // 准备读取 清除缓冲区,将 position 设置为零并 设 limit 为 capacity;
            buffer.clear();  
        }
    } catch(IOException e) {
        throw new RuntimeException(e);
    }
}
  • 将基本类型数据插入 ByteBuffer 的最简单方法就是使用 asCharBuffer() CharBuffer之类的
  • mapped内存映射文件代替RandomAccessFile,这样,一个非常大的文件(最多 2GB)可以很容易地修改,只操作一部分
public static void mappedFile() {
    int length = 100;
    try(
            RandomAccessFile tdat =
                    new RandomAccessFile("test.dat", "rw")
    ) {
        MappedByteBuffer out = tdat.getChannel().map(
                FileChannel.MapMode.READ_WRITE, 0, length);
        for(int i = 0; i < length; i++)
            out.put((byte)'x');
        System.out.println("Finished writing");
        for(int i = length/2; i < length/2 + 6; i++)
            System.out.print((char)out.get(i));
    } catch (Exception e) {
        e.printStackTrace();
    } 
}
  • 因为 Java 文件锁定直接映射到本机操作系统锁定工具tryLock() 是非阻塞的。它试图获取锁,若不能获取它只是从方法调用返回. lock() 会阻塞,直到获得锁,或者调用 lock() 的线程中断
    SocketChannel、DatagramChannel 和 ServerSocketChannel 不需要锁定,因为它们本质上是单进程实体。FileLock.isShared() 查询锁的类型(共享或独占)
public static void fileLock() {
    try(
            FileOutputStream fos =
                    new FileOutputStream("file.txt");
            FileLock fl = fos.getChannel().tryLock();
            //锁部分
            // FileLock fl = fc.lock(start, end, false);
    ) {
        if(fl != null) {
            System.out.println("Locked File");
            TimeUnit.MILLISECONDS.sleep(100);
            fl.release();
            System.out.println("Released Lock");
        }
    } catch(IOException | InterruptedException e) {
        throw new RuntimeException(e);
    }
}

三种IO

对比
  • BIO 同步阻塞方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
  • NIO 同步非阻塞 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
  • AIO 异步非阻塞方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
同步异步概念
  • 同步异步阻塞非阻塞: 同步异步指的是用户进程触发IO操作是否需要等待,阻塞和非阻塞是针对于进程在访问数据是否需要等待。
io/nio经典图
io/nio经典图.jpg
  • NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
  • 在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了。

hashCode() 函数和 equals():

  1. Object的equals 这个函数会比较对象的地址
  2. 命题和逆否命题,在effective java第三章有涉及可以看下。

数据压缩:

  1. 压缩库处理的是字节,而不是字符
  2. DeflaterOutputStream 压缩类的基类
  3. ZipOutputStream GZIPOutputStream DeflaterOutputStream 类的一种,用于压缩数据到 Zip 文件结构
  4. ZipInputStream 解压缩
// 省略try with resource
BufferedOutputStream out =
        new BufferedOutputStream(
                new GZIPOutputStream(
                        new FileOutputStream("test.gz")))
BufferedReader in2 = new BufferedReader(
        new InputStreamReader(new GZIPInputStream(
                new FileInputStream("test.gz"))));

参考文章
  • Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
  • JAVA - IO - IO的类型(AIO, BIO, NIO)

你可能感兴趣的:(IO/NIO/AIO)