为什么最终是4比64的n次方等于多少呢?

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

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

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

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

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

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

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

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

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

小师妹不好意思的说:虽然最新的JDK已经到14了,我还是用的")))

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

resource就是资源,可以打开个关闭,我们可以把实现了

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

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

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

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

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

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

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

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

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

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

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

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

再看下linux文件的权限:

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

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

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

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

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

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

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

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

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

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

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

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

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

这个问题当然难不到我:

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

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

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

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

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

小师妹最新对java IO中的reader和stream产生了一点点困惑,不知道到底该用哪一个才对,怎么读取文件才是正确的姿势呢?今天F师兄现场为她解答。

小师妹最近很迷糊:F师兄,上次你讲到IO的读取分为两大类,分别是Reader,InputStream,这两大类有什么区别吗?为什么我看到有些类即是Reader又是Stream?比如:InputStreamReader?

小师妹,你知道哲学家的终极三问吗?你是谁?从哪里来?到哪里去?

F师兄,你是不是迷糊了,我在问你java,你扯什么哲学。

小师妹,其实吧,哲学是一切学问的基础,你知道科学原理的英文怎么翻译吗?the philosophy of science,科学的原理就是哲学。

你看计算机中代码的本质是什么?代码的本质就是0和1组成的一串长长的二进制数,这么多二进制数组合起来就成了计算机中的代码,也就是JVM可以识别可以运行的二进制代码。

小师妹一脸崇拜:F师兄说的好像很有道理,但是这和Reader,InputStream有什么关系呢?

别急,冥冥中自有定数,先问你一个问题,java中存储的最小单位是什么?

小师妹:容我想想,java中最小的应该是boolean,true和false正好和二进制1,0对应。

对了一半,虽然boolean也是java中存储的最小单位,但是它需要占用一个字节Byte的空间。java中最小的存储单位其实是字节Byte。不信的话可以用之前我介绍的JOL工具来验证一下:

byte翻译成中文就是字节,字节是java中存储的基本单位。

有了字节,我们就可以解释字符了,字符就是由字节组成的,根据编码方式的不同,字符可以有1个,2个或者多个字节组成。我们人类可以肉眼识别的汉字呀,英文什么的都可以看做是字符。

而Reader就是按照一定编码格式读取的字符,而InputStream就是直接读取的更加底层的字节。

小师妹:我懂了,如果是文本文件我们就可以用Reader,非文本文件我们就可以用InputStream。

孺子可教,小师妹进步的很快。

小师妹,接下来F师兄给你讲下按字符读取文件的几种方式,第一种就是使用FileReader来读取File,但是FileReader本身并没有提供任何读取数据的方法,想要真正的读取数据,我们还是要用到BufferedReader来连接FileReader,BufferedReader提供了读取的缓存,可以一次读取一行:

每次读取一行,可以把这些行连起来就组成了stream,通过");

第三种其实并不常用,但是师兄也想教给你。这一种方式就是用工具类中的Scanner。通过Scanner可以通过换行符来分割文件,用起来也不错:

小师妹听得很满足,连忙催促我:F师兄,字符读取方式我都懂了,快将字节读取吧。

我点了点头,小师妹,哲学的本质还记得吗?字节就是java存储的本质。掌握到本质才能勘破一切虚伪。

还记得之前讲过的Files工具类吗?这个工具类提供了很多文件操作相关的方法,其中就有读取所有bytes的方法,小师妹要注意了,这里是一次性读取所有的字节!一定要慎用,只可用于文件较少的场景,切记切记。

如果是比较大的文件,那么可以使用FileInputStream来一次读取一定数量的bytes:

Stream读取都是一个字节一个字节来读的,这样做会比较慢,我们使用NIO中的FileChannel和ByteBuffer来加快一些读取速度:

小师妹:如果是非常非常大的文件的读取,有没有更快的方法呢?

当然有,记得上次我们讲过的虚拟地址空间的映射吧:

