小师妹学IO系列文章集合-附PDF下载(一)

简介: 小师妹学IO系列文章集合-附PDF下载(一)

目录



java中最最让人激动的部分就是IO和NIO了。IO的全称是input output,是java程序跟外部世界交流的桥梁,IO指的是java.io包中的所有类,他们是从java1.0开始就存在的。NIO叫做new IO,是在java1.4中引入的新一代IO。


IO的本质是什么呢?它和NIO有什么区别呢?我们该怎么学习IO和NIO呢?


本系列将会借助小师妹的视角,详细讲述学习java IO的过程,希望大家能够喜欢。


小师妹何许人也?姓名不详,但是勤奋爱学,潜力无限,一起来看看吧。


本文的例子https://github.com/ddean2009/learn-java-io-nio


文章太长,大家可以直接下载本文PDF:下载链接java-io-all-in-one.pdf


第一章 IO的本质


IO的本质


IO的作用就是从外部系统读取数据到java程序中,或者把java程序中输出的数据写回到外部系统。这里的外部系统可能是磁盘,网络流等等。


因为对所有的外部数据的处理都是由操作系统内核来实现的,对于java应用程序来说,只是调用操作系统中相应的接口方法,从而和外部数据进行交互。


所有IO的本质就是对Buffer的处理,我们把数据放入Buffer供系统写入外部数据,或者从系统Buffer中读取从外部系统中读取的数据。如下图所示:


image.png

用户空间也就是我们自己的java程序有一个Buffer,系统空间也有一个buffer。所以会出现系统空间缓存数据的情况,这种情况下系统空间将会直接返回Buffer中的数据,提升读取速度。


DMA和虚拟地址空间


在继续讲解之前,我们先讲解两个操作系统中的基本概念,方便后面我们对IO的理解。


现代操作系统都有一个叫做DMA(Direct memory access)的组件。这个组件是做什么的呢?


一般来说对内存的读写都是要交给CPU来完成的,在没有DMA的情况下,如果程序进行IO操作,那么所有的CPU时间都会被占用,CPU没法去响应其他的任务,只能等待IO执行完成。这在现代应用程序中是无法想象的。


如果使用DMA,则CPU可以把IO操作转交给其他的操作系统组件,比如数据管理器来操作,只有当数据管理器操作完毕之后,才会通知CPU该IO操作完成。现代操作系统基本上都实现了DMA。


虚拟地址空间也叫做(Virtual address space),为了不同程序的互相隔离和保证程序中地址的确定性,现代计算机系统引入了虚拟地址空间的概念。简单点讲可以看做是跟实际物理地址的映射,通过使用分段或者分页的技术,将实际的物理地址映射到虚拟地址空间。


image.png


对于上面的IO的基本流程图中,我们可以将系统空间的buffer和用户空间的buffer同时映射到虚拟地址空间的同一个地方。这样就省略了从系统空间拷贝到用户空间的步骤。速度会更快。


同时为了解决虚拟空间比物理内存空间大的问题,现代计算机技术一般都是用了分页技术。


分页技术就是将虚拟空间分为很多个page,只有在需要用到的时候才为该page分配到物理内存的映射,这样物理内存实际上可以看做虚拟空间地址的缓存。


虚拟空间地址分页对IO的影响就在于,IO的操作也是基于page来的。


比较常用的page大小有:1,024, 2,048, 和 4,096 bytes。


IO的分类


IO可以分为File/Block IO和Stream I/O两类。


对于File/Block IO来说,数据是存储在disk中,而disk是由filesystem来进行管理的。我们可以通过filesystem来定义file的名字,路径,文件属性等内容。


filesystem通过把数据划分成为一个个的data blocks来进行管理。有些blocks存储着文件的元数据,有些block存储着真正的数据。


最后filesystem在处理数据的过程中,也进行了分页。filesystem的分页大小可以跟内存分页的大小一致,或者是它的倍数,比如 2,048 或者 8,192 bytes等。


并不是所有的数据都是以block的形式存在的,我们还有一类IO叫做stream IO。


stream IO就像是管道流,里面的数据是序列被消费的。


IO和NIO的区别


java1.0中的IO是流式IO,它只能一个字节一个字节的处理数据,所以IO也叫做Stream IO。


而NIO是为了提升IO的效率而生的,它是以Block的方式来读取数据的。


Stream IO中,input输入一个字节,output就输出一个字节,因为是Stream,所以可以加上过滤器或者过滤器链,可以想想一下web框架中的filter chain。在Stream IO中,数据只能处理一次,你不能在Stream中回退数据。


在Block IO中,数据是以block的形式来被处理的,因此其处理速度要比Stream IO快,同时可以回退处理数据。但是你需要自己处理buffer,所以复杂程度要比Stream IO高。


