《java世界观》文集说明
- 本文集记载的所有内容均是java的学习笔记,主要重点记录概念,可能不会为每个概念列举完整的代码例子,代码例子请移步《java种田记》文集
- 本文主要涉及内容来自开课吧新职课-JavaEE开发工程师V2.0
- 若有错漏之处,欢迎各位指正
本文涉及类
File, InputStream, OutputStream, Reader, Writer, FileInputStream, FileOutputStream, FileWriter, InputStreamReader, OutputStreamWriter, PrintStream, PrintWriter, BufferedReader, Properties
正文
IO流概述
- 首先让我们明确一点,计算机中的任何数据(文本,图片,视频,音乐等)都是以二进制的形式,字节Byte作为单位进行存储的,所以数据传输时,底层均是二进制,大多以位Bit作为单位
- 将数据传输的操作视为一种数据的流动
- 可以按照传输的方向将流动分为 输入Input 和 输出Output,合起来即IO,如果将一个文件从C盘传输到D盘,则对于C盘来说,在输出,对于D盘来说,在输入。
流的分类
- 如刚才所说,可以按照方向将流分为 输入流 和 输出流
- 按照流动的数据类型将流动分为 字节流 和 字符流,字符的本质也是字节,由于字符读写操作的需求将其划分出来
Java.io常用类
Java中根据上文中的两种分类,提供了4个顶级抽象父类,分别是 字节输入流 InputStream, 字节输出流 OutputStream, 字符输入流 Reader, 和 字符输出流 Writer。无论是什么流,使用完毕后请务必调用close()释放资源
文件操作
文件传输(读写)是常见的IO流涉及领域。要对文件进行操作就不得不提到路径。
路径
路径分为
- 绝对路径
从盘符开始,是一个完整的路径,无论当前在什么地方(路径),都指向那一个文件,如C:\\a.txt
(Windows),~/Downloads/a.txt
(Mac/UNIX) - 相对路径
一个不完整的便捷路径,在命令行/终端中则是当前路径,在java项目中则是项目路径,如a.txt
基础操作
java为文件操作提供了 文件类 java.io.FIle ,一个File既可以表示文件也可以表示文件夹,File类中提供静态常量 默认名称分隔符 separator,和 路径分隔符 pathSeparator,在进行路径描述时应使用这些常量以兼容不同的操作系统。
文件的常用操作为创建createNewFile()
和删除delete()
,文件夹的常用操作为创建目录mkdir()
或mkdirs()
,列举目录中的子文件/文件夹list()
或listFiles()
,删除delete()
;还有一些常见的判断操作,如,isFile()
,isDirectory()
,exists()
;常见属性获取,如,getPath()
,文件大小(单位字节Byte)length()
;更多操作或细节详见API。
读写操作
文件字节流
回到IO流的主题上来,用File类只能进行文件的创建和删除,若要进行读写操作则必须使用流,java为文件的字节读写提供了FileOutputStream 和 FileInputStream 类
两者的构造函数中一参传入文件对象(File类对象)或路径字符串,二参传入是否在已有文件内容基础上进行添加的布尔值,不传默认是false;前者的常用操作为write(byte[] b)
和write(byte[] b, int off, int len)
,后者的常用操作为read()
和read(byte[] b)
,此函数会返回读入缓冲区的总字节数(存在有效读取字节数小于缓冲区长度的情况),两个read()函数返回-1则意味着文件到达末尾,读取完毕。
加密和解密
可以利用 FileInputStream 对文件的每一个字节进行读取,经过一定的运算后,通过 FileOutputStream 将更改过的字节写入新的文件进行加密。同理,通过反向运算可以解密。
对相同的数字进行二进制操作异或(^)两次,得到的结果和最初输入相同,可以利用这一点进行加密编程,运行一次为加密,运行两次为解密;异或原理如下,
- 1 ^ 1 = 0, 0 ^ 1 = 1;
- 1 ^ 0 = 1, 1 ^ 0 = 1;
- 0 ^ 1 = 1, 1 ^ 1 = 0;
- 0 ^ 0 = 0, 0 ^ 0 = 0.
文件字符流
字符编码
如上文所说,计算机中以二进制存储数据,字符编码则是将二进制表示为字符的规则(“辞典”)
广为人知的ASCII码,就将常见英文符号,字母,阿拉伯数字与二进制进行了对应,如A是1000001 (65),ASCII码中每个字符只用一个字节表示,从00000000到11111111,最多可以表示256个不同的字符
256个字符可以将英文常用字符表示完毕,对于其他语种便可能不够用,为了让计算机显示出其他语种的字符,各国均有发明更多的字符编码,均兼容了ASCII码,中国常用的字符编码便有GBK,GB2312,GB18030;当然,字符编码能表示的字符越多,每描述一个字符需要占用的空间则越大
乱码,是由于输入和输出所使用的编码不同,导致最终显示无意义字符的一种现象。现如今跨国业务需求日益增加,需求能够兼容大部分国家语种的字符编码,UTF-8应运而生,它支持绝大部分语言,表示字符的字节长度不固定(动态),此字符编码现被广泛使用,故建议使用
字符流
如上文所提及,UTF-8中一个字符所占用的字节长度是不确定的,如果用字节流进行字符操作,往往会遇到读取了“半个字”的情况,导致后续操作出现问题,最终结果无法解析。字符流通过对字节进行封装,将读写单位由字节转换为字符,解决了这个问题。
FileWriter 是java提供的文件字符输出流,除了Writer的常用操作外,还提供了append()函数,例如fw.append(“锄禾日当午”).append(“,”).append(“汗滴禾下土”);
缓冲 和 输出流的flush()函数
为了提高效率,字节并不是马上输出的,往往缓冲了一定字节大小的内容才会统一输出;而当输出的内容不足以填满缓冲区,却希望马上可以输出的时候,则需要调用 OutputStream 和 Writer 里的flush()函数。而close()函数在关闭流之前也会调用flush()清空缓冲区并输出内容。
转换流
在网络编程中,获取的流往往是字节流,需要转换为字节流;而利用装饰者模式,将字节流装饰为字符流的转换流是解决方案之一。
打印流
打印流提供了各种各样的print方法,使用打印流可以用println(),format()等方法进行更便捷的字符操作
缓存读取流
BufferedReader,提供一次读取一行的readLine()操作
配置文件 Properties
java中的配置类 Properties 实际为键值对存储(Map),通过store()
将配置输出到 .properties 文件中存储,再通过load()
将文件中的信息读取存储到Properties对象中,通过getProperty()
从对象中提取键对应的值字符串,可以实现存储处理应用配置的需求
Try-with-resources
如上文所说,所有的流在使用后都需要调用close()释放资源,而当我们使用try-catch处理异常时,语法上会非常麻烦;jdk1.7之后对此提供了支持,允许在try后添加小括号创建实现closeable接口的资源,在使用完毕后会自动释放;jdk1.9对此进行了优化,在小括号内可以传入多个先前创建过的closeable对象(多个对象之间用;隔开),在使用完毕后会自动释放资源。