IO流
概述
流的特点
IO流用于处理设备之间的数据传输;
java对数据的操作是通过流的方式;
java用于操作留的对象都放在IO包中;
流按操作数据分为两种:字节流和字符流;
按流向分为:输入流和输出流;
字符流对相融合了编码表。
流常用基类
字节流的抽象基类:输入流InputStream,输出流OutputStream;
字符流的抽象基类:输入流Reader,输出流Writer。
由这四个类派生出的子类名称都是以其父类名作为子类名作为子类名作为后缀。
如:InputStream的子类FileInputStream;
Reader的子类FileReader;
对数据的操作分为读和写。
字符流
概述
其实早期IO包中出现的都是字节流,因为无论是硬盘还是内存上的数据都是以字节形式体现的,在我们操作数据时,数据分了很多种,比如说电影,Mp3这些数据,字节流都能进行读取,但是对于文本数据,为了方便对这种数据的处理,单独分离出了字符流,字符流对象中融合了编码表,只有文字涉及到编码,所以在处理文本文件时,我们选择用字符流。
既然IO流是用于操作数据的,数据最常见的体现形式就是文件,对数据的操作:读和写。
文本文件的写入
首先,我们先看字符流对文本的写入操作,用到的对象:FileWriter,它是Writer的子类。
操作文本文件来演示字符流写入方式:在演示时加入对IO异常的处理方式。
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)throws IOException
{
//在外边建立引用,在try里建立对象
FileWriter fw = null;
try
{
//FileWriter fw = new FileWriter("Demo.txt");这是在try语句中new建立的对象;
//fw = new FileWriter("k:\\Demo.txt");//k盘不存在,初始化抛出异常,这个对象没创建成功,对象就不存在,fw为null;
fw = new FileWriter("Demo.txt");//这样就是在当前目录下创建文件;
fw.write("gfsdfs");
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally
{
try
{
if(fw!=null)//fw不等于null,才有关闭流的可能;一定要对此进行判断
fw.close();//,关闭流,并刷新,fw为空,就不能调用close()方法,所以在上边要判断一下fw是否为null;
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
}
}
}
文件的续写:我们发现通过这种方式写入数据,如果该目录中已存在我们要写如数据的文件,那么,每次都会把原文件中的数据覆盖掉,现在,想要不覆盖文件,对文件进行续写,通过查阅API我们发现,FileWriter有一个构造函数:FileWriter(String fileName, boolean append),我们可以根据给定的文件名,以及指示,决定是否附加写入数据的boolean值来构造FileWriter对象。
通过一个例子来演示:
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)throws IOException
{
FileWriter fw = null;
try
{
fw = new FileWriter("Demo.txt",true);//将数据写到文件的末尾处,传递一个true代表不覆盖已有文件;
fw.write("\r\nahdasda");
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
}
}
}
文本文件的读取
流对象几乎都是对应的,有读就有写,对文件的读取我们需要用到的对象是:FileReader,它是Reader的子类。
字符流读取的方式很多,可以一次读一个字符,也可以定义一个数组,将每一次独到的数据放到数组中,然后在读数组中的字符,我们用一个例子来进行演示:
import java.io.*;
class FileReaderDemo
{
public static void main(String[] args)throws IOException
{
readerDemo_2();
}
public static void readerDemo_1()throws IOException//读取字符流方法一
{
//建立读取流对象,和要操作的文件相关联;要保证该文件存在,否则会抛出异常;
FileReader fr = new FileReader("Demo.txt");
int len = 0;
while((len=fr.read())!=-1)//read方法返回的是字符的编码值;当读到文件结尾时会返回-1
{
System.out.println("len="+(char)len);
}
fr.close();
}
public static void readerDemo_2()throws IOException//读取字符流方法二
{
FileReader fr = new FileReader("Demo.txt");
//把读到的数据存到一个char类型的数组中
char[] ch = new char[1024];
int len = 0;
while((len=fr.read(ch))!=-1)
{
System.out.print(new String(ch,0,len));//用println的话,如果文件大小超过数组大小的话,就会在数组装满一次后。下次在读就换行了
}
}
}
练习:拷贝文本文件。
拷贝的原理:其实就是将一个文件中的数据存到另一个文件中去。
步骤:1,创建一个文件,用于存储读取到的数据;
2,定义读取流和要拷贝的文件相关联;
3,通过不断的读写完成存储操作;
4,关闭资源。
import java.io.*;
class FileCopyDemo
{
public static void main(String[] args)throws IOException
{
fileReadWrite_1();
}
public static void fileReadWrite_1()throws IOException//复制文件,方法一,读一个字符写一个
{
FileWriter fw = new FileWriter("IODemo_copy.java");
FileReader fr = new FileReader("IODemo.java");
int len = 0;
while((len=fr.read())!=-1)
{
fw.write(len);//将读到的字符循环的写到目的文件中;
}
fw.close();
fr.close();
}
public static void fileReadWrite_2()//复制文件,方法二,将读取到的内容存到数组中,一次性写入
{
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("IODemo_copy.java");
fr = new FileReader("IODemo.java");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len);//将读到的数据存到数组中,一次性写入目的文件;
}
}
catch (IOException e)
{
throw new RuntimeException("读取写入失败");
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭写入流失败");
}
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭读取流失败");
}
}
}
}
文件拷贝图例:
字符流的缓冲区
字符流的缓冲区提高了对数据的读写效率。
对应的类:BufferedReader和BufferedWriter。
缓冲区的特点
1,缓冲区要结合流才可以使用;
2,在流的基础上对流的功能进行了增强;
3,缓冲区的出现是为了提高流的效率,所以在创建缓冲区之前,要先有流对象。
缓冲区中的方法
newLine():换行,跨平台的,原理是:write("\r\n");
readLine():一次读一行,它的原理:定义一个容器,读到换行符,就将数据返回,不返回换行符。
演示缓冲区的使用:
import java.io.*;
class FileCopyDemo
{
public static void main(String[] args)throws IOException
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
bufr = new BufferedReader(new FileReader("IODemo.java"));
bufw = new BufferedWriter(new FileWriter("IODemo_copy.java"));
String line = null;
while((line=bufr.readLine())!=null)//readLine方法只返回回车符以前的内容,并不返回回车符
{
bufw.write(line);//将读到的一行写出去;
bufw.newLine();//换行;
bufw.flush();//注意:用到缓冲区一定要记得刷新;
}
}
catch (IOException e)
{
throw new RuntimeException("读写文件失败");
}
finally
{
try
{
if(bufw!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭读取流失败");
}
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭写入流失败");
}
}
}
}
自定义缓冲区
明白了ReadLine方法的原理,可以自定义一个类中包含一个功能和readLine一样的方法,来模拟以下BufferedReader。
class MyBufferedReader extends Reader//做一个跟BufferedReader功能一样的类,继承Reader类,和要被装饰的类处在一个体系中。
{
private Reader r;//private FileReader fr;
MyBufferedReader(Reader r)//(FileReader fr)
{
this.r = r;
//this.fr = fr;
}
public String myReadLine()throws IOException//readLine方法的原理就是这个
{
//定义一个临时容器,BufferedReader定义的是一个数组
//为了演示方便,就用StringBuilder容器
StringBuilder sb = new StringBuilder();
int len = 0;
while((len=r.read())!=-1)
{
if(len=='\r')//读到'\r'就结束本次循环,继续下一次循环;
continue;
else if(len=='\n')//读到'\n'就将数据转换成字符串;
return sb.toString();
else
sb.append((char)len);
}
if(sb.length()!=0)//代表缓冲区里有数据,这种情况是防止出现最后一行没有回车符;
return sb.toString();
return null;//readLine方法返回的是null,循环完,没数据了就返回空
}
public int read(char[] cbuf, int off, int len)throws IOException//复写Reader类中的这个抽象方法
{
return r.read(cbuf,off,len);
}
public void close()throws IOException//还有这个抽象方法
{
r.close();
}
}
通过对这个缓冲区的模拟,我们发现,缓冲区的出现增强了已有对象的功能,这就是另一种设计模式:装饰设计模式。
装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类,并传入已有对象,对于已有的功能,提供更强的功能,那么自定义的该类就称为装饰类,装饰类通常会通过构造方法接受被装饰的对象,并基于被装饰的对象,提供更强的功能。