我们可以直接将用户的地址空间和系统的地址空间同时map到同一个虚拟地址内存中,这样就免除了拷贝带来的性能开销:

小师妹:好赞!F师兄你讲得真好,小师妹我还有一个问题:最近在做文件解析,有些文件格式不规范,解析到一半就解析失败了,但是也没有个错误提示到底错在哪一行,很难定位问题呀,有没有什么好的解决办法?

看看天色已经不早了,师兄就再教你一个方法,java中有一个类叫做LineNumberReader,使用它来读取文件可以打印出行号,是不是就满足了你的需求:

今天给小师妹讲解了字符流和字节流,还讲解了文件读取的基本方法,不虚此行。

小师妹又对F师兄提了一大堆奇奇怪怪的需求,要格式化输出,要特定的编码输出,要自己定位输出,什么?还要阅后即焚?大家看F师兄怎么一一接招吧。

小师妹:F师兄,上次你的IO讲到了一半,文件读取是基本上讲完了,但是文件的写入还没有讲,什么时候给小师妹我再科普科普?

小师妹:F师兄,你知道我这个人一直以来都是勤奋好学的典范,是老师们眼中的好学生,同学们心中的好榜样,父母身边乖巧的好孩子。在我永攀科学高峰的时候,居然发现还有一半的知识没有获取,真是让我扼腕叹息,F师兄,快快把知识传给我吧。

小师妹你的请求,师兄我自当尽力办到,但是我怎么记得上次讲IO文件读取已经过了好几天了,怎么今天你才来找我。

小师妹红着脸:F师兄,这不是使用的时候遇到了点问题,才想找你把知识再复习一遍。

那先把输出类的结构再过一遍:

第一个方法传入一个int,第二个方法传入字符数组和开始读取的位置和长度,第三个方法传入字符串和开始读取的位置和长度。是不是很简单,完全可以理解?

小师妹:不对呀,F师兄,后面两个方法的参数,不管是char和String都是字符我可以理解,第一个方法传入int是什么鬼?

小师妹,之前跟你讲的道理是不是都忘记的差不多了,int的底层存储是bytes,char和String的底层存储也是bytes,我们把int和char做个强制转换就行了。我们看下是怎么转换的:

还记得int需要占用多少个字节吗?4个,char需要占用2个字节。这样强制从int转换到char会有精度丢失的问题,只会保留低位的2个字节的数据,高位的两个字节的数据会被丢弃,这个需要在使用中注意。

第一个write方法传入int参数也是需要进行截取的,不过这次是从int转换成byte。

小师妹:F师兄,我们经常用的");

小师妹:F师兄,我们看到可以输出String,char还有Byte,那可不可以输出Integer,Long等基础类型呢?

小师妹:F师兄,有时候我们不需要每次都从头开始写入到文件,能不能自定义在什么位置写入呢?

小师妹:F师兄,最后还有一个问题,怎么保证我在进行文件写的时候别人不会覆盖我写的内容,不会产生冲突呢?

FileChannel可以调用tryLock方法来获得一个FileLock锁,通过这个锁,我们可以控制文件的访问。

今天给小师妹将了好多种文件的写的方法,够她学习一阵子了。

目录和文件傻傻分不清楚,目录和文件的本质到底是什么?在java中怎么操纵目录,怎么遍历目录。本文F师兄会为大家一一讲述。

linux中的文件和目录

小师妹:F师兄,我最近有一个疑惑,java代码中好像只有文件没有目录呀,是不是当初发明java的大神,一步小心走了神?

F师兄:小师妹真勇气可嘉呀,敢于质疑权威是从小工到专家的最重要的一步。想想F师兄我,从小没人提点,老师讲什么我就信什么,专家说什么我就听什么:股市必上一万点,房子是给人住的不是给人炒的,原油宝当然是小白理财必备产品…然后,就没有然后了。

虽然java中没有目录的概念只有File文件,而File其实是可以表示目录的:

File和目录傻傻分不清楚,小师妹,有没有联想到点什么?

