目录
- 第一章 IO的本质
- IO的本质
- DMA和虚拟地址空间
- IO的分类
- IO和NIO的区别
- 总结
- 第二章 try with和它的底层原理
- 简介
- IO关闭的问题
- 使用try with resource
- try with resource的原理
- 自定义resource
- 总结
- 第三章 File文件系统
- 简介
- 文件权限和文件系统
- 文件的创建
- 代码中文件的权限
- 总结
- 第四章 文件读取那些事
- 简介
- 字符和字节
- 按字符读取的方式
- 按字节读取的方式
- 寻找出错的行数
- 总结
- 第五章 文件写入那些事
- 简介
- 字符输出和字节输出
- 格式化输出
- 输出其他对象
- 在特定的位置写入
- 给文件加锁
- 总结
- 第六章 目录还是文件
- 简介
- linux中的文件和目录
- 目录的基本操作
- 目录的进阶操作
- 目录的腰疼操作
- 总结
- 第七章 文件系统和WatchService
- 简介
- 监控的痛点
- WatchService和文件系统
- WatchSerice的使用和实现本质
- 总结
- 第八章 文件File和路径Path
- 简介
- 文件和路径
- 文件中的不同路径
- 构建不同的Path
- 总结
- 第九章 Buffer和Buff
- 简介
- Buffer是什么
- Buffer进阶
- 创建Buffer
- Direct VS non-Direct
- Buffer的日常操作
- 向Buffer写数据
- 从Buffer读数据
- rewind Buffer
- Compact Buffer
- duplicate Buffer
- 总结
- 第十章 File copy和File filter
- 简介
- 使用java拷贝文件
- 使用File filter
- 总结
- 第十一章 NIO中Channel的妙用
- 简介
- Channel的分类
- FileChannel
- Selector和Channel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
- AsynchronousSocketChannel
- 使用Channel
- 总结
- 第十二章 MappedByteBuffer多大的文件我都装得下
- 简介
- 虚拟地址空间
- 详解MappedByteBuffer
- MapMode
- MappedByteBuffer的最大值
- MappedByteBuffer的使用
- MappedByteBuffer要注意的事项
- 总结
- 第十三章 NIO中那些奇怪的Buffer
- 简介
- Buffer的分类
- Big Endian 和 Little Endian
- aligned内存对齐
- 总结
- 第十四章 用Selector来说再见
- 简介
- Selector介绍
- 创建Selector
- 注册Selector到Channel中
- SelectionKey
- selector 和 SelectionKey
- 总的例子
- 总结
- 第十五章 文件编码和字符集Unicode
- 简介
- 使用Properties读取文件
- 乱码初现
- 字符集和文件编码
- 解决Properties中的乱码
- 真.终极解决办法
- 总结
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中读取从外部系统中读取的数据。如下图所示:
用户空间也就是我们自己的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),为了不同程序的互相隔离和保证程序中地址的确定性,现代计算机系统引入了虚拟地址空间的概念。简单点讲可以看做是跟实际物理地址的映射,通过使用分段或者分页的技术,将实际的物理地址映射到虚拟地址空间。
对于上面的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。
看下他们中的继承关系:
DataInput也有三大类,分别是ObjectInput,InputStream和Reader。
看看他们的继承关系:
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
第二个命令是
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文件的权限:
可以看到一个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来更新文件的权限。
总结
好了,文件的权限就先讲到这里了。