一般来说Stream IO是阻塞型IO,当线程进行读或者写操作的时候,线程会被阻塞。


而NIO一般来说是非阻塞的,也就是说在进行读或者写的过程中可以去做其他的操作,而读或者写操作执行完毕之后会通知NIO操作的完成。


在IO中,主要分为DataOutPut和DataInput,分别对应IO的out和in。


DataOutPut有三大类,分别是Writer,OutputStream和ObjectOutput。


看下他们中的继承关系:



image.png


DataInput也有三大类,分别是ObjectInput,InputStream和Reader。


看看他们的继承关系:


image.png


image.png

NIO需要掌握的类的个数比IO要稍稍多一点,毕竟NIO要复杂一点。


就这么几十个类,我们就掌握了IO和NIO,想想都觉得兴奋。


总结


后面的文章中,我们会介绍小师妹给你们认识,刚好她也在学java IO,后面的学习就跟她一起进行吧,敬请期待。


第二章 try with和它的底层原理


简介


小师妹是个java初学者,最近正在学习使用java IO,作为大师兄的我自然要给她最给力的支持了。一起来看看她都遇到了什么问题和问题是怎么被解决的吧。


IO关闭的问题


这一天,小师妹一脸郁闷的问我:F师兄,我学Java IO也有好多天了,最近写了一个例子,读取一个文件没有问题,但是读取很多个文件就会告诉我:”Can't open so many files“,能帮我看看是什么问题吗?


更多内容请访问www.flydean.com


小师妹的要求当然不能拒绝,我立马响应:可能打开文件太多了吧,教你两个命令,查看最大文件打开限制。


一个命令是 ulimit -a


image.png


第二个命令是


ulimit -n
256

看起来是你的最大文件限制太小了,只有256个,调大一点就可以了。


小师妹却说:不对呀F师兄,我读文件都是一个一个读的,没有同时开这么多文件哟。


好吧,看下你写的代码吧:


