以前写程序的时候,对于IO流感觉有点不爽,估计很多学者都有同样的感觉,原因很简单,操作一个IO流,涉及到的类有点多,而且很多时刻都有点晕,所以也一直对它认识都不深刻。经过一段时间的专门学习,我把自己的所得和大家分享下,希望能对大家有点帮助。说前我得感谢黑马的老师,你们讲的内容很详细,很靠谱。最主要的是易懂。
在介绍之前,让我们先看下输出流和输入流以及程序、操作系统、硬盘之间的关系:
在IO流中,我们是以程序为参考对象定义输入输出。即如果数据是从程序输出到硬盘上,我们称之为输出流。反之,称之为输入流。至于数据是怎么通过操作系统接口到硬盘上,这些底层的东西我们就不必去研究,这些工作JAVA已经为我们封装好了。
在流中我们根据操作的最小单位,把它们分为字节流和字符流,换而言之,如果我们所输出的数据是以字节为单位,就称之为字节流。如果是以字符为单位,就称之为字符流。字节流一般用于操作媒体文件,比如说mp3,视频。字符流一般用于操作文本文件。
首先,我从字节流开始,当然字节流分为输入流和输出流,但不管操作的对象是目标是什么,它们分别继承InputStream和OutputStream.下面分别看下它们的类体系结构
操作字节的输入输出流的类体系结构图如上所示,我们在平时会经常进行对文件进行操作;所以我们先分析FileInputStream和FileOutputStream
操作流对象的基本步骤下:
1、创建流对象,把它和目标文件相关联;
2、确定自己的操作,选择对应的方法;
3、关闭数据流;(必要的,否则可能会造成数据丢失);
现在看具体介绍:
FileInputStream:文件输入流对象,用于读取本地文件中的字节数据;
看Read()方法的几个用法:
public static void Read_1()throws IOException{
FileInputStream fs=new FileInputStream("E:/sd.txt");//与目标文件相关相关联,如果目标文件不存在,则抛出IOException异常
int num=0;
while((num=fs.read())!=-1){//一个字节一个字节的读取
System.out.println((char)num);
}
fs.close();
}
无参的Read方法返回的是所读取的字节,每次只能读取一个,并且他返回的是32位的,即Int类型,当读取完文件中的内容是,它返回-1,表示操作完成。
带参数的Read()
public static void Read_2()throws IOException{
FileInputStream fs=new FileInputStream("E:/tttt.txt");
int num=0;
byte[]buf=new byte[1024];//以字节数组作为缓冲区
while((num=fs.read(buf))!=-1){
System.out.println(new String(buf));
}
fs.close();
}
read(byte[]b)接受的是一个字符数组,把所读取的内容返回到数组里,进行缓冲,然后在将数组里的内容显示到控制台上,此种方法与上一种比读取效率更高;
public static void Read_3()throws IOException{
FileInputStream fs=new FileInputStream("E:/tttt.txt");
int num=fs.available();//返回的是目标文件中的字符个数,也是InputStream类中所特有的方法
byte[]buf=new byte[num];
fs.read(buf);//此种方法虽然简化了程序,提高了效率,但是用法不安全,容易造成内存溢出。
System.out.println(new String(buf));
fs.close();
}
记住:操作完数据以后,必须进行流关闭。
其实原理很简单:就是把目标文件里的内容读入到输入流中,通过输入流进入内存里的程序,在显示到控制台,但是它是一个字节一个字节的读取,效率有点低。
还有一个Read重载方法:read(byte[]b,int off,int length);这个方法其实都和上面带参数的方法基本一致,不同的是它把规定特定的一个字符数组空间来存储读取的内容;
FileOutputStream类,用法基本一致,用于写入诸如图像数据之类的原始字节的流
其方法如下:
基本和文件输入流对象一致
public static void Writer_1()throws IOException{
FileOutputStream fos=new FileOutputStream("e:/sd.txt");//如果文件不存在,则创建它。如果存在,则覆盖他。
fos.write("sdfsdf".getBytes());//getBytes()使用平台的默认字符集将此 String
编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
/*
* 此方法与FileWriter方法有区别:在不刷新缓冲区情况下,他直接将数据写入文件;因为他操作的是字节数据,不需要转化;
* FileWriter虽然操作的是字符,但其底层还是操作的是字节,在把数据写入缓冲区/内存是,虚拟机会将相应的字符和一张码表对应(本机器的是GBK的,可以通过System.getProperties()方法查看)
* 然后将相应的字符转化为相应的字节,所以会在内存中驻留,需用flush刷新该缓冲区,将其写入文件
*/
fos.close();
}
将内容从内存一个字节一个字节的写入到目标文件中;两个最长用的字节流对象基本上就是如此;下面介绍字符流对象
字符流:前面说过就是说数据最小传输的单位是字符,其实,本质上而言,它还是字节流传输。它的原理就是,当它从文件里读写数据时,会把所读写的数据存放到缓冲区里,然后再把缓冲区里的字符数据和一张相应的码表相对应,取出所对应的编码,换成字节数据,然后在传入到流中,经过流到达目标文件。所以操作这类字符数据,其读写能力和字节流相比就会显得弱势一点。FileReader和FileWriter都是使用的默认编码,想看自己虚拟机的默认编码可以同个System.properties()查询,具体的操作大家可以研究下,不难。我的机器是GBK的。还是先介绍文件操作流对象FileWriter和FileReader
FileReader:用于以字符为单位读取文本文件。
看下其方法:
大多数方法和字节流对象一致,但不同的是Read()重载的参数是字符数组
import java.io.*;
public class FileReaderDemo {
/**
* @param args
*/注意try-catch的常规应用形式
public static void main(String[] args) {
// TODO Auto-generated method stub
FileReader fw=null;//定义在try块的外面
try{
fw=new FileReader("D:\\Workspaces\\MyEclipse 10\\Day18\\src\\FileReaderDemo.java");
int num=0;
int reads=0;
//while((reads=fw.read())!=-1)System.out.print((char)reads);//不带参数的read方法,返回的是所读取字符的ASCII码
char []a=new char[1024];
while((num=fw.read(a))!=-1){//调用的是带字符数组参数的read方法,返回的是所读取的字符个数;相比前一种,此种效率更快;
System.out.print(new String(a,0,num));
}
}catch(IOException e){
e.printStackTrace();//默认情况是把异常打印到控制台上
}finally{
try{
if(fw!=null)fw.close();//关闭流
}
catch(IOException e){
e.printStackTrace();
}
}
}
}
FileWriter类的基本方法如下:
基本操作:
/*
* 字符流和字节李流
* 字节流的两个基类:
* InputStream OutputStream
* 字符流的两个基类:
* Reader Writer
* 先学习字符流的特点
* 既然IO流是用于操作数据的
* 那么数据的最常用体现形式是:文件
* 需求:在硬盘上,创建一个文件写入一些文字数据
* 找到一个专门用于操作文件的Writer子类,FileWriter 前缀名为改流对象的功能,
* 后缀名为所属类别
*/
import java.io.*;
public class FileWriterDemo {
/**
* @param args
*/
public static void main(String[] args) throws IOException{
// TODO Auto-generated method stub
//创建一个FileWriter对象,该对象一被初始化就必须要明确要被操作的文件
//而且该文件会被创建到指定的目录下,如果该目录下有同名文件,则覆盖它;如果不想覆盖原来内容,可以在创建流对象时向构造函数在加一个参数true。
//其实该步就是要明确存放数据的目的地
FileWriter fw=new FileWriter("x.txt");
fw.write("abc");
//fw.close();
fw.flush();//刷新,把缓冲里面的数据刷新到目标文件中,如果没有这步,将无法写入数据;
fw.close();
}
}
Write()的重载方法很多,可以写入字符,字符数组,字符串等,但写入单个字符时,其参数是int类型,写完后别忘了刷新,其实忘了不打紧,close()方法本身就带有刷新功能,它是等把全部数据都读取完后,才进行刷新,所以为了不必要的麻烦,还是在每次读了之后都调用下flush()方法,把数据写入目标文件;
看下FileWriter和FileReader两者一起的应用
原理用图表示下:
代码如下
import java.io.*;
public class CopyText {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
FileReader fr=null;
FileWriter fw=null;
try{
fr=new FileReader("D:\\Workspaces\\MyEclipse 10\\Day18\\src\\FileReaderDemo.java");
fw=new FileWriter("e:/x.txt");
int length=0;
char[]buff=new char[1024];
while((length=fr.read(buff))!=-1)
fw.write(buff,0,length);
}catch(IOException e)
{
throw new RuntimeException("读取失败!");
}finally{
try{
if(fr!=null)fr.close();
}catch(IOException e){
throw new RuntimeException("读取流关闭失败!");
}
finally{
try
{
if(fw!=null)fw.close();
}catch(IOException e){
throw new RuntimeException("写入流关闭失败!");
}
}
}
}
}
现在基本把这常用的几种介绍完了,我将在深入IO流中详细介绍有关IO流的更多知识。