IO流体系的知识梳理与深化,用EditPlus以代码格式编写
IO流(重点)
设备之间的数据传输,由于会操作系统底层设备,io操作会抛 IOException,编程时要进行处理
要认清"流"的概念,read 是获取数据到流中,write是将流中的数据写出去
主要体系: 抽象基类,派生出的子类都是以父类作为后缀
----------------------------------
IO继承体系"*"为较常用; "♂"&"♀"为成对使用
字符流继承体系
//Reader 实现接口Closeable, Flushable, 提供close()和flush()功能
Reader -->InputStreamReader "*"转换流又称桥梁流-->FileReader "*"//由此可见字符流在底层调用了字节流
-->BufferedReader "*"缓冲&装饰-->子类: LineNumberReader 行操作
-->CharArrayReader 操作字符数组
-->StringReader 操作字符串
//Writer 实现接口 Closeable, Flushable, Appendable 多一个append() Appendable接口出现于1.5版本
Writer -->OutputStreamWriter "*" -->FileWriter "*" //append方法用于添加字符
-->BufferedWriter "*"
-->PipedWriter -->PrintWriter 打印流 "*"
-->CharArrayWriter
-->StringWriter
字节流继承体系:
//InputStream 实现Closeable接口 close()
InputStream -->FileInputStream "*"
-->FilterInputStream 过滤流-->BufferedInputStream 字节流缓冲区(装饰)
-->DataInputStream 操作基本数据类型
-->SequenceInputStream 合并流
-->ObjectInputStream"♀" 序列流 对象反序列化,将对象数据读取进流
-->PipedInputStream "♂" 管道流,发送
-->ByteArrayInputStream 操作字节数组
//OutputStream 实现Closeable, Flushable接口 close() & flush()
OutputStream-->FileOutputStream "*"
-->FilterOutputStream-->BufferedOutputStream
-->PrintStream 打印流
-->DataOutputStream
-->ObjectOutputStream "♂"对象序列化 持久化,对象需实现 Serializable "标记接口"
-->PipedOutputStream"♀" 管道流,接收
-->ByteArrayOutputStream
IO体系外其它相关类
File"*" 操作文件和目录
RandomAccessFile 随机读写
Properties 该类是 Hashtable 的子类,位于java.util包中
-----------------------------------
字符流
FileReader 读入步骤: new FileReader(File)/*已有文件*/-->(循环)int read() ||int read(char[])-->操作--(-1 循环结束)-->close()
FileWriter 写出步骤: new FileWriter(File)/*创建|覆盖*/-->(循环)write(...)-->flush()-->close()
文件续写: new FileWriter(File,true) ... //将true改为false就是覆盖原文件
字符文件的copy就是循环的read()和write()
字符流缓冲区:用于提高效率 "装饰类"
BufferedReader 读入: new BufferedReader(new FileReader(File))-->(循环)String ReadLine()-->操作--(null 循环结束)-->close()
子类: LineNumberReader 使用方法: new LineNumberReader(new FileReader(File))
//.setLineNumber(int n)设置行号,从第n行开始readLine() || .getLineNumber()获取行号
BufferedWriter 写出: new BufferedWriter(new FileWriter(File))-->(循环)write(linne)-->newLine()-->flush()-->close()
使用缓冲区能够减少读写的次数,提高效率
设计模式之--"装饰设计模式"Decorator
当想要对已有对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,提供加强功能,自定的该类称为装饰类
readLine()是read()方法的增强,BufferedReader是FileReader类的增强类
相比继承扩展,装饰模式降低了两个类之间的关联度,避免了继承体系的臃肿,具有更高的灵活性和扩展性
----------------------------------------------------------------
字节流
FileInputStream 读入:new FileInputStream(File)-->(循环)int read() || read(byte[])-->操作--(-1 循环结束)-->close()
读入的第三种方式: new FileInputStream(File)--> new byte[ fis.available()]-->read(byte[])-->close()
//available返回该文件对象有效的字节数,不使用循环而是一次性写入,文件较大时要慎用(JVM内存分配默认为64m)
在读写文件时,使用数组缓冲与单字节读写相比往往有数十倍的性能提升,可以使用"模板模式"来对比三种方法的效率
一般来说缓冲区大小为1M左右时效率是最高的,当然,不同系统环境也可能有差异
FileOutputStream 写出:new FileOutputStream(File)-->write(byte[])-->close()
BufferedInputStream
BufferedOutputStream
自定义字节流缓冲区 <==> read & write的特点
计算机中数据以二进制保存,以字节以byte为单位存取。读取时为便于操会将二进制转成十进制
这样问题就随之而来了,字节流的read方法以返回值 -1 来表示到达文件末尾
如果刚好有连续的8个1,直接转成int型就是-1,就意外满足了read 方法-1的控制条件
导致程序提前结束,所以字节流缓冲区的read方法必须避免这种情况的发生生
解决办法:将 byte 转为 int,然后通过二进制"&"运算补0
byte -1 --> int -1
11111111 11111111 11111111 11111111
&00000000 00000000 00000000 11111111 //255
------------------------------------
00000000 00000000 00000000 11111111 //int
这也是为什么读取单个字节时read 方法的返回值为 int 而不是 byte 的原因
---------------------------------------
读取键盘录入
标准的输入输出流: System.in(InputStream) & System.out(PrintStream)
转换流: InputStreamReader
OutputStreamWriter
键盘录入最常见形式:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //字符 <--字节
BufferedWriter bufw = new BufferedWriter(new InputStreamReader(System.out));//字符 -->字节
设置源和目的:
System.setIn(new FileInputStream("ReadIn.java")); //源,也可直接将字节流传入转换流
System.setOut(new PrintStream("printStream.txt")); //目的, FileOutputStream 亦可
1)字节流和字符流最大的区别是,字节流可以操作任何数据,字符流只能操作文本数据。
2)字符流在底层调用了字节流的缓冲区,所以需要刷新动作;而字节流在操作时一般不需要刷新
数据在硬盘中是以二进制存储的,以 byte (8位)为单位,字节流的read 和write 操作单位对象是 byte
字符流在底层调用了字节流,专门用于操作字符,其read 和 write 方法操作的单位对象是 char
使用时要区分"源"&"目的",操作对象是否为纯文本? 字符:字节
--------------------------------------
IO流中另一个重要的类 "File"
File 类是文件和目录路径名的抽象表示形式。 专门用于对文件进行增删改查的操作
File类的过滤,两个接口
FileFilter 过滤路径
FilenameFilter 过滤文件名
示例: 过滤获取指定文件夹下的 .java文件
File dir = new File("f:\\JAVA\\test");
String[] arr = dir.list(new FilenameFilter(){//匿名内部类
public boolean accept(File dir,String name) {
return name.endsWith(".java"); //String类方法
} });
Java的跨平台分隔符"separator"
"递归": 即函数自身调用自身
使用递归要注意: 1)要有明确的递归结束条件;避免死循环
2)控制调用次数,避免栈内存溢出
3)递归书写简洁,但效率较低,应避免在算法题中使用
应用: 遍历访问指定目录下文件及其子目录中内容,并对其进行操作
-----------------
Properties 类
Properties 是hashtable的子类,具备map集合的特点,里面存储的键值对都是字符串,该集合可以加载进IO流
该类提供了一些对io流进行操作的方法
较为熟悉的应用:
系统运行日志: Properties prop = System.getProperties();
从配置文件中获取数据,以键值对的形式存入集合并进行操作。如限制软件运行次数 time = 5;时停止服务
"打印流" PrintWriter & PrintStream
可以直接操作输入流和文件。
PrintWriter out = new PrintWriter(new FileWriter("...") ,true)
PrintWriter 的print & println 就是常用的输出语句sop,//该方法可以自动刷新,换行
"合并流" SequenceInputStream
对多个输入流进行合并,到同一个输出流
"文件的分割" 即将一个输入流对应到多个输出流, 输出大小以一个缓冲数组为单位
"文件的合并" 即将多个输入流指向同一个输出流
使用 Vector & Enumeration :List数组集合枚举
示例:
Vector<FileInputStream> v = new Vector<FileInputStream>
v.add(new FileInputStream(File))... //添加多个输入流
new SequenceInputStream(v.elements()); //该参数为枚举类型,合并源
定义输出目的...读写操作
使用 Vector+枚举 不够高效,使用 ArrayList+迭代 来代替
而 SequenceInputStream 的构造参数必须为枚举类型
所以就有必要复写 Enumeration 接口中的方法
示例:
定义 ArrayList 和 Iterator
合并: new SequenceInputStream(new Enumeration<FileInputStream>(){
public boolean hasMoreElements(){//接口型匿名内部类
return it.hashNext();
} //将迭代结果作为枚举结果,提高效率
public FileInputStream nextElement(){
return it.next();
}
})
"序列流" ObjectInputStream & ObjectOutputStream
这两个类必须成对使用
方法: readObject() & writeObject()
为了在下次程序执行时继续使用对象数据,out序列化将对象保存到硬盘,in反序列化从硬盘读取数据
对对象进行序列化(持久化) 与反序列化操作, 被操作的对象所属类需要实现 Serializable "标记接口"
序列化示例:
class Person implements Serializable //被操作的对象要实现标记接口
new ObjectOutputStream(new FileOutputStream("obj.txt")).writeObject(person)//对象写入文件
new ObjectInputStream(new FileInputStream("obj.txt")).readObject() //从文件中读取对象
注:序列化操作的是堆中的对象,所以 static 成员不能被序列化
private 成员无法被序列化,使用 UID 标识可打破该限制
非静态成员加上 transient 修饰就不会被序列化
"管道流" PipedInputStream & PipedOutputStream
这两个类也是一起使用的
一般流的输入输出没有太大关联,而管道流输入输出可以直接进行连接,
必须相互连接后创建通信管道;要结合多线程使用,单线程可能会死锁。
示例:
class Read implements Runnable{} //封装线程任务
class Write implements Runnable{}
main //主函数调用
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream(in);//构造参数in,也可空参,然后in.connect(out)关联
new Thread(new Read(in)).start(); //创建线程
new Thread(new Write(out)).start();
阻塞式方法,使用中要预防线程死锁
Object-->RandomAccessFile 随机读写文件
强大的随机读写功能
特性: 构造对象时指定读写权限, "r"只读,"rw"读写
seek(8*x)方法,通过指针从文件的某个位置开始读写
该类是IO包中成员,具备文件读写功能,内部封装了字节输入流和输出流
随机是因为在内部封装了一个byte[] 数组,通过指针对数组的元素进行操作,
同时可以通过getFilePointer获取指针位置,通过seek改变指针位置。
创建对象示例: new RandomAccessFile("File","rw") //能够对该文件读写操作
RandomAccessFile 类如果引入多线程技术,就可以实现 P2P 下载,大大提高效率
其它类似的类区分
RandomAccess util包中的标记接口,被 List 实现,用来表明其支持快速随机访问,
该类与 RandomAccessFile 无直接联系,另见该包中 Random 类
另 java.lang 包中 Math 类的 random() 方法也提供了随机功能"骰子"
----------------------------------------------
其他基本数据流
DataInputStream & DataOutputStream 专门操作基本数据类型的流对象
构造时要包装字节流使用
如使用 writeInt(22)方法写入; 再使用 readInt()读取到的 数据类型为 int 而不是 byte
ByteArrayInputStream & ByteArrayOutputStream 专门用于操作"字节"数组
数据源 为字节数组
CharArrayReader & CharArrayWriter 操作"字符"数组
StringReader & StringWriter 操作字符串
-----------------------------------------------
字符编码的问题:乱码
如果编码时的码表与解码时的码表不一致,就会导致乱码的情况
转换流 具有指定码表的构造方法
示例: new InputStreamReader(new FileInputStream("gbk.txt") , "utf-8")//指定utf-8来解码
编码与解码的过程: String --编码--> byte[] --解码-->String
"中间码"的问题:
客户端与服务器端的码表不一致,例如一个服务器端(iso8859-1)的网页在客户端(中文 GBK)打开时就可能会产生乱码,
所以要加上二次编码与解码的过程,将乱码按照 iso8859-1 码表编码成 byte,再将该数据按照 GBK 解码
要注意的是: 作为中间码,一定要保持数据的精度, 即一个字符占据的字节要少于"终端码",
如: 终端码为GBK时,就不能用utf-8 作为中间码,因为utf-8 部分字符会占据3个字节,gbk为2个
在二次编码的过程中会按照utf-8 码表添加一些字节进去,导致数据精度损失,还会造成乱码。
"为什么不能用字符流copy 图片"
字节流可以操作任何文件,因为其操作单位是数据存储的最基本类型 byte
字符流只能用于操作文本,操作的基本数据类型为字符 char,一个字符一般占据 2(1~3)个字节
字符流在底层使用了字节流,通过查询编码表(java默认为 unicode)获取到字符,如果码表中能查到这两个字节对应的字符
就会返回该 char, 如果没有对应的字符就返回 -1;
所以,如果字符流操作的是文本,该文件内的字节在码表中都能够找到,就不会发生丢失数据的问题
而如果字符流操作的是非文本文件,就不可能保证所有的字节都能在码表中找到对应的字符,而这些找不到字符的字节
随着返回的-1 就消失了,从而造成字节丢失,数据损坏