BufferedReader bufferedReader = null;
        try {
            String line;
            bufferedReader = new BufferedReader(new FileReader("trywith/src/main/resources/www.flydean.com"));
            while ((line = bufferedReader.readLine()) != null) {
                log.info(line);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }


看完代码,问题找到了,小师妹,你的IO没有关闭,应该在使用之后,在finally里面把你的reader关闭。


下面这段代码就行了:


BufferedReader bufferedReader = null;
        try {
            String line;
            bufferedReader = new BufferedReader(new FileReader("trywith/src/main/resources/www.flydean.com"));
            while ((line = bufferedReader.readLine()) != null) {
                log.info(line);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                if (bufferedReader != null){
                 bufferedReader.close();
                }
            } catch (IOException ex) {
                log.error(ex.getMessage(), ex);
            }
        }


小师妹道了一声谢,默默的去改代码了。


使用try with resource


过了半个小时 ,小师妹又来找我了,F师兄,现在每段代码都要手动添加finally,实在是太麻烦了,很多时候我又怕忘记关闭IO了,导致程序出现无法预料的异常。你也知道我这人从来就怕麻烦,有没有什么简单的办法,可以解决这个问题呢?


那么小师妹你用的JDK版本是多少?


小师妹不好意思的说:虽然最新的JDK已经到14了,我还是用的JDK8.JDK8就够了,其实从JDK7开始,Java引入了try with resource的新功能,你把使用过后要关闭的resource放到try里面,JVM会帮你自动close的,是不是很方便,来看下面这段代码:


try (BufferedReader br = new BufferedReader(new FileReader("trywith/src/main/resources/www.flydean.com")))
        {
            String sCurrentLine;
            while ((sCurrentLine = br.readLine()) != null)
            {
                log.info(sCurrentLine);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }


try with resource的原理


太棒了,小师妹非常开心,然后又开始问我了:F师兄,什么是resource呀?为什么放到try里面就可以不用自己close了?


resource就是资源,可以打开个关闭,我们可以把实现了java.lang.AutoCloseable接口的类都叫做resource。


先看下AutoCloseable的定义:


public interface AutoCloseable {
        void close() throws Exception;
}


AutoCloseable定义了一个close()方法,当我们在try with resource中打开了AutoCloseable的资源,那么当try block执行结束的时候,JVM会自动调用这个close()方法来关闭资源。


我们看下上面的BufferedReader中close方法是怎么实现的:


public void close() throws IOException {
    synchronized (lock) {
        if (in == null)
            return;
        in.close();
        in = null;
        cb = null;
    }
}


自定义resource


小师妹恍然大悟:F师兄,那么我们是不是可以实现AutoCloseable来创建自己的resource呢?


当然可以了,我们举个例子,比如给你解答完这个问题,我就要去吃饭了,我们定义这样一个resource类:


public class CustResource implements AutoCloseable {
    public void helpSister(){
        log.info("帮助小师妹解决问题!");
    }
    @Override
    public void close() throws Exception {
        log.info("解决完问题,赶紧去吃饭!");
    }
    public static void main(String[] args) throws Exception {
       try( CustResource custResource= new CustResource()){
           custResource.helpSister();
       }
    }
}


运行输出结果:


[main] INFO com.flydean.CustResource - 帮助小师妹解决问题!
[main] INFO com.flydean.CustResource - 解决完问题,赶紧去吃饭!


总结


最后,小师妹的问题解决了,我也可以按时吃饭了。


第三章 File文件系统


简介


小师妹又遇到难题了,这次的问题是有关文件的创建,文件权限和文件系统相关的问题,还好这些问题的答案都在我的脑子里面,一起来看看吧。


文件权限和文件系统


早上刚到公司,小师妹就凑过来神神秘秘的问我:F师兄,我在服务器上面放了一些重要的文件,是非常非常重要的那种,有没有什么办法给它加个保护,还兼顾一点隐私?


更多内容请访问www.flydean.com


什么文件这么重要呀?不会是你的照片吧,放心没人会感兴趣的。


小师妹说:当然不是,我要把我的学习心得放上去,但是F师兄你知道的,我刚刚开始学习,很多想法都不太成熟,想先保个密,后面再公开。


看到小师妹这么有上进心,我老泪纵横,心里很是安慰。那就开始吧。


你知道,这个世界上操作系统分为两类,windows和linux(unix)系统。两个系统是有很大区别的,但两个系统都有一个文件的概念,当然linux中文件的范围更加广泛,几乎所有的资源都可以看做是文件。


有文件就有对应的文件系统,这些文件系统是由系统内核支持的,并不需要我们在java程序中重复造轮子,直接调用系统的内核接口就可以了。


小师妹:F师兄,这个我懂,我们不重复造轮子,我们只是轮子的搬运工。那么java是怎么调用系统内核来创建文件的呢?


创建文件最常用的方法就是调用File类中的createNewFile方法,我们看下这个方法的实现:


public boolean createNewFile() throws IOException {
        SecurityManager security = System.getSecurityManager();
        if (security != null) security.checkWrite(path);
        if (isInvalid()) {
            throw new IOException("Invalid file path");
        }
        return fs.createFileExclusively(path);
    }


方法内部先进行了安全性检测,如果通过了安全性检测就会调用FileSystem的createFileExclusively方法来创建文件。


在我的mac环境中,FileSystem的实现类是UnixFileSystem:


public native boolean createFileExclusively(String path)
        throws IOException;


看到了吗?UnixFileSystem中的createFileExclusively是一个native方法,它会去调用底层的系统接口。


小师妹:哇,文件创建好了,我们就可以给文件赋权限了,但是windows和linux的权限是一样的吗?


这个问题问得好,java代码是跨平台的,我们的代码需要同时在windows和linux上的JVM执行,所以必须找到他们权限的共同点。


我们先看一下windows文件的权限:


image.png


可以看到一个windows文件的权限可以有修改,读取和执行三种,特殊权限我们先不用考虑,因为我们需要找到windows和linux的共同点。


再看下linux文件的权限:


ls -al www.flydean.com 
-rw-r--r--  1 flydean  staff  15 May 14 15:43 www.flydean.com


上面我使用了一个ll命令列出了www.flydean.com这个文件的详细信息。 其中第一列就是文件的权限了。


linux的基本文件权限可以分为三部分,分别是owner,group,others,每部分和windows一样都有读,写和执行的权限,分别用rwx来表示。


三部分的权限连起来就成了rwxrwxrwx,对比上面我们的输出结果,我们可以看到www.flydean.com这个文件对owner自己是可读写的,对Group用户是只读的,对other用户也是只读的。


你要想把文件只对自己可读,那么可以执行下面的命令:


chmod 600 www.flydean.com


小师妹立马激动起来:F师兄,这个我懂,6用二进制表示就是110,600用二进制表示就是110000000,刚刚好对应rw-------。


对于小师妹的领悟能力,我感到非常满意。


文件的创建


虽然我们已经不是孔乙己时代了,不需要知道茴字的四种写法,但是多一条知识多一条路,做些充足的准备还是非常有必要的。


小师妹,那你知道在java中有哪几种文件的创建方法呢?


小师妹小声道:F师兄,我只知道一种new File的方法。


我满意的抚摸着我的胡子,显示一下自己高人的气场。


之前我们讲过了,IO有三大类,一种是Reader/Writer,一种是InputStream/OutputStream,最后一种是ObjectReader/ObjectWriter。


除了使用第一种new File之外,我们还可以使用OutputStream来实现,当然我们还要用到之前讲到try with resource特性,让代码更加简洁。


先看第一种方式:


public void createFileWithFile() throws IOException {
        File file = new File("file/src/main/resources/www.flydean.com");
        //Create the file
        if (file.createNewFile()){
            log.info("恭喜,文件创建成功");
        }else{
            log.info("不好意思,文件创建失败");
        }
        //Write Content
        try(FileWriter writer = new FileWriter(file)){
            writer.write("www.flydean.com");
        }
    }


再看第二种方式:


public  void createFileWithStream() throws IOException
    {
        String data = "www.flydean.com";
        try(FileOutputStream out = new FileOutputStream("file/src/main/resources/www.flydean.com")){
            out.write(data.getBytes());
        }
    }


第二种方式看起来比第一种方式更加简介。


小师妹:慢着,F师兄,JDK7中NIO就已经出现了,能不能使用NIO来创建文件呢?


这个问题当然难不到我:


public void createFileWithNIO()  throws IOException
    {
        String data = "www.flydean.com";
        Files.write(Paths.get("file/src/main/resources/www.flydean.com"), data.getBytes());
        List<String> lines = Arrays.asList("程序那些事", "www.flydean.com");
        Files.write(Paths.get("file/src/main/resources/www.flydean.com"),
                lines,
                StandardCharsets.UTF_8,
                StandardOpenOption.CREATE,
                StandardOpenOption.APPEND);
    }


NIO中提供了Files工具类来实现对文件的写操作,写的时候我们还可以带点参数,比如字符编码,是替换文件还是在append到文件后面等等。


代码中文件的权限


小师妹又有问题了:F师兄,讲了半天,还没有给我讲权限的事情啦。


别急,现在就讲权限:


public void fileWithPromission() throws IOException {
        File file = File.createTempFile("file/src/main/resources/www.flydean.com","");
        log.info("{}",file.exists());
        file.setExecutable(true);
        file.setReadable(true,true);
        file.setWritable(true);
        log.info("{}",file.canExecute());
        log.info("{}",file.canRead());
        log.info("{}",file.canWrite());
        Path path = Files.createTempFile("file/src/main/resources/www.flydean.com", "");
        log.info("{}",Files.exists(path));
        log.info("{}",Files.isReadable(path));
        log.info("{}",Files.isWritable(path));
        log.info("{}",Files.isExecutable(path));
    }


上面我们讲过了,JVM为了通用,只能取windows和linux都有的功能,那就是说权限只有读写和执行权限,因为windows里面也可以区分本用户或者其他用户,所以是否是本用户的权限也保留了。


上面的例子我们使用了传统的File和NIO中的Files来更新文件的权限。


总结


好了,文件的权限就先讲到这里了。



相关文章
|
1月前
|
存储 缓存 Python
如何使用Python抓取PDF文件并自动下载到本地
如何使用Python抓取PDF文件并自动下载到本地
34 0
|
1月前
|
前端开发
前端实现生成pdf文件并下载
前端实现生成pdf文件并下载
38 1
|
2月前
|
存储 Rust 安全
Rust标准库概览:集合、IO、时间与更多
本文将带领读者深入了解Rust标准库中的一些核心模块,包括集合类型、输入/输出处理、时间日期功能等。我们将通过实例和解释,探讨这些模块如何使Rust成为高效且安全的系统编程语言。
|
3月前
|
存储
如何解决网页中的pdf文件无法下载?pdf打印显示空白怎么办?
如何解决网页中的pdf文件无法下载?pdf打印显示空白怎么办?
180 0
|
3月前
|
移动开发 JavaScript
echarts生成图表并下载为PDF文件(附带js源码地址)
echarts生成图表并下载为PDF文件(附带js源码地址)
40 0
|
4月前
宜搭系统发布之后,怎样能使数据下载PDF格式
宜搭系统发布之后,怎样能使数据下载PDF格式
38 1
|
5月前
|
人工智能 Cloud Native 大数据
100+PDF开放下载!云栖大会一手资料来啦!(持续更新中)
我们为大家整理了本次云栖大会主分论坛共100多个PDF,欢迎下载学习!
23301 73
|
5月前
|
JavaScript 前端开发 API
vue 实现pdf预览和下载
vue 实现pdf预览和下载
401 0
|
6月前
|
存储 缓存 Java
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
167 0
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
|
6月前
|
对象存储
使用流式下载从阿里OSS获取PDF文件时,确保正确处理输入流的读取。
使用流式下载从阿里OSS获取PDF文件时,确保正确处理输入流的读取。
90 1