一、IO流概述
Java中IO流的分类:
流按流向分为:输入流、输出流
流按操作数据分为:字节流、字符流
字符流的由来:字符流的出现是为了方便处理文本。
通用字节流,字符流基于字节流,字符流的对象中融合了编码表,只有文字是它的编码,想处理图片用字节流。
IO流常用的有四个基类,其中字节流有两个抽象基类:InputStream和OutputStream,字符流有两个抽象基类:Reader和Writer
(注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如InputStream的子类FileInputStream,Reader的子类FileReader。)
字符编码演变过程:ASCII码——>gb2312——>gbk——>unicode——>UTF-8
unicode中所有的符号都用两个二进制码表示,一个汉字在不同的码表中都能识别,但是它的编码是不同的,所以当我们用gbk的码表存储一些数据,而在另一台采用UTF-8的机器上进行读取,就会出现乱码的情况。为了解决这一问题,java当中在流技术就基于基于字节流产生了字符流,字符流的好处是在内部可以融合编码表,这样我们处理文字是就会很方便。
字符流的对象中融合了编码表,只有文字是它的编码,如果想要处理图片,则要用字节流,字节流是通用流,字符流基于字节流。
文件是IO操作最常用的对象。
二、字符流
(1)写文件
小细节:观看父类的直接子类,可以看出如下规律:子类的后缀是父类名,前缀是流对象的功能,例如BufferWrite中后缀Writer是父类名,前缀Buffer是其功能——缓冲。
FileWrite的构造函数中没有空参数的构造函数,这是因为
创建FileWriter对象,对象一旦被创建就必须明确被操作的文件,且该文件会被创建到指定目录下,如果该目录中有同名文件,则被覆盖。
创建对象:FileWriter fw=new FileWriter("demo.txt");
写数据:fw.write("abcde"); 注意:次方法只是将数据写到了内存中即流中,此时目标文件依旧是空的。
为了将流中的数据写到目标文件中,有两种方法:flush()和close(),他们都通过刷新缓存中的数据将数据写入目标文件。这两种方法虽然都能达到预期的效果,但是二者是有差别的:flush刷新之后,流可以继续使用,而close刷新后,流将会关闭
关闭文件的重要性:java本身是不能网硬盘中写数据的,java之所以能写是因为调用了系统内部的资源,使用完了,就需要释放这些资源,不然随着资源的不断占用,将会出现内存不足的情况,这就是close()方法的重要性,每次对文件操作完之后,都需要关闭文件。
(2)数据的续写
public FileWriter(String fileName,boolean append) throws IOException方法可以实现在原有文件中添加内容的需求。方法中的append参数是一个boolean值,当它为true是则表示将数据写入文件末尾处。
在windows的软件中,换行符是由两个字符表示的“\r\n”,在Linux操作系统中,换行符使用“\n”是可以实现的,但是newLine( )是跨平台的,无论是在Linux还是Windows中,都可以实现换行,但是newLine()方法是缓冲区提供的,只有用到缓冲区,才可以使用newLine()方法进行换行。
(3)读数据
读数据的两种方法:
1)读取单个字符read()。该方法的返回值是一个整数,范围在0~65535之间,如果已达到流的末尾,则返回-1,所以可以利用“读到末尾返回-1”这一点作为循环条件,即只有((ch = fr.read())!=-1才继续读取。
import java.io.*;
class FileReaderDemo{ public static void main(String[] args) throws IOException{
//创建一个文件读取流对象,和指定名称的文件相关联
//要保证该文件已经存在,如果不存在会发生异常
FileReader fr = new FileReader("demo.txt"); //读取流的对象方法
//int ch = fr.read(); //System.out.println((char)ch);
//read():一次读一个字符,而且会自动往下读 int ch = 0;
while((ch = fr.read())!=-1){
System.out.println((char)ch);
} fr.close();
}
}
2)将字符读入数组read(char[ ] cbuf)。该方法的返回值是读取字符的个数,当读到末尾是,则返回-1。在使用数组是会出现一个问题就是当字符的个数不是数组长度的整数倍时,最后一次读入数据的个数与显示的结果不一致,这是我们就可以用另外一种方法read(char[ ] cbuf,int off,int len)这样我们不仅可以指定读数据的起始位置,还可以指定读入数据的长度。还有一个常识是:数组的长度通常设置为1024的倍数。
import java.io.*;
class FileReaderDemo2{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("demo.txt");
char[] buf = new char[3]; int num=0;
while((num = fr.read(buf))!=-1) {
System.out.println("num="+num+"....."+ new String(buf,0,num));
}
fr.close();
}
}
总结:两种读数据方法的比较:第一种是读一个数据就打印一次,而第二种是现将读取的数据存放到缓冲区内,达到一定数量之后“一次性”输出,所以,相比较而言,第二种读数据的方法比较好。
(4)IO异常
IO异常是IO体系中最常见的异常。无论是创建流对象,读写数据还是关闭文件,都会抛出异常,总之凡是能和设备上的资源发生关系的都会发生IO异常。
下例是标准的IO异常处理程序,在这里我们需要注意一下几点:
(1)是fw.close()的处理,在调用close()方法之前,一定要对文件是否存在进行判断,即在fw!=null时,才能进行关闭操作。
(2)在try{}catch{}finally{}块中,所有必须执行的操作都要放在Finally块中。
import java.io.*;
class FileWriterDemo2
{
public static void main(String[] args){
FileWriter fw = null;
try
{
fw = new FileWriter("demo.txt",true);
fw.write("abcdefg"); }
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally {
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
} }
}
(5)复制文件(例如:将C盘的一个文件复制到D盘中)
原理:将C盘的一个文件中的数据存储到D盘的一个文件中
步骤:
(1)在D盘中创建一个文件,用于存放C盘中的数据
(2)创建一个流对象,和C盘的文件进行关联
(3)通过不断的读写完成文件的存储
(4)关闭资源
import java.io.*
class FileCopy{
public static void main(String[ ] args) {
copy();
}
public static void copy(){
FileWriter fw = null;
FileReader fr = null;
try{
fw = new FileWriter("Demo_Copy.txt");
fr = new FileReader("Demo.txt");
char[ ] buf = new char[1024];
int len = 0;
while((len = fr.read(buf))!=-1){
fw.r.write(buf,0,len);
}
catch(IOException e){
throws new RuntimeException("读写失败");
}
finally{
try{
if(fr!=null)
fr.close();
}
catch(IOException e){
}
try{
if(fw!=null)
fw.close();
}
catch(IOException e){
}
}
}
}
}
(6)字符流的缓冲区
字符流的缓冲区对应的类是:BufferReader和BufferWriter 。缓冲区要结合流才可以使用,在流的基础上对流的功能进行增强——提高对数据的读写效率。
由于缓冲区是为了提高操作效率而出现的,所以在创建缓冲区之前,必须现有流对象。
缓冲技术的原理是:对象中封装了数组。
BufferedWriter:该流对象是为了提高写的效率而产生的,其中它所提供的newLine()方法,可以实现跨平台性。
BufferedReader:该流对象是为了提高读取效率而产生的,该对象中有一个readLine()方法,可以一次读一行。
三、字节流
字节流是通用操作流,不仅可以读写文字,还可以读写图片、各类媒体文件等等。
字节流的基类是InputStream和OutputStream 。
注:在java API中,凡是类名的后缀中带有Stream的都是字节流。
InputStream中的available()方法,对于读取数据是什么有用的,使用这个方法,我们不经可以确定存放数据所需缓冲区数组的大小,而且也不用利用“读到文件末尾,返回-1”这个条件来进行判断了,只需直接将数组中的内容转换成字符串输出即可:System.out.println(new String(buf)) 。但是如果我们操作的是媒体文件,且比较大,这时则不建议用available(),原因是为了避免内存溢出,这时将数组缓冲区定义为1024的整数倍比较好。
字节流同样有缓冲对象,他们是BufferedInputStream和BufferedOutputStream,他们的功能与字符流中的BufferedReader和BufferedWriter是相似的。
四:转换流
InputStreamReader:字节转字符
OutputStreamWriter:字符转字节
转换流什么时候用:转换流是字节与字符之间的桥梁,通常涉及到字符编码转换时,需要用到字符流。
转换流中,常用的两句话
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));这句是从键盘录入数据最常用的写法。
BufferedWriter bufw=new BufferedWriter(new OutputStreamReader(System.Out));
五、总结
流操作的基本规律:
由于流对象很多,我们有时会不知道改用那一个,现在通过如“三个明确”规律,帮助我们选择合适的对象。
(1)明确源和目的
源:输入流。InputStream ,Reader
目的:输出流。OutputStream,Writer
(2)明确操作的数据是不是纯文本
是:字符流
不是:字节流
(3)当体系明确后,再确定需要使用那个具体的对象,这一点通过设备来区分:
源:内存,硬盘,键盘。
目的:内存,硬盘,控制台。
注意:有时根据前两点所确定出来的流对象,和根据第三点确定出来的流对象是不同的,这时,我们就要用到转换流来帮我们转换成更适合的对象。