IO流用来处理设备之间的数据传输
Java对数据的操作时通过流的方式
Java用于操作流的对象都在io包中
流按操作数据分为两种方式:字节流和字符流
流按流向分为:输入流和输出流
字节流的抽象基类:
InputStream, OutputStream
字符流的抽象基类:
Reader,Writer
既然IO流是用于操作数据的,那么数据最常见体现形式是:文件
需求:在硬盘上,创建一个文件并写入一些文字数据
找到一个专门用于操作文件的Writer子类对象,FileWriter。后缀名是父类名,前缀名是该流对象的功能。
public static void main(String[] args) {
try {
//创建FileWriter对象,该对象一被初始化就必须明确被操作的文件
//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖
//其实该步就是在明确数据要存放的目的地
FileWriter fw = new FileWriter("e:/kk.txt");
//调用write方法,将字符串写入到流中
fw.write('c');
fw.write("abc");
//刷新流对象中的缓冲中的数据
//将数据刷到目的地中
fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据
//将数据刷到目的地中
//和的flush区别:flush刷新后,流可以继续使用,close刷新会将流资源关闭
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
如果希望对文件的写入是续写的方式,可以用这种如下方式创建流对象:
FileWriter fw = new FileWriter("e:/kk.txt", true);
Linux下换行用的是\n,而windows下用的是\r\n,如果写入数据时用\n来换行记事本不识别。
public static void main(String[] args) throws IOException {
//创建一个文件读取流对象,和指定名称的文件相关联
//要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
FileReader fr = new FileReader("e:/kk.txt");
//调用读取流对象的read方法
//read();一次读取一个字符,而且会自动往下读
//int read(char[] arr);将一堆数据读入字符数组中,并返回读取的数据个数
char[] arr = new char[512];
int len = 0;
//循环读取数据
while((len=fr.read(arr))!=-1){
System.out.println(new String(arr, 0, len));
}
//关闭流资源
fr.close();
}
练习1:
读取一个.java文件,并打印在控制台上
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("e:/hello.java");
char[] buf = new char[1024];
int num = 0;
while((num=fr.read(buf))!=-1){
//换行格式就会混乱
System.out.print(new String(buf, 0, num));
}
fr.close();
}
练习2:
将C盘一个文本文件复制到D盘
方式一:读一个字符,写一个字符
public static void main(String[] args) throws IOException {
//创建输入流,与源文件相关联
FileReader fr = new FileReader("c:/hello.java");
//创建输出流,与目的文件相关联
FileWriter fw = new FileWriter("d:/mm.java");
int c;
while((c=fr.read())!=-1){
fw.write(c);
}
fw.close();
fr.close();
方式二:先将字符读入到数组中,在将数组写入文件中,再继续读,直到文件末尾
public static void main(String[] args) throws IOException {
//创建输入流,与源文件相关联
FileReader fr = new FileReader("c:/hello.java");
//创建输出流,与目的文件相关联
FileWriter fw = new FileWriter("d:/mm.java");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1){
fw.write(buf, 0, len);
}
fw.close();
fr.close();
}
方式二的读写效率比方式一的效率高
带缓冲区的读写流:BufferedReader和BufferedWriter
缓冲区的出现是为了提高流操作效率而出现的,所以在创建缓冲区之前,必须要先有流对象。
字符写入流缓冲区提供了跨平台的换行符
字符写入缓冲流示例:
public static void main(String[] args) throws IOException {
//创建字符写入流
FileWriter fw = new FileWriter("e:/kk.txt");
//为了提高字符写入流的效率,加入了缓冲技术
//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可
BufferedWriter bw = new BufferedWriter(fw);
bw.write("hahahh");
//跨平台的换行符
bw.newLine();
bw.write("hello");
bw.flush();
//关闭缓冲区就是关闭缓冲区中的流对象
bw.close();
}
字符读取流缓冲区提供了一个一次读取一行数据的方法,当返回空时,文件已读到末尾
字符读取缓冲流示例:
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("buf.txt");
BufferedReader br = new BufferedReader(fr);
String line = null;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
}
读一行,获取多个字符,其实最终都是在硬盘上一个一个读取,所以最终使用的还是read方法一次读一个的方法
模拟BufferedReader类中特有方法readLine,代码如下
class MyBufferedReader2{
private Reader r;
public MyBufferedReader2(Reader r) {
this.r = r;
}
public String readLine()throws IOException{
//定义临时容器,原BufferedReader封装的是字符数组
StringBuilder sb = new StringBuilder();
int c;
while((c=r.read())!=-1){
if(c=='\r')
continue;
else if(c=='\n'){
//遇到换行符就返回一行数据
return sb.toString();
}
sb.append((char)c);
}
if(sb.length()!=0)
return sb.toString();
//没有数据就返回空
return null;
}
public void close()throws IOException{
if(r!=null)
r.close();
}
}
这里用到了装饰设计模式
装设设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能,
那么自定义的该类称为装饰类。
装饰类通常会通过构造方法结束被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
装饰和继承的区别
继承
MyReader 专门用于读取数据的类
|--MyTextReader
|--MyBufferedTextReader
|--MyMediaReader
|--MyBufferedMediaReader
|--MyDataReader
|--MyBufferedDataReader
装饰
找到其参数的共同类型,通过多态的形式,可以提供扩展性
class MyBufferedReader extends MyReader{
MyBufferedReader(MyReader mr){
}
}
装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低类与类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能,
所以装饰类和被装饰类通常都属于一个体系中。