------- android培训、java培训、期待与您交流! ----------
写在前面:
在学习IO一章的时候,一开始因为流的种类太多,学到中间就把自己弄晕了,所以自己查API文档好好整理了一下IO流一章节的整体框架,再进行方法比较,思路就变得很清晰透彻了。
其实弄清了IO流很简单,无非是方法多了点儿,但去除从基类继承的也就那么几个,分清就好了。比如:基类Reader:其主要和常用子类有两个:InputStreamReader(自己有个getEncoding()方法)和BufferdedReader(自己有个readLine()功能); InputStreamReader有个子类FileReader(方法全部继承),BufferdeReader有个子类LineNumberReader(多了个设置和获取lineNumber的方法)。 看,就是这么简单。 其他基类都一样模式。
弄清了这章节的学习方法后,下面就是分散的知识点讲解了。
一、IO流的概念:
IO流用来处理设备之间的数据传输
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
流按操作数据分为两种:字节流与字符流
流按流向分为:输入流,输出流
一定要记住四个基类:其他流都是从这四个基类中继承出来的。所以,也只需主要记住基类的常用方法,然后再去单独记住各个子类的特性方法就会简单很多:
字节流的抽象基类:
• InputStream ,OutputStream。
字符流的抽象基类:
• Reader , Writer。
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
• 如:InputStream的子类FileInputStream。
• 如:Reader的子类FileReader。
二:IO程序的书写的三部曲。
1、导包:导入IO包中的类
2、异常处理:IO章节最常见的异常时IOException。如当内存不足,或文件不存在时。
a)为了让引用变量能在finally中被访问到,应在try外面建立引用,在try内进行初始化。
b) finally中关闭流之前,一定要对引用变量不等于0的判断。若有多个引用变量,要分别判断。
3、在finally中对流进行关: 因为流在使用时,调用了系统资源,所以要保证流的关闭以释放系统资源。
三、创建和读取文件:此处先说字符流。
File打头的流是对文件进行读写的便捷类。
A:用字符流创建文件
1、创建流对象,建立数据存放文件
FileWriter fw = newFileWriter(“Test.txt”);//如果想在已有文件基础上追加内容,即文件袋续写,可用调用构造方法FileWrite(文件名,true)即可
2、调用流对象的写入方法,将数据写入流
fw.write(“text”);
3、关闭流资源,并将流中的数据清空到文件中。
代码示例:
fw.close();
FileWriter fw = null;
try{
fw = newFileWriter("Test.txt");
fw.write("text");
}
catch (IOException e){
System.out.println(e.toString());
}
finally{
If(fw!=null)
try{
fw.close();
}
catch (IOException e){
System.out.println(e.toString());
}
}
B:用字符流读取文件
1、建立一个流对象,将已存在的一个文件加载进流。
• FileReader fr = new FileReader(“Test.txt”);
2、创建一个临时存放数据的数组。
• char[] ch = new char[1024];//一般定义为1024的整数倍
3、调用流对象的读取方法将流中的数据读入到数组中。
• fr.read(ch);
定义数组可以一次性读取大量数据,而不用一个数据一个的写,有利于提高读写效率。
举例代码:
FileReader fr = null;
try{
fr = new FileReader("c:\\test.txt");
char[] buf = new char[1024];
int len= 0;
while((len=fr.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
}
catch (IOException e){
System.out.println("read-Exception :"+e.toString());
}
finally{
if(fr!=null){
try{
fr.close();
}
catch (IOException e){
System.out.println("close-Exception :"+e.toString());
}
C:在对文件的读写操作时应注意的问题:
1、定义文件路径时,可以用“/”或者“\\”。(\\的转移字符是\)
2、在创建一个文件时,如果目录下有同名文件将被覆盖。
3、在读取文件时,必须保证该文件已存在,否则出异常。
四、字符流的缓冲区
缓冲区是为提高对数据的读写效率而出现的。
对应类
• BufferedWriter
• BufferedReader
缓冲区要结合流才可以使用,其构造方法中要传入一个流对象作为参数,因为它是在流的基础上对流的功能进行了增强。
其原理:其实就是在内部封装了一个数组,一次性将大量数据读入数组,再一次性写出,大大减少了磁头来回移动的频率。
这里涉及到了装饰类的概念:会在后面讲解。
A:缓冲区-写 BufferedWrite
为了提高字符写入流效率,加入了缓冲区;只要将被提高效率的流对象作为参数提交给缓冲区的构造函数即可。
缓冲区提供了一个跨平台的换行符方法: void newLine()
readLine只返回回车之前的内容,并不返回回车符。 所以,在写的时候要加上一个newLine()语句。
只要用到缓冲区写,就要记得刷新(用一次刷一次),以免因突然停电而丢失数据。
关闭缓冲区就是在关闭缓冲区中的流对象。
B:缓冲区 – 读。
字符读取流缓冲区,该缓冲区提供了一次读一行的方法: String readLine(),当返回null是,表示读到文件末尾。
C: 模拟缓冲区(模拟readLine())
其实,readLine()也是一个一个的读取字符,到封装的一个数组中,当读到换行符时即终止读取,返回一个字符串。
readLine()归根结底还是用的Filereader的read()方法一个一个字符的读取,并存到自己缓冲区数组中,所以,在模拟的BufferedReader类中,要封装一个FileWriter的对象。并且,在readLine中定义一个StringBuilder容器。
五、字节流:
A:
字节流的基本操作与字符流类相同,但它不仅可以操作字符,还可以操作其他
媒体文件,同样可以用BufferedInputStream/BufferedOutputStream来提高效率。
而字符流只能直接操作字符,如果想操作其他文件,就只能用字节流了,但可以用字节转换流包装后,再用其他字符流。
B:字节流File读写操作
InputStream/OutputStream
OutputStream 在调用write(int)一次写一个字符的时候,是不需要刷新的。但其他情况都是需要刷新的,比如()里的是字节数组
OutputStream 的write()方法,不同于Writer,不能直接写字符串;但可以将字符串转换成字节数组,如 fos.write(“abcdefg”.getbytes());
想要操作非纯文本文件,如图片、视频等,则要用到字节流。
C:字节流读取的时候,有三种方式:
1、 用read()方式,一次读一个字符;用while循环。
2、 定义一个字节数组(初始化长度一般指定为1024的整数倍),一次读取一个数组长度。用while循环。
3、 定义一个字节数组,长度初始化为[对象.available],也不用定义接受读取长度的变量和循环语句了,直接打印。(因为该字节数组长度刚好等于文件长度,刚好一次性读取完)。但有一定限制,因为虚拟机启动时默认内存空间为64M,故available的值若大于该内存,就会出现异常;所以,只对内存小的文件适用。
4、 平时,推荐使用第二种方式。
D: 字节流读写方法中的强转
InputStream和OutputStream的intread()和write(int)方法中,都对单个字符进行了强转处理。 读取时,为了防止文件二进制码中有连续八个一的状况被读取到会返回-1,就在每次读取一个字符后,把该字符进行了&255的操作,将单字节byte型提升到了四字节int型。但前三位补的是0,而不是1.
同理,当用write(int)方法写时,就进行了向下的强转,只保留低八位。
E:举例:拷贝图片
思路:其实就是读写结合。注意最后的finally分别判断并关闭资源。
1. 用字节读取流对象和图片关联。
2. 用字节写入流对象创建一个图片文件,用于存储获取到的图片数据。
3. 通过循环读写,完成数据的存储。
4. 关闭资源。
六、转换流:就是把字节流转换成字符流写入或将字节流转换为字符流写出而提供的流。
只有InputStreamReader, OutputStreamWriter两种。
转换流的由来
• 字符流与字节流之间的桥梁
• 方便了字符流与字节流之间的操作
转换流的应用
• 字节流中的数据都是字符时,转成字符流操作更高效。
例如:标准输入输出。
• 当需要用指定的编码集进行去除或写出时,只能用转换流了。
例如:编码解码。
七、标准输入输出流:
System类中的字段:in,out。
它们各代表了系统标准的输入和输出设备。
默认输入设备是键盘,输出设备是显示器。
System.in的类型是InputStream.
System.out的类型是PrintStream是OutputStream的子类FilterOutputStream 的子类.
例如:获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地。
可以通过System类的setIn,setOut方法对默认设备进行改变。可以实现文件的复制操作。
• System.setIn(new FileInputStream(“1.txt”));//将源改成文件1.txt。
• System.setOut(new FileOutputStream(“2.txt”));//将目的改成文件2.txt
但由于字节流处理的是文本数据,可以转换成字符流,操作更方便。两个经典写法,一定要熟练:
BfferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(System.out));
八、LineNumberReader
BufferedReader的子类,和BufferedReader用法相同,只是多了个setLineNumber()和getLineNumber()方法。
模拟LineNumberReader(set/getLineNumber(),用一个计数器,在每次调用readLine时增一)
可以继承父类BufferedWriter,调用父类的readLine()方法构造自己的readLine()方法时,计数器增加1即可。构造方法要传递一个FileWriter 对象,并定义一个此对象作为自己成员。
九、流的选择:
A:流操作规律-1
对文件操作时,如何选择各种流?用一下三步分析法和两个明确来完成:
1、
源:键盘录入;
目的:控制台。
2、需求:想把键盘录入的数据存储到一个文件中。
源:键盘。
目的:文件
3、需求:想要一个文件的数据打印在控制台上。
源:文件
目的:控制台。
两个明确:
1. 明确源和目的。
a) 源:输入流:InputStream Reader
b) 目的:输出流:OutputStream Writer
2. 操作的数据是否是 纯文本。
a) 是:字节流。
b) 不是:字符流。
3. 当体系明确后,再明确要使用哪类具体的对象。
a) 通过设备来进行区分:
i. 源设备:内存,硬盘(文件用File——),键盘
ii. 目的设备:内存,硬盘(文件用File——),控制台
B: 流操作规律-2
FileReader作为InputStreamReader的子类,只能用默认的字符编码集读取文件,如果需要用指定的字符编码集来操作文件的话,就只能用InputStreamReader了因为其构造方法可以指定字符编码集;同样FileWriter和OutputStreamWriter也是一样的。 如果是对文件进行操作,InputStreamReader中可以穿FileInputStream作为参数。
十、装饰设计模式(前面第四节的缓冲区提到)
A:装饰设计模式:
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象,
并基于被装饰的对象的功能,提供更强大的功能。
B:装饰和继承的区别
为了增强扩展性,装饰类应找到其参数的共同类型,通过多态的形式,提高其扩展性。
装饰模式比继承要灵活,避免了继承体系臃肿,而且降低了类与类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的。只不过提供了更强的功能,所以装饰类和被装饰类通常都是属于一个体系的。
C:自定义装饰类
自定义装饰类继承被装饰类的父类,就要实现其所有抽象方法,可以通过调用实际传入的子类对象的实现方法来实现。
十一、File类
用来将文件或者文件夹封装成对象
方便对文件与文件夹的属性信息进行操作
File对象可以作为参数传递给流的构造函数。
File类的构造方法中可以传递:目录,文件名,代表路径的字符串变量,目录和文件名。
十二、File类常见方法:
1, 创建。
Boolean createNewFile(); 在指定位置创建文件,如果该文件已经存在,则不创建,返回false.
和输出流不同,输出流对象一定会建立创建文件,而且文件若已存在,会覆盖。
Boolean mkdir():创建文件夹。最多创建一级(a\\b,就是一个文件夹下再有一个文件夹)
Boolean mkdirs();创建多级文件夹。
2, 删除。
Boolean delete();删除失败返回false。如果文件正在被使用,则删除不了,返回false.
void deleteonExit();在程序退出时删除指定文件。
3,判断。
Boolean exits():文件是否存在。不存在的对象也是不可执行的。
//在判断文件对象是否是文件或目录是,必须要先判断该文件对象封装的内容是否存在。
isFile();//判断一个文件对象是否是文件。若文件不存在,也就是只定义没被创建,结果肯定是false;因此在做是否是文件或路径判断前,一定要先判断文件是否存在。
isDirectory();//同上。
isHidden();
isAbsolute();判断文件抽象路径名是否是绝对路径,即使文件不存在也会进行判断。只要在File构造函数中,写的带盘符就认为是绝对路径。
4,获取信息。
getName();
getPath(); 构造函数中传递的抽象路径所有内容。也就是说,你封装的是什么路径,我获取的就是什么路径。
getParent(); 获取父目录。就是封装的路径中,去处文件名前面剩下的部分,如果只封装了文件名,则返回空。如果相对路径中有上一层目录,那么该目录就是返回结果。
getAbsolutePath();获取文件绝对路径。即使只封装了文件名(默认绝对路径为在当前路径),也会获得绝对路径。
long lastModified(); 最后一次修改时间。
long length();文件大小。
5, renameTo(); 可以用来改名字,或剪切文件。用一个文件调用该方法,改为另一个路径下的文件名,就完成了文件的剪切功能。
list();打印出指定文件目录下的所有文件,包括隐藏文件。可以将结果传递给一个字符串数组,在遍历打印。
调用list()的file对象必须是封装了一个目录,该目录还必须存在。否则返回值为空。
List(文件名过滤器);用内部类实现过滤器的accept()方法。
listFiles();
十三、File类操作常用的递归思想:列出目录下所有内容
列出目录下文件或者文件夹,包含子目录中的内容。也就是列出指定目录下所有内容。
因为目录中还有目录,只要使用同一个列出目录功能的函数即可。在列出过程中出现的还是自身的话,还可以再次调用本功能。也就是函数自身调用。这种表现形式,或者编程手法,称为递归。说白了,就是函数自己调用自己。
应用场景:
• 当某一功能要重复使用时。
递归要注意:
1. 限定条件。 要有限定条件的自我调用,否则会成死循环。
2. 要注意递归的次数,尽量避免内存溢出。
删除带内容的目录
删除原理:
在windows中,删除目录从里面往外删除的。
既然是从里往外删除。就需要用到递归。
应用实例:
将一个指定目录下的java文件的绝对路径,存储到一个文本中,建立一个java文件列表文件。
思路:
1. 对指定目录进行递归。
2. 获取递归过程所有的java文件的路径。
3. 将这些路径存储到集合中。
4. 将集合中的数据写入到一个文件中。
十四、Properties简介
Properties是hashtable的子类。
也就是说,它具有map集合的特点,而且它里面存储的键值对都是字符串。
设置和获取:
setProperty(key,value); 调用Hashtable的方法put. 设置键值对。
getProperty(key);获取指定键对应的值。
Set<String> stringPropertyNames(); 返回此属性列表中的键集。(是一个Set集合,可以遍历打印,可以用增强for循环)
Properties存取配置文件
如何将流中的数据存储到集合中。
想要将info.txt中的键值数据存储到集合中进行操作。
1. 用一个流和info.txt文件关联。
2. 读取一行数据,将改行数据用“=”进行切割。String[] arr = str.split(“=”);
3. 等号左边作为建,右边作为值。存入到Properties集合中即可。
方法二: 将流中的数据加载进集合。
用load(字符/字节(1.6以后)读取流)方法即可。
如:prop.load(new FileInputStream(“info.txt”));
输出(将属性表指定到输出流):list()方法,参数传一个PrintStream(包含System.out) 或PrintWriter
存储(到文件中):store(输出流,注释)方法,参数传一个OutputStream,或Wtiter,和load是对应的。
小技巧:创建共享软件的配置文件,限制免费试用次数。
十五、IO包中的其他类
A: Print 打印流
PrintStream
构造函数可以接收的参数类型:
1. file对象。File
2. 字符串路径。String
3. 字节输出流。OutputStream
字节打印流:<web开发中最常用的>
PrintWriter
构造函数可以接收的参数类型:
1. file对象。File
2. 字符串路径。String
3. 字节输出流。OutputStream
4. 字符输出流。Writer
5. PrintWriter的构造函数的自动刷新功能如果为ture,则 println、printf或 format 方法将刷新输出缓冲区(疑问:如果用其他方法编译会报错吗?是仅仅不能自动刷新吗,还是不能用其他方法了。)
以上两种打印流中,PrintWriter最常用。
也可以在输出流中指定文件为目的地,这样就打印输出到指定的文件上了。
个人建议:读入的时候用BufferedReader的readLine()方法,打印的时候,用Println().
IO流合并
SequenceInputStream(InputStream s1, InputStream s2)两个流合并。
SequenceInputStream(Enumeration<? extends InputStream> e)多个流合并。传递一个枚举类型参数,而枚举类型只有Vector(一种list容器)中有。
一般形式如下(以读取文件为例):
Vector<FileInputStream> v = newVector<FileInputStream>();
v.add(new FileInputStream(“C:\\1.txt”));
v.add(new FileInputStream(“C:\\2.txt”));
v.add(new FileInputStream(“C:\\3.txt”));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
以上就是第二个构造函数的基本格式。
但那样效率低),所以一般用ArrayList来代替Vector,虽然ArrayList没有枚举类型,但Iterator有。
对于大容量文件,比如500M进行切割成5个文件,每个100M。
这是就不能定义一个数组了一次存一个文件了,因为数组内存不能接近虚拟机启动默认内存64M。 解决方法: 可以先定义一个合适的数组,如1M的;再定义一个计数器,我们可以获取文件的大小,每次读取后存入文件,当文件达到100M时就再创建一个文件。
具体实现,以后再说。
对象的序列化
ObjectInputStream 对象读取流,
ObjectOutputStream 对象输出流/对象存储流
二者的构造方法,都可以传递一个字节流进去。
一般常用方法:readObject(obj) 和writeObject(obj) 成对出现,因为存入和读取对象类型只能用这两个方法。
readObject(obj) 将存储在指定文件中的对象读出来。一次读一个。
writeObject(obj) 将指定类在堆中new出的对象存储到指定的文件中。
被存储的对像所属的类必须被序列化。其实就是给类加上一个标识,如果类被修改后,则标识也会变化,故用修改后的类定义的对象就不能接收之前该类被存储的对象。除非,你用static final long serialVersionUID = 42L;自定义序列号。
被序列化的对象,必须要实现Serializable接口(没有方法)。
静态成员不能被序列化,因为静态不再堆内存;如果不是静态页不想被序列化,只需在其前面加一个transient 修饰符就好了。
管道流
PipedInputStream
PipedOutputStream
可以在构造方法中将相对的流作为参数,也可以用空构造方法构造后,用connect(对应管道流)连接。
最好两个管道使用不同的线程。可以通过创建两个实现ruannable接口的类,然后将两个管道流分别传穿进去,再把两个类对象传递给Thread的构造方法,调用start方法。
RandomAccessFile
• 随机访问文件,自身具备读写的方法。
• 通过skipBytes(int x),seek(int x)来达到随机访
该类不是IO体系中的子类,而是直接继承自Object.
但他是IO包中成员,因为具备读写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取指针位置。
同时可以通过seek改变指针的位置。
其实完成读写的原理就是,内部封装了字节输入流和输出流。
通过构造函数可以看出,该类只能操作文件。
而且操作文件还有模式:只读 r ,读写 rw 等。
如果模式为r,不会创建文件,会去读取一个已存在的文件,如果该文件不存在,则会出现异常。
如果模式为 rw,操作的文件不存在,会自动创建,如果存在,不会覆盖。
用seek()方法设定指针位置,就可以随机读写了。
另外,跳过指定的字节skipBytes(n)方法不常用。
操作基本数据类型的流对象
DataInputStream
DateOutputStream
其构造方法中传入一个字节输出流。
写的时候怎么写,读取的时候也要按照相应的格式读取。不然会乱码。
如:writeInt();writeBoolean();writeDouble(); 则读的时候也要按这个顺序。因为每次读取的字节数与写入时的应该一致。
writeUTF()写入的数据,也只能用readUTF方法读取,因为里面使用了UTF-8写改版的编码模式。
ByteArrayInputStream
用于操作字节数组的流对象。
ByteArrayInputStream;在构造的时候需要接受一个字节数组作为源数据。
ByteArrayOutputStream;在构造的时候不用自定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的。
因为这两个流对象都操作的数组,并没有使用系统资源。所以,不用进行close关闭。
也不会抛IO异常。(调用writeTo方法除外)
在流操作规律讲解时:
原设备:
键盘 System.in ;硬盘 FileStream ;内存 ArraySteam。
目的设备:
控制台System.out ;硬盘 FileStream ;内存ArrayStream。
用流的读写思想来操作数据。
以后操作数组时,可以封装到字节数组流中来。
writeTo(OutputStream)可以将数组中的全部内容一次性写入指定输出流中。当用该方法,就要抛IO异常了。也只有该方法抛IO异常。
size()返回缓冲区的当前大小。
toString()
另外两个类似的流:
操作字符数组:(如果文件里都是文字,这个就比较方便了)
CharArrayReader与CharArrayWrite
CharArrayReade源传递一个字符数组
操作字符串:
StringReader与StringWriter。
StringReade源传递一个字符串。
转换流的字符编码
主要用到InputStreamReader和OutputStreamWriter。
常用的GBK(中文默认),用两个字节代表一个字符;
GB2312用两个字节。
utf-8(也认识中文,但同一个字和GBK编码解码都不同),用三个字节标示中文
iso8859-1拉丁文编码、欧洲用的。 Tomcat服务器上默认的解码方式。
如果中文(GBK)被不认识中文的编码(iso8859-1)解读出项乱码,可以在用错码再编回去,用正确的解码再解一次。
但如果,被认识中文的错误编码(UTF-8)给解码成乱码了,就不能再反编码回去了。
所以,编码解码用的编码集一定不要弄错。
小技巧:发现文件乱码,可以往文件中写入“你好”,如果读取结果是“??”,就说明,是用GBK编码,UTF-8解的码;如果读取结果是“浣豺Y”就说明是用UTF编码,GBK解的码。