——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
注:视频来源,毕向东老师的 JAVA 基础视频。
第一部分 IO概述
1、IO 流用来处理设备之间的数据传输。
2、Java 对数据的操作是通过流的方式。
3、Java 用于操作流的对象都在 IO 包中。
4、流按操作数据分为两种:字节流和字符流。
5、流按流向分为:输入流和输出流。
注意:流只能操作数据,而不能操作文件。
字节流的抽象类:
|–InputStream
|–|–FileInputStream
|–|–|–BufferedInputStream
|–OutputStream
|–|–FileOutputStream
|–|–|–BufferedOutputStream
|–ObjectInputStream
|–PipedInputStream
字符流的抽象类:
|–Reader
|–|–FileReader
|–|–|–BufferedReader
|–|–|–|–InputStreamReader
|–Writer
|–|–FileWriter
|–|–|–BufferedWriter
|–|–|–|–OutputStreamWriter
注意:由这四个类派生出来的子类名称都是以其父类名作为子类后缀的。
如:InputStream 的子类 FileInputStream
如:Reader 的子类 FileReader
第二部分 字符流和字符流缓冲区
一、字符流 FileWriter
1、找到一个专门用于操作文件的 Writer 子类对象:FileWriter (分解 FileWriter :后缀名是父类名,前缀名是该流对象的功能。)
2、FileWriter 的操作步骤:
1)、创建一个 FileWriter 对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定的目录下。如果该目录下存在同名文件,将被覆盖。
2)、调用 write 方法,将字符串写入到流中。注意:没有直接写到文件中,写入到流中(其实是写到内存中了)。
3)、刷新流对象中缓冲中的数据。将数据刷到目的地中。
4)、关闭流资源,但是关闭之前会刷新一次内部缓冲中的数据,将数据刷到目的地中。close和 flush 的区别:flush 刷新后,流可以继续使用,close 刷新后,流将会关闭。
3、把 FileWriter 定义成全局变量,供 finally 中的 close()方法。
问:为什么 close 方法要写在 finally 方法中呢?因为不再对流进行写入的时候,必须确保流的关闭。
4、使用 FileWriter 对已有文件的续写。给初始化的对象传递一个值为true的参数。
Demo:
package fuxi2;
import java.io.FileWriter;
import java.io.IOException;
/**
*
*@author XiaLei
*/
public class Day18Test1 {
//IO异常标准处理方式。
public static void main(String[] args) {
FileWriter fw =null;
//把 FileWriter 定义成全局变量,供 finally 中的 close()方法
//为什么 close 方法要写在 finally 方法中呢?因为不再对流进行写入的时候,必须确保流的关闭。
try{
//创建一个文件读取流,并确定数据要存放的目的地。如果文件已被创建,则会覆盖文件。
fw = new FileWriter("d:\\username.txt"); //在文件名后可以加上,true传递一个true参数,代表不覆盖已有文件,并在已有文件的末尾处进行数据的续写。
//把数据写入流中。
fw.write("abcd");
//刷新流,和close区别在于,刷新后可以继续使用流
// fw.flush();
}
catch(IOException e){
System.out.println(e.toString());
}
finally{
try{
if(fw!=null)//如果不加这句,可能会出现nullPointException,因为文件可能会创建失败,fw指向null。
//关流
fw.close();
}
catch(IOException e){
System.out.println(e.toString());
}
}
}
}
二、字符流 FileReader
1、常见一个文件流读取对象,和指定名称的文件相关联。要保证该文件是已经存在的,如果不存在,会发生异常:FileNotFoundException。
2、调用读取流对象的两种方法。
1)read 方法。read():一次读取一个字符。而且会自动往下读取。当读取不到数据的时候,就返回 -1 ,这时候把 -1 当中一个标记,如果返回-1,则结束读取。
2)通过字符数组进行读取。
定义一个字符数组,用于存储读到的字符
该 read(char[]) 返回的是读到字符个数。
char 的长度,通常定义1024 的倍数。
3、FileReader 与 FileWriter的测试练习:
package fuxi2;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 需求:将c盘的一个文本文件复制到d盘
*@author XiaLei
*/
public class Day18Test2 {
public static void main(String[] args){
FileReader fr = null;
FileWriter fw = null;
try{
fr = new FileReader("c:\\username.txt");//创建目的地
fw = new FileWriter("d:\\username.txt");//与已有文件关联
char[] buf = new char[1024];//创建一个字符数组容器
int len = 0;
while ((len = fr.read(buf))!=-1){//将读到的数据存入数组中,并返回读到的个数,如果没有数据返回-1
fw.write(buf, 0, len);//将数组中的数据写入流中
}
}
catch(IOException e){
throw new RuntimeException("读写失败");
}
finally{
//关流要分开try
try{
if(fr!=null)
fr.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
try{
if(fw!=null)
fw.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}
三、字符流的缓冲区
1、缓冲区的出现提高了对数据的读写效率。
2、对应类:
|–BufferedWriter
|–BufferedReader
3、缓冲区要结合流才可以使用。
4、在流的基础上对流的功能进行了增强。
因为缓冲区的出现是为了提高流的操作效率,所以在创建缓冲区之前,必须要先有流对象。该缓冲区中提供了一个跨平台的换行符:newLine();
readLine():读行,读一行数据。readLine() 返回:包含该内容的字符串,不包含任何终止符,如果已达到流末尾,则返回 null 。
缓冲区的知识点:
1、newLine 换行:在 linux 中或者是 windows 中都是换行。记住,只要用到缓冲区,就要记得刷新。
2、关闭缓冲区,其实关闭缓冲区其实就是在关闭缓冲区的流对象。
3、真正读写操作的是 FileWriter ,所以 fw 也要操作 close 方法。
缓冲区的使用方式:
只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
字符流缓冲区练习:
package fuxi2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 需求:通过缓冲区复制一个.java文件
*@author XiaLei
*/
public class Day18Test3 {
public static void main(String[] args) {
BufferedReader bufr = null;
BufferedWriter bufw = null;
try{
//创建一个读取流缓冲区对象和文件关联
bufr = new BufferedReader(new FileReader("D:\\JAVA\\day13\\StringTest.java"));
//创建一个字符写入流缓冲区对象和文件关联
bufw = new BufferedWriter(new FileWriter("D:\\StringTest.txt"));
String line =null;//可以理解为缓冲区的中转站
//while循环,判断,如果读取的行数不等于空,则继续循环
while((line=bufr.readLine())!=null){//readLine读取的是换行符之前的内容,当读到换行符时,它就会把读到的内容返回。
bufw.write(line);
bufw.newLine();//所以这里要加上newLine换行
bufw.flush();//有缓冲区必须要刷新
}
}
catch(IOException e){
throw new RuntimeException("读写失败");
}
finally{
try{
if(bufr!=null)
bufr.close();//缓冲区关闭,读取写入流也就关闭了,不用单独关闭
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
try{
if(bufw!=null)
bufw.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}
第三部分 装饰设计模式
1) 装饰设计模式:当想要对已有的对象进行功能增强时,可以定义一个类,将已有对象传入,基于已有对象的功能,并提供加强功能,那么自定义的该类就称为装饰类。装饰类通常会通过构造方法接受被装饰对象,并基于被装饰对象的功能基础上,提供更强的功能。
2) 优点:装饰设计模式比继承要灵活,避免了继承体系的臃肿。而且降低了类与类之间的关系。装饰类因为增强了已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能。所以装饰类和被装饰类都是属于一个体系中。
3) readLine()原理让我们明白了 BufferedReader 类中特有的方法 readLine 可以自定义一个类中包含一个功能和 readLine 一致的方法。来模拟一下 BufferedReader。
装饰设计模式之MyBufferedReader类:
package fuxi2;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 需求:建立一个MyBufferedReader类
* 这其实就是利用了装饰设计模式而设计的装饰类。这种设计模式,主要是对已有对象经行功能增强。其比继承要灵活,避免了继承体系的臃肿,降低了类与类之间的联系。
* 装饰类与被装饰类通常属于同一个体系。
*@author XiaLei
*/
public class Day19Test1 {
public static void main(String[] args) {
MyBufferedReader bufr = null;
BufferedWriter bufw = null;
try{
bufr = new MyBufferedReader(new FileReader("D:\\JAVA\\day13\\StringTest.java"));
bufw = new BufferedWriter(new FileWriter("D:\\StringTest.txt"));
String line =null;//可以理解为缓冲区的中转站
while((line=bufr.myReadLine())!=null){//readLine读取的是换行符之前的内容,当读到换行符时,它就会把读到的内容返回。
bufw.write(line);
bufw.newLine();//所以这里要加上newLine换行
bufw.flush();//有缓冲区必须要刷新
}
}
catch(IOException e){
throw new RuntimeException("读写失败");
}
finally{
try{
if(bufr!=null)
bufr.myClose();//缓冲区关闭,读取写入流也就关闭了,不用单独关闭
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
try{
if(bufw!=null)
bufw.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}
class MyBufferedReader{
//自定义的缓冲区,将fr作为参数传入
private FileReader fr;
MyBufferedReader(FileReader fr){
this.fr = fr;
}
//自定义一个可以一次读取一行的方法
public String myReadLine() throws IOException{//根据readLine读取到换行符就返回之前读到的内容这一原理,设计自己的读取方式。
StringBuilder sb = new StringBuilder();//建立容器,将读到的内容存入。
int num = 0;
while((num=fr.read())!=0){
if(num=='\n')
continue;
if(num=='\r')
return sb.toString();
else
sb.append((char)num);
}
//如果ch=='\n',就直接返回了。由于数据还存在于缓冲区,但是未写入到sb中,所以再做一次判断
if(sb.length()!=0)//当遇到条件判断时,一定要细心。多考虑一些情况!!!!!!!
return sb.toString();
return null;
}
public void myClose() throws IOException{//建立自己的关流动作。
fr.close();
}
}
4) LineNumberReader
在BufferedReader中有个直接的子类LineNumberReader,其中有特有的方法获取和设置行号:
setLineNumber();//设置初始行号
getLineNumber();//获取行号
根据其方法也可以创建一个与其功能相同的装饰类。
装饰设计模式之My LineNumberReader类:
package fuxi2;
import java.io.FileReader;
import java.io.IOException;
/**
* 需求:自定义个MyLineNumberReader装饰类,实现LineNumberReader功能。
*
*@author XiaLei
*/
public class Day19Test2 {
public static void main(String[] args) {
MyLineNumberReader lnr = null;
try{
lnr = new MyLineNumberReader(new FileReader("d:\\StringTest.txt"));
String line = null;
lnr.mySetLineNumber(100);
while((line=lnr.myReadLine())!=null){
System.out.println(lnr.myGetLineNumber()+":"+line);
}
}
catch(IOException e){
throw new RuntimeException("读取失败");
}
finally{
try{
if(lnr!=null){
lnr.myClose();
}
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}
class MyLineNumberReader{//这个类跟前面自定义MyBufferedReader装饰类,有很多相似功能,可以继承MyBufferedReader类,简化书写。
private FileReader fr;
private int count;
private int x;
MyLineNumberReader(FileReader fr){
this.fr = fr;
}
public String myReadLine() throws IOException{//根据readLine读取到换行符就返回之前读到的内容这一原理,设计自己的读取方式。
StringBuilder sb = new StringBuilder();//建立容器,将读到的内容存入。
int num = 0;
while((num=fr.read())!=0){
if(num=='\n')
continue;
if(num=='\r')
return sb.toString();
else
sb.append((char)num);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
public int mySetLineNumber(int x){
return this.x=x;
}
public int myGetLineNumber(){
if(count==0)
count = x;
return 1+count++;
}
public void myClose() throws IOException{//建立自己的关流动作。
fr.close();
}
}
部分打印结果如下:
第四部分 字节流和字节流缓冲区
1) 常用于操作媒体文件,如图片,mp3等。注意:不要拿字符流来拷贝媒体文件。
2) 字节的存储,不需要刷新,用到字节流缓冲区的时候才需要刷新等功能。
3) InputStream特有方法:
int available();//返回文件中的字节个数
注:可以利用此方法来指定读取方式中传入数组的长度,从而省去循环判断。但是如果文件较大,而虚拟机启动分配的默认内存一般为64M。当文件过大时,此数组长度所占内存空间就会溢出。所以,此方法慎用,当文件不大时,可以使用。
练习1 通过字节流拷贝图片:
package fuxi2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 需求:拷贝一个图片。
*@author XiaLei
*/
public class Day19Test3 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("d:\\1.jpg");//字节读取流关联资源
fos = new FileOutputStream("d:\\2.jpg");//字节写入流创一个对象,用于存储数据
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1){//循环读写,完成复制。
fos.write(buf, 0, len);
}
}
catch(IOException e){
throw new RuntimeException("复制图片失败");
}
finally{//关流。
try{
if(fis!=null)
fis.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
try{
if(fos!=null)
fos.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}
练习2 通过字节流缓冲区拷贝MP3文件:
package fuxi2;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 需求:利用带缓冲的字节流复制mp3文件。
*@author XiaLei
*/
public class Day19Test4 {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try{
bis = new BufferedInputStream(new FileInputStream("d:\\1.mp3"));
bos = new BufferedOutputStream(new FileOutputStream("d:\\2.mp3"));
int buf = 0;
while((buf=bis.read())!=-1){//read方法在将数据由byte类型提升为int类型,防止数据出现-1情况。
bos.write(buf);//write方法在做强转,将int类型转回byte类型。
}
}
catch(IOException e){
throw new RuntimeException("复制MP3失败");
}
finally{//关流。
try{
if(bis!=null)
bis.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
try{
if(bos!=null)
bos.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}
第五部分 转换流
转换流
System.out:对应的是标准的输出设备,控制台。
System.in :对应的是标准的输入设备,键盘。
readLine方法是字符流 BufferedReader 类中的方法。而键盘录入的 read 方法是字节流 InputStream 类中的方法。能不能将字节流转变成字符流再使用字符流缓冲区的 readLine 方法呢?这时候就用到转换流。
Demo:
package fuxi2;
import java.io.IOException;
import java.io.InputStream;
/**
* 需求:在控制台输入一行数据后就打印,要求当输入”over“时停止程序。
*@author XiaLei
*/
public class Day19Test5 {
public static void main(String[] args) throws IOException{
//获取键盘录入对象。
//InputStream in=System.in;
//将字节流对象转成字符流对象,使用转换流。
//InputStreamReader isr=new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
//BufferedReader br=new BufferedReader(isr);
//键盘录入最常见写法
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true){
int buf = in.read();
if(buf=='\n')
continue;
if(buf=='\r'){
String s = sb.toString();
if("over".equals(s))
break;
System.out.println(s);
sb.delete(0, sb.length());
}
else
sb.append((char)buf);
}
}
}
总结
1)高效缓冲区的使用。
文本文件操作的字符流:(将文本文件提高为字节流的操作方法,方便了文本文件的操作。)
|–BufferedReader
|–InputStreamReader
|–FileInputStream
|–BufferedWriter
|–OutputStreamWriter
|–FileOutputStream
媒体文件操作的字节流:
|–BufferedInputStream
|–FileInputStream
|–BufferedOutputStream
|–FileOutputStream
2)流操作的基本规律:
最痛苦的就是流对象有很多,不知道该用哪一个,那么使用下面方法分析
明确:(通过两个明确来完成)
1、明确数据 “源” 和 “目的”
源(目的):输入流,InputStream Reader
BufferedInputStream BufferedReader
源(目的):输出流,OutputStream Writer
2、明确操作的数据是否是 “纯文本”。
是:字符流,
不是:字节流,
3、当体系明确后,再确定要使用那个具体对象。
通过设备来进行区分:
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台。
练习:
package fuxi2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* 需求:将一个文本数据打印在控制台。
* 流操作基本规律:
* 1:明确源和目的:文本和控制台
* 2:操作的数据是否为纯文本本件:是
* 3:当体系明确后,再明确要使用哪个具体的对象:通过设备进行区分:
* 这里源设备为:硬盘
* 目的设备:控制台。
* 另外,System类中提供了setIn和setOut两个方法,分别可以改变源对象和目的对象。
*@author XiaLei
*/
public class Day19Test6 {
public static void main(String[] args) {
BufferedReader bufr = null;
BufferedWriter bufw = null;
try{
bufr = new BufferedReader(new FileReader("d:\\StringTest.txt"));
bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
catch(IOException e){
throw new RuntimeException("复制文件失败");
}
finally{//关流。
try{
if(bufr!=null)
bufr.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
try{
if(bufw!=null)
bufw.close();
}
catch(IOException e){
throw new RuntimeException("关流失败");
}
}
}
}