小师妹:F师兄,我记得你上次讲到Linux下面所有的资源都可以看做是文件,在linux下面文件和目录的本质是不是一样的?

对的,在linux下面文件是一等公民,所有的资源都是以文件的形式来区分的。

什么扇区,逻辑块,页之类的底层结构我们就不讲了。我们先考虑一下一个文件到底应该包含哪些内容。除了文件本身的数据之外,还有很多元数据的东西,比如文件权限,所有者,group,创建时间等信息。

在linux系统中,这两个部分是分开存储的。存放数据本身的叫做block,存放元数据的叫做inode。

inode中存储了block的地址,可以通过inode找到文件实际数据存储的block地址,从而进行文件访问。考虑一下大文件可能占用很多个block,所以一个inode中可以存储多个block的地址,而一个文件通常来说使用一个inode就够了。

为了显示层级关系和方便文件的管理,目录的数据文件中存放的是该目录下的文件和文件的inode地址,从而形成了一种一环套一环,圆环套圆环的链式关系。

上图列出了一个通过目录查找其下文件的环中环布局。

我想java中目录没有单独列出来一个类的原因可能是参考了linux底层的文件布局吧。

因为在java中目录和文件是公用File这个类的,所以File的基本操作目录它全都会。

基本上,目录和文件相比要多注意下面三类方法:

为什么说是三类呢?因为还有几个和他们比较接近的方法,这里就不一一列举了。

isDirectory判断该文件是不是目录。listFiles列出该目录下面的所有文件。mkdir创建一个文件目录。

小师妹:F师兄,之前我们还以目录的遍历要耗费比较长的时间,经过你一讲解目录的数据结构,感觉listFiles并不是一个耗时操作呀,所有的数据都已经准备好了,直接读取出来就行。

对,看问题不要看表面,要看到隐藏在表面的本质内涵。你看师兄我平时不显山露水,其实是真正的中流砥柱,堪称公司优秀员工模范。

小师妹:F师兄,那平时也没看上头表彰你啥的?哦,我懂了,一定是老板怕表彰了你引起别人的嫉妒,会让你的好好大师兄的形象崩塌吧,看来老板真的懂你呀。

好了小师妹,你懂了就行,下面F师兄给你讲一下目录的进阶操作,比如我们怎么拷贝一个目录呀?

小师妹,拷贝目录简单的F师兄,上次你就教我了:

一个命令的事情不就解决了吗?难道里面还隐藏了点秘密?

咳咳咳,秘密倒是没有,小师妹,我记得你上次说要对java从一而终的,今天师兄给你介绍一个在java中拷贝文件目录的方法。

其实Files工具类里已经为我们提供了一个拷贝文件的优秀方法:

使用这个方法,我们就可以进行文件的拷贝了。

如果想要拷贝目录,就遍历目录中的文件,循环调用这个copy方法就够了。

小师妹:且慢,F师兄,如果目录下面还有目录的,目录下还套目录的情况该怎么处理?

这就是圈套呀,看我用个递归的方法解决它:

基本思想就是遇到目录我就遍历,遇到文件我就拷贝。

小师妹:F师兄,假如我想删除一个目录中的文件,或者我们想统计一下这个目录下面到底有多少个文件该怎么做呢?

虽然这些操作有点腰疼,还是可以解决的,Files工具类中有个方法叫做walk,返回一个Stream对象,我们可以使用Stream的API来对文件进行处理。

本文介绍了目录的一些非常常见和有用的操作。

小师妹这次遇到了监控文件变化的问题,F师兄给小师妹介绍了JDK7 nio中引入的WatchService,没想到又顺道普及了一下文件系统的概念,万万没想到。

小师妹:F师兄最近你有没有感觉到呼吸有点困难,后领有点凉飕飕的,说话有点不顺畅的那种?

没有啊小师妹,你是不是秋衣穿反了?

小师妹:不是的F师兄,我讲的是心里的感觉,那种莫须有的压力,还有一丝悸动缠绕在心。

