File简介
要io首先得有文件,所以先介绍java中的文件类File。
File是java.io包下的类,File类的对象抽象代表当前操作系统的文件或者文件夹。
创建File对象时,文件路径中的分隔符使用斜线“/”或双反斜线“\\”或者使用“File.separator”作为分隔符。
//File.separator使用如下
File f = new File("D:" + File.separator + "dir1" + File.separator + "file1");
File类中常用方法小结:
boolean exist();//判断文件是否存在
boolean isFile();//判断是否文件
boolean isDirectoy();//判断文件夹
String File.getName();//获取文件
long File.lastModified();//最后修改时间
String getPath();//获取创建时的路径
由于通过File无法读写文件,所以需要IO流
IO流
字节流与字符流
IO流可根据传输的数据形式(大概算“数据形式”吧,不纠结专有名词)可分为字节流和字符流。
字节流中数据以字节为单位传输,字符流则以字符为单位。
字符集、编码、解码
字符集
数据在计算机中都是二进制码01,以字节为单位就是8bit一个单位,而字符是按特定规则编码而成的,不同的编码方式对应着不同的字符集,或者说不同的字符集有不同的编码方式。下面简单介绍ASCII码、GBK和UTF-8编码:
1. ASCII,全名美国信息交换标准代码,使用8bit编码一个字符,一共字母一个字节,总共可表示128个字符
2. GBK,用于编码汉字,兼容ASCII,包含两万多个汉字字符,汉字第一个字节第一位为1
3. Unicode32字符集,32bit存储,4个字节一个字符,太大不常用
4. UTF-8字符集,优化Unicode,采取可变长编码方案,分为4个长度区:1、2、3、4个字节
英文、数字只占用1个字节,汉字字符占用3个字节。区分规则:
一个字节首位为0,两个字节首位为110 第二个字节首位为10,三个字节首位1110,第二个字节首位10……
字符的编码和解码
//字符的编码和解码
Byte[] getBytes();//使用平台默认的字符集将String编码为一系列字节,存进新数组中
Byte[] getBytes(String cahrset);//指定字符集
//解码:
String(bytes[] bytes);//默认字符集解码
String(bytes[], String charset);//指定字符集解码
//例:
String s = new String(byte1, "GBK");
IO流体系
IO流分为输入流+输出流
或按数据最小单位分为字节流+字符流
字节流适合所有类型的文件,字符流只适合操作文本文件
叶节点就是抽象类的实现类
FileInputStream文件字节输入流
使用这些类时可使用多态方法,例如
InputStream stream = new FileInputStream("……");
Int Stream.read()读取文件的一个字节数据,会自增,一直循环可遍历输出,结束返回-1
在流使用结束后,要结束流释放资源,stream.close()
Int read(byte[] buffer) 可读取多个字节到buffer中,读满buffer或读完文件
在连续读取时,若最后读取到的不足以填满buffer,buffer中仍会满,读取的只会覆盖前面的数据,后面的仍会保留。所以在输出时,需要使用String(buffer, offset: 0, len: len)来实现"读多少出多少",0表示从buffer的头开始读,len表示获取出来的长度
由于按字节读取,分批读取可能会出现编码乱码问题,比如截断汉字编码,一次性读取完则不会出现截断问题
还可以使用byte[] readALLBytes()方法来读取,会返回所有的字节到一个数组中一起返回解码,若文件太大存不下就不会分配。
FileOutputStream文件字节输出流
输出时会自动创建输出的文件
1.创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("……");
在FileOutputStream后加true参数,表示追加输出,不会覆盖原数据
2.开始写字节出去
os.write()类比上述read()方法,每次写出一个字节,若直接输出会截断长字节编码导致输出乱码
综合使用上述俩流时,通过buffer数组来中继,读出到buffer中,再写出到目标文件中
释放资源
若操作流时出现问题导致程序中止,而没有执行末尾的close方法,则资源将被锁死。
Try-catch-finally
Try {
Sout();
}catch(Exception e){
e.printStackTrace();
}finally{
Sout();
}
加finally的区别在于,无论是否异常都会执行finally中的程序,而不会直接停止,除非JVM终止。
注意不要在finally中返回数据,主要用于释放资源
easier:
Try-with-resource:在try后加个(),在其中写资源分配的语句,结束后会自动释放
包装流
缓冲流
对原始流进行包装,提高原始流读写数据的性能
原来:不使用缓冲流,字节流每次读个字节进入内存的buffer然后写出,总共需要32次io
现在:加入8k缓冲流,2次读入,2次写出,一共4次io,内存中数据流动极快忽略时间,故大大提高速度
相当于默认使用buffer[8192]?
但是缓冲流仍然可以使用默认的read和write方法来一个一个字节读写?
使用原始流使用buffer,buffer与缓冲池同大时,效率相近,黑马说可以近似相同
缓冲池并非越大越快,缓冲池大虽然可以减少io次数,但是会增加单次io耗时。
BufferedInputStream(is, 32 * 1024)就是把字节输入流is包装成缓冲流,缓冲池大小32KB
缓冲流复制文件速度对比
缓冲流+一次读写一个数组 >= 原始流+一次读写一个数组 > 缓冲流+一次读写一个字节 >= 原始流+一次一个字节
对于缓冲池和byte[] buffer的大小,适中为好,太小会导致内外存io次数太多,太大会导致单此io时间过长,当使用buffer时,缓冲流和原始流复制文件速度相近。
字符输入转换流
解决不同编码时,字符流读取文本内容乱码的问题。
先获取文件的原始字节流,再将其按真实的字符集编码转换成字符输入流
用于文本复制时自然不会乱码,毕竟没有解码,但是在程序中读取并操作时,就会乱码
读入的时候,将原始流通过
InputStreamReader类包装,可指定也可自定义字符集编码,然后就可通过特定字符类型读入
写出同理。
打印流PrintStream
打印流能实现打印啥出去就是啥出去
可支持自动刷新和自定义打印字符集
应用:改变打印流方向
数据流
用于io数据及其类型
DataOutputStream dos = new DataOutputStrram(new FileOutputStream("……"));
Dos.writeInt
Dos.writeUTF("……")
DataInputStream ios = new InputStream(new FileInputStream("……"));
Ins.readInt()
Ins.readDouble()
序列化流
用ObjectInputStream类包装一个原始流,然后writeObject(Object obj)
如果要序列化某个类,该类要实现Serializable接口
学习黑马java基础篇io流小结,不甚全面,对于性能的探究并不深入,针对一40000KB大小文件,缓冲池与buffer在256KB时速度整体最快,增加到512KB、1MB时速度开始减慢
仍有疑惑,当buffer为50000KB时,速度反而极慢,此时只有读文件到buffer,写到目标这两次IO,单次IO会这么慢吗?
256KB时有320次左右IO,却最快。
以后再究,第一次blog,希望能坚持下去,也算挖个坑吧