Java基础梳理之-IO操做

回想最开始学习Java IO相关的操做时, 被各类Reader/Stream绕晕。 如今再回头梳理这一块的知识点,感受清晰了不少。 Java做为编程语言,
大部分东西都是从系统层面带来的, 因此学习的知识点虽然在Java, 可是背后的答案却在操做系统层面。编程

首先理解核心概念:IO, 究竟什么是IO? 所谓IO就是内存与外设相关的数据传输。经常使用的外设有硬盘,网卡,打印机, 鼠标...
咱们接触最频繁的IO操做是硬盘上文件的读写,因此学习IO基本上都是以文件操做为例子。IO做为操做系统的核心,知识点至关庞杂,若是没有合适的切入点,容易迷失其中。 vim

若是必定要找一个切入点,学习的前后顺序,我的建议以下:windows

  1. RandomAccessFile

对于文件的操做,最适合是RandomAccessFile: 它能读能写能定位。 使用RandomAccessFile, 就是把文件看成一个数组,只不过这个数组是在硬盘上而已。读写文件就像操做数组同样。更难能难得的是, RandomAccessFile封装了Java全部的基础类型, 能够说基本知足操做单个文件的使用需求了。数组

public static void main(String[] args) throws IOException {

        RandomAccessFile bigArray = new RandomAccessFile(new File("/home/shgy/a.txt"),"rw");
        // 写
        bigArray.seek(10);
        bigArray.writeUTF("hello,world");

        System.out.println("filePointer at " + bigArray.getFilePointer());
        // 读
        bigArray.seek(10);
        System.out.println(bigArray.readUTF());
        bigArray.close();
    }

RandomAccessFile类有14个写的方法,17个读的方法,2个定位方法,2个长度相关操做,1个获取当前文件游标getFilePointer()的方法, 1个关闭文件释放系统资源的方法。 剩下的两个方法getChannel()getFD()getChannel() 跟NIO相关,getFD()是系统文件描述符,就本文所要总结的内容而言,已经超出三界以外,不在五行之中,暂时略过不提。安全

当文件数量多了之后,必然面临管理的问题。不管是windows仍是Linux都采用层级管理,最后造成目录树。这带来一个新的问题就是文件的路径以及文件的归类。 面对这样的需求,Java提供了Files类来解决。经过了解Files类提供的API, 能够看出,其功能特色在于粗粒度的文件读写及文件属性的管理。
使用Files来读写文件更简单:网络

public static void main(String[] args) throws IOException {

        Files.write("hello world", new File("/home/shgy/a.txt"),Charset.defaultCharset());

        System.out.println(Files.readLines(new File("/home/shgy/a.txt"), Charset.defaultCharset()));
    }

Files 能够读写文件,能够重命名文件,能够读取设置文件属性,简直是瑞士×××般的存在。这里涉及了更多文件相关的知识点,若是有学习过《鸟哥的Linux私房菜》第七章,再学习代码操做文件,就不会那么困惑了。 dom

使用Files操纵文件引出了一个新的知识点Charset, 即字符集。字符集产生的缘由很简单: 人类语言是字符形式,计算机只能以字节的方式存储数据,字符跟字节之间得有个映射关系。好比上例中存储的hello world, 实际上存储的内容可使用vim的xdd命令查看:异步

// vim  + %!xdd 命令便可

00000000: 6865 6c6c 6f20 776f 726c 640a            hello world.

关于字符集的知识,能够参考阮一峰的《字符编码笔记:ASCII,Unicode 和 UTF-8》。编程语言

理解了字符集,再进入Java的IO模块,才瓜熟蒂落。前面已经说过,所谓IO,就是内存与计算机外设的数据传输。Java从语言层面对IO进行了抽象, 这个抽象就是Steam, 数据流。这样的话,不管数据来源是文件,网页,内存块仍是其余,都以一种统一的视角和处理方式看待。 因此Java定义了InputStream和OutputStream。
InputStream用于将数据读入内存, 对应的操做是read; OutputStream用于将数据写入外设,对应的操做是write。InputStream和OutputStream操纵的数据只能是字节或者字节数组, 这样就不用关心数据是文本,图片,音频,视频了,毕竟无论什么类型的数据,最终的呈现形式就是字节流。
这样,文件的操做就至关繁琐了:ide

