------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------
一、IO流概述
1、概述
IO流:(Input Output Stream)
2、特点
1) IO流用来处理设备间的数据传输。
2)Java对数据的操作是通过流的方式。
3)Java用于操作流的对象都在IO包中。
4)流按操作数据分为两种:字节流和字符流。
5)流按流向分为:输入流和输出流
字符流用来处理文本数据,而除了文本数据以外,都是字节流的处理对象。
3、IO流的常用基类
IO流中有n多个子类,形成一个体系。体系中一共有四个基类。对于数据的操作,只有两种:读和写。
1) 字节流的抽象基类
inputStream(读) OutputStream(写)
2) 字符流的抽象基类
Reader Writer
注: 由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream
如:Reader的子类FileReader。
如何使用IO体系呢?先找IO包,java.IO中的四个基类。
二、字符流
1、概述
public abstract class Writer extends Object implements Appendable, Closeable, Flushable
写入字符流的抽象类。
字符流用于处理文本数据。使用的是编码表,我们的机器中默认使用的是GBK。美国默认的是UTF-8.
子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 看到构造函数被protected修饰,我们就明白,这是供子类使用的。方法全是抽象的,是需要被子类实现的。既然IO流是用于操作数据的。那么数据的最常见体现形式是:文件。
一个专门用于操作文件的Writer子类对象,FileWriter。后缀名是父类名,前缀名是该流对象的功能。
2、字符流操作
1)写入字符流
a 创建一个FileWriter对象,它没有空参数的构造函数,所以该对象一被初始化,就必须要明确被操作的文件。如果该目录下如果已有同名文件,则同名文件将被覆盖。
b调用write(String s)方法,将字符串写入到流中。
c调用flush()方法,刷新该流的缓冲,将数据刷新到目的地中。
d调用close()方法,关闭流资源。但是关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。
close()和flush()区别:
flush()刷新后,流可以继续使用;
而close()刷新后,将会关闭流,不可再写入字符流。
注意:记得关闭资源,因为是java在调用系统的底层资源完成读写操作的。
文件的数据的续写是通过构造函数FileWriter(Strings,boolean append),在创建对象时,传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
由于在创建对象时,需要指定创建文件位置,如果指定的位置不存在,就会发生IOException异常,所以,需要对IO异常进行处理。
说明:Flush()和close()的区别:
A Close()关闭流资源,但是,关闭之前会刷新一次内部的缓冲中的数据,将数据刷到目的地中。
B Flush()刷新后,流可以继续使用。Close()刷新后,会将流关闭。
我们知道,我们直接在图形界面窗口中,也能新建文件,并写入数据,其实调用的就是windows内部的文件操作。 其实,java本身是不能在文件中写数据的,因为它是基于平台的,平台不同,内部的操作方式也不同,所以java是依赖调用系统内部的文件操作来写数据。所以是在使用系统的资源。所以,有一个动作一定要做,就是close();而close()的内部就是先调用一次flush(),然后再到底层关闭下资源。
C 两者都抛出IOException。
比如:write(),有可能写数据写到没内存了。Close(),有可能关闭不了。
2)文本文件读取方式一
A 创建一个文件读取流对象,和指定名称的文件相关联。该文件若不存在,将会发生异常FileNotFoundException。
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader。
B调用读取流对象的read()方法。read():一次读一个字符,且会继续往下读。
第一种方式:读取单个字符。第二种方式:通过字符数组进行读取。
C调用close方法关闭流资源。
3)文本文件读取方式二
public int read(char[] cbuf) throws IOException
将字符读入数组。在某个输入可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。
返回:读取的字符数,如果已到达流的末尾,则返回 -1
注意:返回的是字符个数。通过字符数组进行读取
如果文件超大呢?缓冲区buf定义多少比较合适呢?通常我们定义的是1024的整数倍。
char[]buf=new char[1024];//2kb的数据。
两种读取方式,比较下来,这种方式比较好。
4)IO异常处理方式
凡是能和设备上发生数据操作时,都可能发生IO异常。
A FileWriter fw=new FileWriter(“demo.txt”);有可能发生读取文件不存在,空指针异常
B write()发生内存溢出等异常
C close()发生关闭资源失败等异常
因为这三条语句经常在一起,所以用一个try处理就可以,对应多个catch处理
一个完整的IO异常处理方式
1 |
FileWriter fw=null;try{ fw=new FileWriter(“demo.txt”); fw.write(“abcdefg”);}catch(IOException e){ System.out.println(e.toString())}finally{ try { if(fw!=null)//写到try上面,也没有问题。 fw.close(); } Catc h(IOException e) { System.out.println(e.toString()); }} |
如果我们开了5个文件流,记得,要一个一个关,不能用fw!=null||fw1!=null来判断,要一步一步来。
5)练习
读取一个.java文件,并打印到控制台上。
练习二
将C盘一个文本文件复制到D盘
三、IO流缓冲区技术
1、概述
缓冲区的优点是提高了流的读写效率,在流的基础上对流的功能进行了增强。很多软件都加载自己的缓冲区,如下载软件。
public class BufferedWriter extends Writer
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
查阅api,发现该类的构造函数没有空参数的,这是为什么?
我们知道,缓冲区的出现,是为了提高流的读写效率。所以,缓冲区产生之前,一定先有流。
2、缓冲技术原理:封装数组,将数据存入,再一次性取出。
3、写入流缓冲区BufferedWriter的步骤:
1)创建一个字符写入流对象。
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
2)为了提高字符写入流效率。加入缓冲技术。只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
如:BufferedWriter bufw =new BufferedWriter(fw);
3)调用write方法写入数据到指定文件
如:bufw.write("java come");
记住,只要用到缓冲区,就要记得刷新。(关闭流同样会刷新,但为了排除意外事故,保证数据存在,建议写入一次就刷新一次)
4)缓冲区是为提高效率的而创建的,真正调用资源的是FileWriter对象,所以其实关闭缓冲区,就是在关闭缓冲区中的流对象。
如:bufw.close();
BufferedWriter缓冲区中提供了一个跨平台的换行符:newLine();可以在不同操作系统上调用,用作数据换行。 newLine()函数里面就是,如果是windows系统,就是write(“\r\n”)如果是lunix系统,就是write(“\n”).
4、读取流缓冲区BufferedReader的步骤
public class BufferedReaderextends Reader
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
构造函数: BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。
1)创建一个读取流对象和文件相关联
如:FileReader fr=newFileReader("buf.txt");
2)为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲区对象的构造函数。
如:BufferedReader bufr=new BufferedReader(fr);
3)该缓冲区提供了一个一次读一行的方法readLine().方便于对文本数据的获取。当返回null时,表示读到文件末尾。返回的字符串不包含行终止符。
如:String str=bufr.readLine();
readLine方法的原理:无论是读一行,或者读取多个字符,其实最终都是在硬盘上一个一个读取,最终使用的都是read()一次读一个。
4)关闭流资源
要注意的是读取循环结束的条件。
练习
通过缓冲区复制一个.java文件
四、IO流——自定义缓冲区技术
1、实现原理
基于的是read方法。定义一个存储单位,然后,把一行数据存进去,满行后,再返回来。
明白了BufferedReader类中特有方法readLine的原理后,可以自定义一个类中包含一个功能和readLine一致的方法。来模拟一个BufferedReader
2、实现方法:
1)自定义一个缓冲区类
2)成员变量为FileReader对象。
3)自定义一个newline方法,用StringBuilder类实现。
3、练习
五、IO流——装饰设计模式
1、概述
通过简单模拟,我们发现readLine方法底层运用的还是read方法,它的出现是增强了read方法的功能。
BufferedReader就是对FileReader的增强
思想:
增强功能,是通过被增强的对象传递给增强类。
这种设计模式叫做装饰设计模式。
2、定义
装饰设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有对象的功能,并提供加强功能。那么,自定义的该类就称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
3、装饰和继承的区别
1)装饰模式比继承要灵活,避免了继承体系臃肿
2)降低了类与类之间的关系。
3)将继承结构转变为组合结构。
注意
装饰类因为增强已有对象,具备的功能和已有对象是相同的,只不过提供了更强的功能,所以,装饰类和被装饰类通常都属于一个体系中的。
4、自定义装饰类
我们定义的MyBufferedReader就是一个装饰类,现在定义的只能对一个方法进行包装。
那么我们要装饰一组类,怎么办?传进他们的父类Reader就可以了。
class MyBufferedReader extends Reader{ private Reader r; MyBufferedReader(Reader r) { this.r=r; }}
六、IO流——LineNumberReader
1、概述
BufferedReader的直接子类LineNumberReader
public class LineNumberReaderextends BufferedReader
跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
默认情况下,行编号从 0 开始。该行号随数据读取在每个行结束符处递增,并且可以通过调用 setLineNumber(int) 更改行号。但要注意的是,setLineNumber(int) 不会实际更改流中的当前位置;它只更改将由 getLineNumber() 返回的值。
可认为行在遇到以下符号之一时结束:换行符('\n')、回车符('\r')、回车后紧跟换行符。
也有BufferedReader有的方法readLine()read等等。
它也是一个包装类,或者叫做装饰类。
2、特有方法
setLineNumber(); 设置初始行号
getLineNumber(); 获取行号
3、练习
练习二:自定义带行号的缓冲区类