基础加强____【IO流个人总结】



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 就消失了,从而造成字节丢失,数据损坏




posted on 2014-02-09 17:45  hwren  阅读(211)  评论(0编辑  收藏  举报

导航