public static void main(String[] args) throws IOException {

        // 读取文件
        FileInputStream fis = new FileInputStream(new File("/home/shgy/a.txt"));
        byte[] bytes = new byte[1024];
        int n = fis.read(bytes);
        if(n>0){
            System.out.println(new String(bytes,0,n));
        }
        fis.close();

        // 写文件
        FileOutputStream fos = new FileOutputStream(new File("/home/shgy/a.txt"));
        fos.write("hello, world".getBytes());
        fos.close();
}

鉴于咱们处理的文件,绝大部分都是字符类型的文件,并且以字节的方式操纵字符确实过于原始,因而Java也定义了字符IO, 即Reader/Writer。

public static void main(String[] args) throws IOException {

        // 读取文件
        FileReader fr = new FileReader(new File("/home/shgy/a.txt"));
        char[] buf = new char[1024];
        int n = fr.read(buf);
        System.out.println(new String(buf,0,n));
        fr.close();

        FileWriter fw = new FileWriter(new File("/home/shgy/a.txt"));
        fw.write("hello, world");
        fw.close();
    }

因为计算机本质是处理字节,因此字符和字节之间须要一个桥梁,这个就是InputStreamReader/OutputStreamWriter. 为了应对各类字符集和字节之间的编码解码,因此定义了StreamEncoder/StreamDecoder。

对于文件的读写,因为是须要操做硬盘或者网卡;考虑到安全性, 在系统层面须要系统调用,由用户态切入内核态。这个操做代价较高, 因此又添加了一层缓冲,将原来须要屡次请求的IO合并到一块儿,即BufferedInputStream/BufferedOutputStream 和 BufferedReader/BufferedWriter。

整个IO操做在InputStream/OutputStream和Reader/Writer基础之上丰富多彩起来。

因为外设,好比硬盘和网络数据的传输效率相比CPU的处理效率相差太远, 在《性能之颠》中有这样一个让人影响深入的对比:
1个CPU周期为0.3ns, 1次机械磁盘IO周期为1~10ms, 1次从旧金山到纽约的互联网传输须要40ms; 因为时间单位过小,咱们没有概念。 咱们放大一下,假如:
1个CPU周期为1s, 则一次机械磁盘IO周期为1~12个月,1次从旧金山到纽约的互联网传输须要4年。 在这样一个差距面前,如何提升IO的效率,就显得尤其重要,
这就是NIO的由来。

在《UNIX网络编程卷1:套接字联网API》一书中总结了5种IO模型: 阻塞,非阻塞,IO复用,信号驱动,异步IO。Java的NIO是采用了IO复用(select)模型。

NIO处理数据,方式跟Stream有所不一样。 Stream比较碎,以字节为最小粒度; NIO以数据块为最小粒度。因此能够避免数据的反复搬运,更高效,操做起来就更繁琐一些。

// 使用NIO写数据到文件
    public static void main(String[] args) throws IOException {

        FileOutputStream fos = new FileOutputStream(new File("/home/shgy/a.txt"));

        FileChannel fc = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocate(1024);
        buf.put("hello,world".getBytes());
        buf.flip();
        fc.write(buf);
        System.out.println("file channel position is " + fc.position());
        fos.close();

    }

NIO有以下的几个优势:

  1. channel是支持读写的,因此相比Stream更灵活。
  2. buffer能够分配堆外内存,这个对于IO来讲,避免了数据从堆内存中倒腾一边,也避免了Java的GC, 性能天然有提高。
  3. 对于网络IO, NIO能够在同一个线程同时监听多个端口,避免了建立多个线程和线程管理的开销。

因为IO这一块的知识点过于庞杂,不是一篇博客能说清楚的,这里只是简单梳理一下学习思路。

相关文章
相关标签/搜索