别绕弯子了小师妹,是不是又遇到问题了。

小师妹:还是F师兄懂我,这不上次的Properties文件用得非常上手,每次修改Properties文件都要重启java应用程序,真的是很痛苦。有没有什么其他的办法呢?

办法当然有,最基础的办法就是开一个线程定时去监控属性文件的最后修改时间,如果修改了就重新加载,这样不就行了。

小师妹:写线程啊,这么麻烦,有没有什么更简单的办法呢?

就知道你要这样问,还好我准备的比较充分,今天给你介绍一个JDK7在nio中引入的类WatchService。

  1. 带时间参数的poll 在等待的一定时间内获取下一个watchKey
  2. take 获取下一个watchKey,如果没有则一直等待

小师妹:F师兄,那怎么才能构建一个WatchService呢?

上次文章中说的文件系统,小师妹还记得吧,FileSystem中就有一个获取WatchService的方法:

哎呀,这就又要扯远了,为什么每次问问题都要扯到天边…

从前当JDK还是9的时候,做了一个非常大的改动叫做模块化JPMS(Java Platform Module System),这个Jrt就是为了给模块化系统用的,我们来举个例子:

万物皆有因,没有无缘无故的爱,也没有无缘无故的恨。一切真的是妙不可言啊。

我们来看下File和path的定义:

首先,File是一个类,它表示的是所有的文件系统都拥有的属性和功能,不管你是windows还是linux,他们中的File对象都应该是一样的。

小师妹:F师兄,这个怎么这么拗口,给我来一个直白通俗的解释吧。

既然这样,且听我解释:爱国版的,或许我们属于不同的民族,但是我们都是中国人。通俗版的,大家都是文化人儿,为啥就你这么拽。文化版的,同九年,汝何秀?

再看两者的实现接口,File实现了Serializable表示可以被序列化,实现了Comparable,表示可以被排序。

Path继承Comparable,表示可以被排序。继承Iterable表示可以被遍历,可以被遍历是因为Path可以表示目录。继承Watchable,表示可以被注册到WatchService中,进行监控。

小师妹:F师兄,File中有好几个关于Path的get方法,能讲一下他们的不同之处吗?

getPath返回的结果就是new File的时候传入的路径,输入什么返回什么。

getAbsolutePath返回的是绝对路径,就是在getPath前面加上了当前的路径。

小师妹:F师兄,我记得路径有相对路径,绝对路径等,是不是也有相应的创建Path的方法呢?

当然有的,先看下绝对路径的创建:

我们还可以从URI中构建Path:

还记得java对象的底层存储单位是什么吗?

小师妹:这个我知道,java对象的底层存储单位是字节Byte。

对,我们看下Buffer的继承图:

Buffer是一个接口,它下面有诸多实现,包括最基本的ByteBuffer和其他的基本类型封装的其他Buffer。

小师妹:F师兄,有ByteBuffer不就够了吗?还要其他的类型Buffer做什么?

小师妹,山珍再好,也有吃腻的时候,偶尔也要换个萝卜白菜啥的,你以为乾隆下江南都干了些啥?

ByteBuffer虽然好用,但是它毕竟是最小的单位,在它之上我们还有Char,int,Double,Short等等基础类型,为了简单起见,我们也给他们都搞一套Buffer。

小师妹:F师兄,既然Buffer是这些基础类型的集合,为什么不直接用结合来表示呢?给他们封装成一个对象,好像有点多余。

我们既然在面向对象的世界,从表面来看自然是使用Object比较合乎情理,从底层的本质上看,这些封装的Buffer包含了一些额外的元数据信息,并且还提供了一些意想不到的功能。

  • Capacity表示的是该Buffer能够承载元素的最大数目,这个是在Buffer创建初期就设置的,不可以被改变。
  • Limit表示的Buffer中可以被访问的元素个数,也就是说Buffer中存活的元素个数。
  • Position表示的是下一个可以被访问元素的index,可以通过put和get方法进行自动更新。

