【Lehr】Java IO
前言
与C语言只有单一类型FILE* 包打天下不同,Java拥有一个流家族,包含各种输入/输出流类型,其数量超过60个!!!!!!
之前试图跟着CSAPP后面的Proxy Lab去写一个C语言服务器,奈何我太菜了,所以就用Java写了…
可是写的时候发现…emmmm…这个输入输出流可差点没把我给搞死…传个图片没有选择正确的输出流类型服务器直接爆炸…
所以复习了一波,写了点小总结…
文件与编码
在开始讲IO之前,先想废话谈谈Java的老File和一些编码相关的知识
可以直接跳过XD
File类
Java.io.File类:不涉及到具体的文件内容 ,用来代表文件。只涉及到文件的基本属性,路径,名称,类型
构建对象的方法:
File d = new File("地址字符串");
Ps:Java中路径分割最安全的方法是 File.separator 来代替\\在Windows,比你直接写死了好
常用方法
isFile() 判断是否是文件
isDirectory() 判断是不是文件夹 就是看是不是目录
getName() 得到文件的名字
getParent() 得到上一层目录
getPath() 获取文件的全路径
length() 获取文件的大小
lastModified() 获取最后的修改事件
listFiles() 返回所有子文件 返回值是个文件数组类 File[]
delete() 删除功能
exist(); 判断存在 返回布尔类
d.mkdirs(); 创建多级目录
d.createNewFile(); 创建这个文件
Ps:如何在文件不存在的情况下创建目录?
exist() + d.mkdirs() +d.createNewFile()这三个一起配合使用即可
编码相关
字节和字符
字符:不可以分割的,人为理解的,计算机不认识的,Eg: 0
a
我
这种都是字符
字节:计算机只懂的0和1,全是二进制数字
字符利于人来阅读,但是不利于计算机阅读
编码类型
**ASCII码:**一个字节Byte八个小比特来存储常见英文和字符最多表示256个字符,所以显然是不够的
扩展编码(加字节)
ISO8859 ----- 西欧语言
GBK --------- (最常用的2万多个汉字)
GB18030 ---- 是GB系列表示中文编码 最全的 七万个汉字和字符
Big5 ------------ 繁体中文
即使这样了但还是不能共通,所以,有了Unicode
**Unicode:**全世界所有字符囊括在内的,目标是不断扩充囊括全球语言!
编码方案
UTF-8: 兼容ASCII ,采用变长字符 ,1-4个字符存储字符小的字母(比如a,b,c)就1个 ;一个汉字就用2个。
UTF-16 : 2-4个变长
UTF-32: 用四个字节。反正32bits肯定够大,但是不经济。所以UTF-8还是最常用的
**ANSI编码:**Windows上非Unicode的默认编码,简体中文系统时是GBK,繁体中文的系统就是Big5。记事本默认的是采用ANSI保存 所以你不同地区国家的互发文件就容易出乱码。
IO概况
Java中的IO操作可以按照以下方式分类:
输入流和输出流
比较好理解,就是从程序的角度,把文件当成外部,则:
你要从外界读入内容就是Input
要往外界里写入就是Output
这里的外界可以是:文件、socket、标准输入输出设备(说白了就是键盘和屏幕终端)等等
字符流和字节流
我们以Input为例,常常会看到以下两种类型:
InputStream:
- 以字节方式读入文件
- 就是byte[]
- 每个单位8bit,就是1个字节
往往媒体文件,比如视频和图片就会以二进制形式进行存储
Reader:
- 以字符方式读入文件
- 就是char[]
- 每个单位16bit,就是2个字节(Java里char就是2个字节)
以上只是输入,还有输出系列:
Writer
OutputStream
所以:er结尾的就是字符,Stream结尾的就是字节
节点类和包装类
节点类是直接建立程序与外界的通道:
例如:
- 要想建立程序与文件之间的通道:FileInputStream fis = new FileInputStream(“fileName”);
但是现在你只能以byte的方式来读取文件内容,而且每次读一个字节就会访问一次文件
包装类则是让你在读or写的时候更方便的:
比如你想一次读出一个 数据类型 (继续沿用上面的fis)
- DataInputStream dis = new DataInputStrream(fis);
或者你想一次读一行数据?
- BufferedInputStream bis = new BufferedInputStream(bis);
支持一层包一层组合使用
这也是装饰思想的一个体现,大概长这样:
节点类
InputStream、OutputStream、Writer、Reader这些,都是抽象类
通常要建立起一个IO流,我们需要:首先来一个节点流,然后再来一堆套娃包装类
不过再怎么样,基本上都会用到以下这些方法:
读
read();
取出单个字节然后以int的形式返回。由于字节只有8bit 所以就是 0-255的范围
所以你读汉字的时候必不可能用字节流。读到尾部返回-1
你输出的时候就可以这样:
int c = f.read();
System.out.println((char)c);
read(buffer);
使用之前先创建缓存区,也就是byte[] 或者char[] 然后大小确定了之后放进去一取就ok了,直到返回值是-1
多个字节的读入方法 返回值是读了多少个,但是如果你每次读100个,最后只有40 个,那就会重复读60个,从倒数100个读。
byte[] buffer = new byte[1024]
int len = System.in.read(buffer);
read读的所有IO操作都带着exception的异常处理或者抛出!
这里注意 是以字节的方式读入的所以如果用输入流的时候会把那个回车也读进来 length就会比实际的多一个!
写
flush();
write();
基本和上面一样的,把这个字节写入
关闭
close();
对于外部资源,不在JVM的GC管辖范围内,所以需要手动关闭
而且,包装外层的关闭了,内层的也就自动关闭了,例如:
FileInputStream fis = new FileInputStream("fileName");
BufferedInputStream bis = new BufferedInputStream(bis);
bis.close();
这样的话fis也自动关闭了。但是,真正在写的时候close还会抛出错误IOException,所以还需要try-catch,这样比较麻烦。他们实际上还继承了一个AutoCloseable的接口,在JDK1.7以后,提供了一个语法糖try-resources
用法如下:
try(
FileInputStream fis = new FileInputStream("fileName")
){
//....
}
用完就自动关闭了不用管了
示例
把一个文件的内容写入另外一个文件的过程:
try(
FileInputStream fis = new FileInputStream("fileName");
FileInputStream fos = new FileOutputStream("anotherFileName")
){
Integer len = 0;
byte[] buffer = new byte[1024];
//循环读入
while((len = fis.read(buffer))!=-1)
{
fos.write(buffer,0,len);
}
}catch(IOException e)
{
System.out.println("md出错了!");
}
装饰类
在上面那个例子里我们可以发现,我们只能对byte(或者char)进行单个操作,而且实际在操作的时候每次都是从文件里访问一次,取一个字节,这样是不是太不方便了呢?假如我们想每次读入一行来判断呢?
这时候,就需要引入包装类的概念了
包装类可以在原有的流的基础上进行操作,相当于过滤器,让我们在操作的时候有更多的方法,下面是几个常见的包装类
BufferedReader
Buffer是什么意思?缓冲!
所以,Buffered系列是在建立流的同时附带了一个缓冲区,每次在你读写的时候,数据会被先放到这个缓冲区里,而不是每次都访问设备,直到缓冲区满了的时候,才写入或者读入
以BufferedReader为例,最大的好处就是,他可以让你把字符串一行一行读入:
FileReader fr = new FileReader("fileName");
BufferedReader br = new BufferedReader(fr);
......
String firstLine = br.readLine();
InputStreamReader
你想从你的二进制文件里面以字符串形式来读内容?可以的!
InputStreamReader提供了自定编码来帮你转换这个过程的接口!
再配合上BufferedReader!
使用示例如下:
FileInputStream fis = new FileInputStream("fileName");
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
BufferedReader br = new BufferedReader(isr);
这样中规中矩的写起来有点麻烦,所以实际可以这样写:
BufferedReader br =
new BufferedReader(
new InputStreamReader(
new FileInputStream("fileName"),"utf-8"));
这才是真正的套娃玩法…
其他还有很多比如DataInputStream,可以让你一个数据类型一个数据类型的读写,还有ZipInputStream,用来处理压缩文件的读写;还有PushBackInputStream这种,读多了可以回溯的…使用上也基本类似,就不做讲解了。
…未完待续