小师妹:F师兄呀,这么多Buffer创建起来是不是很麻烦?有没有什么快捷的使用办法?

一般来说创建Buffer有两种方法,一种叫做allocate,一种叫做wrap。

allocate可以为Buffer分配一个空间,wrap同样为Buffer分配一个空间,不同的是这个空间背后的数组是自定义的,wrap还支持三个参数的方法,后面两个参数分别是offset和length。

hasArray用来判断该Buffer的底层是不是数组实现的,可以看到,不管是wrap还是allocate,其底层都是数组。

需要注意的一点,最后,我们调用了clear方法,clear方法调用之后,我们发现Buffer的position和limit都被重置了。这说明wrap的三个参数方法设定的只是初始值,可以被重置。

小师妹:F师兄,你说了两种创建Buffer的方法,但是两种Buffer的后台都是数组,难道还有非数组的Buffer吗?

小师妹:Direct和非Direct有什么区别呢?

Direct Buffer就是说,不需要在用户空间再复制拷贝一份数据,直接在虚拟地址映射空间中进行操作。这叫Direct。这样做的好处就是快。缺点就是在分配和销毁的时候会占用更多的资源,并且因为Direct Buffer不在用户空间之内,所以也不受垃圾回收机制的管辖。

所以通常来说只有在数据量比较大,生命周期比较长的数据来使用Direct Buffer。

小师妹:F师兄,看起来Buffer确实有那么一点复杂,那么Buffer都有哪些操作呢?

Buffer的操作有很多,下面我们一一来讲解。

同时,我们还可以指定put在什么位置。上面的代码输出:

读数据使用get方法,但是在get方法之前我们需要调用flip方法。

flip方法是做什么用的呢?上面讲到Buffer有个position和limit字段,position会随着get或者put的方法自动指向后面一个元素,而limit表示的是该Buffer中有多少可用元素。

可以通过hasRemaining来判断是否还有下一个元素。通过调用clear来清除Buffer,以供下次使用。

今天给小师妹介绍了Buffer的原理和基本操作。

一个linux命令的事情,小师妹非要让我教她怎么用java来实现,哎,摊上个这么杠精的小师妹,我也是深感无力,做一个师兄真的好难。

今天小师妹找到我了:F师兄,能告诉怎么拷贝文件吗?

拷贝文件?不是很简单的事情吗?如果你有了文件的读权限,只需要这样就可以了。

当然,如果是目录的话还可以加两个参数遍历和强制拷贝:

这么简单的linux命令,不要告诉我你不会。

小师妹笑了:F师兄,我不要用linux命令,我就想用java来实现,我不正在学java吗?学一门当然要找准机会来练习啦,快快教教我吧。

既然这样,那我就开讲了。java中文件的拷贝其实也有三种方法,可以使用传统的文件读写的方法,也可以使用最新的NIO中提供的拷贝方法。

使用传统方法当然没有NIO快,也没有NIO简洁,我们先来看看怎么使用传统的文件读写的方法来拷贝文件:

上面的例子中,我们首先定义了两个文件,然后从两个文件中生成了OutputStream和InputStream,最后以字节流的形式从input中读出数据到outputStream中,最终完成了文件的拷贝。

传统的File IO拷贝比较繁琐,速度也比较慢。我们接下来看看怎么使用NIO来完成这个过程:

之前我们讲到NIO中一个非常重要的概念就是channel,通过构建源文件和目标文件的channel通道,可以直接在channel层面进行拷贝,如上面的例子所示,我们调用了");

直接使用工具类Files提供的copy方法即可。

太棒了,小师妹一脸崇拜:F师兄,我还有一个需求,就是想删除某个目录里面的以.log结尾的日志文件,这个需求是不是很常见?F师兄一般是怎么操作的?

一般这种操作我都是一个linux命令就搞定了,如果搞不定那就用两个:

当然,如果需要,我们也是可以用java来实现的。

java中提供了两个Filter都可以用来实现这个功能。

今天讲解了Channel的具体分类,和一个简单的例子,后面我们会再体验一下Channel的其他例子,敬请期待。

大大大,我要大!小师妹要读取的文件越来越大,该怎么帮帮她,让程序在性能和速度上面得到平衡呢?快来跟F师兄一起看看吧。

小师妹:F师兄,你有没有发现,最近硬盘的价格真的是好便宜好便宜,1T的硬盘大概要500块,平均1M五毛钱。现在下个电影都1G起步,这是不是意味着我们买入了大数据时代?

没错,小师妹,硬件技术的进步也带来了软件技术的进步,两者相辅相成,缺一不可。

小师妹:F师兄,如果要是去读取G级的文件,有没有什么快捷简单的方法?

还记得上次我们讲的虚拟地址空间吗?

再把上次讲的图搬过来:

通常来说我们的应用程序调用系统的接口从磁盘空间获取Buffer数据,我们把自己的应用程序称之为用户空间,把系统的底层称之为系统空间。

传统的IO操作,是操作系统讲磁盘中的文件读入到系统空间里面,然后再拷贝到用户空间中,供用户使用。

这中间多了一个Buffer拷贝的过程,如果这个量够大的话,其实还是挺浪费时间的。

于是有人在想了,拷贝太麻烦太耗时了,我们单独划出一块内存区域,让系统空间和用户空间同时映射到同一块地址不就省略了拷贝的步骤吗?

这个被划出来的单独的内存区域叫做虚拟地址空间,而不同空间到虚拟地址的映射就叫做Buffer Map。 Java中是有一个专门的MappedByteBuffer来代表这种操作。

小师妹:F师兄,那这个虚拟地址空间和内存有什么区别呢?有了内存还要啥虚拟地址空间?

虚拟地址空间有两个好处。

第一个好处就是虚拟地址空间对于应用程序本身而言是独立的,从而保证了程序的互相隔离和程序中地址的确定性。比如说一个程序如果运行在虚拟地址空间中,那么它的空间地址是固定的,不管他运行多少次。如果直接使用内存地址,那么可能这次运行的时候内存地址可用,下次运行的时候内存地址不可用,就会导致潜在的程序出错。

第二个好处就是虚拟空间地址可以比真实的内存地址大,这个大其实是对内存的使用做了优化,比如说会把很少使用的内存写如磁盘,从而释放出更多的内存来做更有意义的事情,而之前存储到磁盘的数据,当真正需要的时候,再从磁盘中加载到内存中。

这样物理内存实际上可以看做虚拟空间地址的缓存。

小师妹:MappedByteBuffer听起来好神奇,怎么使用它呢?

它实际上是一个抽象类,具体的实现有两个:

小师妹:F师兄,这两个ByteBuffer有什么区别呢?这个R是什么意思?

R代表的是ReadOnly的意思,可能是因为本身是个类的名字就够长了,所以搞了个缩写。但是也不写个注解,让人看起来十分费解…

我们看下map方法的定义:

小师妹:F师兄,文件有只读,读写两种模式,是不是MapMode也包含这两类?

对的,其实NIO中的MapMode除了这两个之外,还有一些其他很有趣的用法。

    小师妹:F师兄,MappedByteBuffer因为使用了内存映射,所以读写的速度都会有所提升。那么我们在使用中应该注意哪些问题呢?

    本文再次介绍了虚拟地址空间和MappedByteBuffer的使用。

    小师妹:F师兄不都说JDK源码是最好的java老师吗?为程不识源码,就称牛人也枉然。但是我最近在学习NIO的时候竟然发现有些Buffer类居然没有注释,就那么突兀的写在哪里,让人好生心烦。

    居然还有这样的事情?快带F师兄去看看。

    小师妹:F师兄你看,以ShortBuffer为例,它的子类怎么后面都带一些奇奇怪怪的字符:

    还真有这种事情,给我一个小时,让我仔细研究研究。

    一个小时后,小师妹,经过我一个小时的辛苦勘察,结果发现,确实没有官方文档介绍这几个类到底是什么含义,但是师兄我掐指一算,好像发现了这些类之间的小秘密,且听为兄娓娓道来。

    小师妹,F师兄,你刚刚讲的都不重要,我就想知道类后面的B,L,R,S,U是做什么的。

    好吧,在给你讲解这些内容之前,师兄我给你讲一个故事。

    话说在明末浙江才女吴绛雪写过一首诗:《春 景 诗》

    小师妹,可有看出什么特异之处?最好是多读几遍,读出声来。

    小师妹:哇,F师兄,这首诗从头到尾和从尾到头读起来是一样的呀,又对称又有意境!

    不错,这就是中文的魅力啦,根据读的方式不同,得出的结果也不同,其实在计算机世界也存在这样的问题。

    我们知道在java中底层的最小存储单元是Byte,一个Byte是8bits,用16进制表示就是Ox00-OxFF。

    java中除了byte,boolean是占一个字节以外,好像其他的类型都会占用多个字节。

    如果以int来举例,int占用4个字节,其范围是从Ox-OxFFFFFFFF,假如我们有一个int=Ox,存到内存地址里面就有这样两种方式。

    第一种Big Endian将高位的字节存储在起始地址

    第二种Little Endian将地位的字节存储在起始地址

    其实Big Endian更加符合人类的读写习惯,而Little Endian更加符合机器的读写习惯。

    如果不同的CPU架构直接进行通信,就由可能因为读取顺序的不同而产生问题。

    java的设计初衷就是一次编写处处运行,所以自然也做了设计。

    在讲解这几个类之前,我们先要回顾一下JVM中对象的存储方式。

    还记得我们是怎么使用JOL来分析JVM的信息的吗?代码非常非常简单:

    上面的输出中,我们可以看到:Objects are 8 bytes aligned,这意味着所有的对象分配的字节都是8的整数倍。

    再注意上面输出的一个关键字aligned,确认过眼神,是对的那个人。

    aligned对齐的意思,表示JVM中的对象都是以8字节对齐的,如果对象本身占用的空间不足8字节或者不是8字节的倍数,则补齐。

    可以看到一个String对象占用24字节,但是真正有意义的是22字节,有两个2字节是补齐用的。

    对齐的好处显而易见,就是CPU在读取数据的时候更加方便和快捷,因为CPU设定是一次读取多少字节来的,如果你存储是没有对齐的,则CPU读取起来效率会比较低。

    这个问题其实还是很难回答的,但是经过师兄我的不断研究和探索,终于找到了答案:

    为什么相反?再看两者get方法的不同:

    不写注释实在是害死人啊!尤其是JDK自己也不写注释的情况下!

    NIO有三宝:Buffer,Channel,Selector少不了。本文将会介绍NIO三件套中的最后一套Selector,并在理解Selector的基础上,协助小师妹发一张好人卡。我们开始吧。

    小师妹:F师兄,最近我的桃花有点旺,好几个师兄莫名其妙的跟我打招呼,可是我一心向着工作,不想谈论这些事情。毕竟先有事业才有家嘛。我又不好直接拒绝,有没有什么比较隐晦的方法来让他们放弃这个想法?

    F师兄你看,我使用了Properties来读取文件,文件里面的内容是key=value形式的,在做配置文件使用的时候非常恰当。我是从Spring项目中的properties配置文件中得到的灵感,才发现原来java还有一个专门读取属性文件的类Properties。

    小师妹现在都会抢答了,果然青出于蓝。

    小师妹你做得非常好,就这样触类旁通,很快java就要尽归你手了,后面的什么scala,go,JS等估计也统统不在话下。再过几年你就可以升任架构师,公司技术在你的带领之下一定会蒸蒸日上。

    做为师兄,最大的责任就是给小师妹以鼓励和信心,给她描绘美好的未来,什么出任CEO,赢取高富帅等全都不在话下。听说有个专业的词汇来描述这个过程叫做:画饼。

    小师妹有点心虚:可是F师兄,我还有点小小的问题没有解决,有点中文的小小乱码…

    我深有体会的点点头:马赛克是阻碍人类进步的绊脚石…哦,不是马赛克,是文件乱码,要想弄清楚这个问题,还要从那个字符集和文件编码讲起。

    在很久很久以前,师兄我都还没有出生的时候,西方世界出现了一种叫做计算机的高科技产品。

    初代计算机只能做些简单的算数运算,还要使用人工打孔的程序才能运行,不过随着时间的推移,计算机的体积越来越小,计算能力越来越强,打孔已经不存在了,变成了人工编写的计算机语言。

    一切都在变化,唯有一件事情没有变化。这件事件就是计算机和编程语言只流传在西方。而西方日常交流使用26个字母加有限的标点符号就够了。

    最初的计算机存储可以是非常昂贵的,我们用一个字节也就是8bit来存储所有能够用到的字符,除了最开始的1bit不用以外,总共有128中选择,装26个小写+26个大写字母和其他的一些标点符号之类的完全够用了。

    后面计算机传到了全球,人们才发现好像之前的ASCII编码不够用了,比如中文中常用的汉字就有4千多个,怎么办呢?

    没关系,将ASCII编码本地化,叫做ANSI编码。1个字节不够用就用2个字节嘛,路是人走出来的,编码也是为人来服务的。于是产生了各种如GB2312, BIG5, JIS等各自的编码标准。这些编码虽然与ASCII编码兼容,但是相互之间却并不兼容。

    这严重的影响了国际化的进程,这样还怎么去实现同一个地球,同一片家园的梦想?

    于是国际组织出手了,制定了UNICODE字符集,为所有语言的所有字符都定义了一个唯一的编码,unicode的字符集是从U+0000到U+10FFFF这么多个编码。

    我笑着问小师妹:小师妹,把大象装进冰箱有几步?

    小师妹:F师兄,脑筋急转弯的故事,已经不适合我了,大象装进冰箱有三步,第一打开冰箱,第二把大象装进去,第三关上冰箱,完事了。

    小师妹呀,作为一个有文化的中国人,要真正的承担起民族复兴,科技进步的大任,你的想法是很错误的,不能光想口号,要有实际的可操作性的方案才行,要不然我们什么时候才能够打造秦芯,唐芯和明芯呢?

    师兄说的对,可是这跟unicode有什么关系呢?

    unicode字符集最后是要存储到文件或者内存里面的,那怎么存呢?使用固定的1个字节,2个字节还是用变长的字节呢?根据编码方式的不同,可以分为UTF-8,UTF-16,UTF-32等多种编码方式。

    其中UTF-8是一种变长的编码方案,它使用1-4个字节来存储。UTF-16使用2个或者4个字节来存储,JDK9之后的String的底层编码方式变成了两种:LATIN1和UTF16。

    而UTF-32是使用4个字节来存储。这三种编码方式中,只有UTF-8是兼容ASCII的,这也是为什么国际上UTF-8编码方式比较通用的原因(毕竟计算机技术都是西方人搞出来的)。

    小师妹,要解决你Properties中的乱码问题很简单,Reader基本上都有一个Charsets的参数,通过这个参数可以传入要读取的编码方式,我们把UTF-8传进去就行了:

    小师妹又有问题了:F师兄,这样做是因为我们知道文件的编码方式是UTF-8,如果不知道该怎么办呢?是选UTF-8,UTF-16还是UTF-32呢?

    小师妹问的问题越来越刁钻了,还好这个问题我也有准备。

    接下来介绍我们的终极解决办法,我们将各种编码的字符最后都转换成unicode字符集存到properties文件中,再读取的时候是不是就没有编码的问题了?

    转换需要用到JDK自带的工具:

    上面的命令将utf-8的编码转成了unicode。

    如果要做国际化支持,也是这样做的。

    千辛万苦终于解决了小师妹的问题,F师兄要休息一下。

}

我要回帖

更多关于 4的n次方等于多少 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信