您的位置:首页 > 职场人生

黑马程序员——java基础——IO流

2014-11-24 13:35 302 查看

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

一、IO基础知识

1、IO流概述

1)、IO,即输入输出,用于计算机内部或计算机之间,进行数据的传输。对于IO流的理解,可以通俗的把它想作一个蓄水池,如果一个待传输的文件为一个池子,那么要进行文件的传输的话,即水流要流动起来,这个与该文件关联的IO流,就是一个与水池连接的蓄水池,之后数据的存取都是在与这个流打交道,而不是直接对文件进行操作。

2)、IO流按字面意思(InputOutput)就可以知道,IO流分为两个派系,即输入流和输出流。注意输入输出都是相对应于机器内存而言的,而不是对于文件或其它对象而言的,比如想要将磁盘上的一个文件读取到内存中来,就要用一个输入流与之关联;相反要将内存中的数据写到磁盘文件中,则要用一个输出流关联一个文件,该文件可以存在或者不存在。以上两种派系是按照数据在计算机内的流向来区分的。

        另外,还有常用的一种分法—字节流与字符流。计算机中的数据都是以0、1字节码形式存在的,也就是说,都是字节数据,所以字符流底层也用到了字节流,而之所以这样区分,也是为了方便与实际的操作。字符流常用来操作文本数据,如各种语言文字类型的数据,这类数据的特殊之处就在于,它们涉及到了编码解码问题。当然这些文本数据无论在内存或者外存中还是以字节码形式存在的,只是在显示器上进行显示时,会将字节码拿去查编码表,编码表中存放的是字节码与相应的文本字符的映射。用字节码去查询编码表得到对应的文本字符,这个过程称之为解码,相应的反过来称之为编码。这里就编码问题就谈到这里,后面会有祥细的说明。

        字节流则可以关联任意的文件对象,如文本文件、图片文件、音乐文件以及视频文件等,它不涉及到编码问题。

2、java中常见的IO流

        java中操作IO流的类都存放在java.io包中。

1)、首先从超类进行流对象的学习,介绍java中字节流的两个超类—InputStream与OutputStream。

        它们为抽象类,对应了输入流和输出流。对具体文件的操作则由其子类实现,常见的有FileInputStream和FileOutputStream。这两个类可以关联磁盘中的文件数据。

        常用的方法有int read()与void write(int byte)方法,这两个方法是对文件数据一个一个字节进行操作,比如read方法调用一次就读取一个字节,再调用一次读取下一个字节,读取到文件末尾就返回-1。可见read方法底层操作的是一个字节数组,并且有一个指针在循环移动,因为该数组的大小往往小于文件大小,先读取这个数组的容量的文件数据后,数组指针就依次一个字节一个字节地返回,返回到数组末尾,就跳转回到0,并通知底层操作系统资源,再读取一个数组大小的文件数据,重复以上操作,直到读取到了个文件结束符为止。

        另外还有两个更常用的方法:int read(byte[] buf)与void write(byte[] buf)。第一个方法是将输入流中的数据读取到一个字节数组中,并返回读取到的字节数;第二个方法是将内存中的一个字节数组写出到文件中。原理是这样的,它们底层还是用到的是前面两个方法,就是一个字节一个字节进行操作的方式,只不过前者将读到的数据一个一个存放在内存中的一个字节数组中,后者将字节数组中的数组一个一个写到文件中而已。

最后

        下面来看一段图片文件的复制代码,就很清楚这两个字节流的使用了:

import java.io.*;
class DuplicatePic
{
public static void main(String[] args)
{
FileInputStream fis=null;
FileOutputStream fos=null;
try
{
fis=new FileInputStream("E:\\1.jpg");//E盘必须存在一个这样的图片文件,否则会出现FileNotFoundException异常
fos=new FileOutputStream("2.jpg");//输出到当前目录
byte[] buf=new byte[1024];//上面提到的内存中的字节数组,现在还没有数据
int len=0;
while((len=fis.read(buf))!=-1)//图片数据读到内存字节数组中
{
fos.write(buf,0,len);//将内存中的数据写到流内
}
}
catch (IOException e)
{
throw new RuntimeException("图片复制失败!");
}
finally
{
try
{
if(fos!=null)//由于可能发生错误,导致文件关联失败,所以先进行判断,若直接关流,可能发生空指针异常NullPointerException
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("输出流关闭失败!");
}
try
{
if(fis!=null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("输入流关闭失败!");
}
}
}
}


2)、字符流的两个超类Reader与Writer

        引入字符流是为了操作文本数据的方便,因为文本数据有一些特别的特性,比如文本数据有字符串的概念,文本文件中可以进行换行,这些是非文本数据不具备的。其实我们常用的字符串也涉及到了编码,读者可查看java.lang.String类中字符串的构造方法。所以只要涉及到字符显示,都会存在编码的问题。该类的学习可类比字节类的方式,常用的子类如FileReader与FileWriter。

        常用的方法有:int read()读入单个字符,int read(char[] buf)将字符读入数组;void write(int c)写入单个字符,void write(String buf)写入字符串。

        在这里要强调流的关闭了。流的关闭用void close()方法,因为java中,无论是流的读取还是写出,都是调用了底层系统资源,所以在读取或写出完结后,一定要注意关流,以释放系统资源。

        这里Writer类中有一个void flush()方法,该方法将该流的缓冲刷到流中,close在关闭流之前也有同样的操作,只是flush调用后,还可以继续对流进行操作,而close一旦调用流就关闭了,不能继续使用。

        另外FileWriter有一个特别的构造方法—FileWriter(String dir,boolean append)可以对文本文件进行续写,当append值为true时,就能续写。这里强调一点,对于读取流在关联文件时,文件必须存在,否则会出现文件无法找到异常                                             FileNotFoundException,而写出流则不同,文件是否存在均可,只是在关联输出流时,若文件不存在,会按文件字符串路径新建一个文件,若文件存在,则会覆盖。而FileWriter提供的文件续写功能,即使在文件存在时也不会覆盖,而是在文件末尾进行续写。

        这里是一个文本文件的复制的简单程序:

import java.io.*;
class FileDuplication
{
public static void main(String[] args)
{
// copyFile();
duplicateFile();
}
public static void copyFile()
{
FileReader fr=null;
FileWriter fw=null;
try
{
fr=new FileReader("E:\\1.txt");
fw=new FileWriter("2.txt");
int ch=0;
while((ch=fr.read())!=-1)
{
fw.write(ch);
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败!");
}
finally
{
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("输入流关闭失败!");
}
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("输入流关闭失败!");
}
}
}
public static void duplicateFile()
{
FileReader fr=null;
FileWriter fw=null;
try
{
fr=new FileReader("E:\\1.txt");
fw=new FileWriter("2.txt");
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(fr!=null)
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("输入流关闭失败!");
}
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("输出流关闭失败!");
}
}
}
}


        这里是文件续写的一段代码:

import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)
{
FileWriter fw=null;
try
{
fw=new FileWriter("1.txt",true);
fw.write("nihao\r\nhehe");//多次执行程序,观察结果
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println("finally:"+e.toString());
}
}
}
}


3)、缓冲字节流与缓冲字符流
        缓冲流的出现是为了提高读写的效率。从前面字节流与字符流的原理来看,它们都是采取单个字节或单个字符进行读写操作的,这种IO读写方式效率很低。比如说,上面的文件复制,底层过程是这样的,先从磁盘中读取一个字节到内存,然后将内存中的这个字节数据写到指定的磁盘目的中去,反复进行这一过程,也就是说,磁盘的磁头会在磁盘的两个不同的区域上反复进行跳转,这样的读写方式效率是很低的。而缓冲技术的出现,很好地提高了IO流的读写效率。在缓冲流中封装了一个数组,该数组在内存中,在文件的读取时,先读取该数组大小的数据到内存中,然后再将这些数据全都写入目的磁盘位置,也就是说,在读取磁盘时,读取了多个字节到内存后,然后将许多字节从内存中写到磁盘上,这样来提高读写效率的。

        在java中缓冲类有四个——BufferedInputStream、BufferedOutputStream与BufferedReader、BufferedWriter,它们都是InputStream、OutputStream与Reader、Writer的子类,故能使用父类中的常用方法。在这里介绍BufferedReader中一
d768
个常用的方法String readLine(),该方法用于读取文本数据的一行,在读取行的过程在,当读到行分隔符是就返回整个行数据,行分隔符在windows中用"\r\n"来表示,在Unix中用"\n"来表示。BufferedWriter中也有一个常用方法void
newLine()用于写入一个行分隔符,该方法具有跨平台性,推荐使用。

        缓冲流的出现是为了增强IO流的功能而存在的,在创建缓冲流对象时,要传入对应的IO流对象。

        参看下面的代码,MP3文件的复制,用到了缓冲流:

import java.io.*;
class DuplicateMP3
{
public static void main(String[] args)
{
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try
{
bis=new BufferedInputStream(new FileInputStream("E:\\1.mp3"));//传入相应的IO流对象
bos=new BufferedOutputStream(new FileOutputStream("2.mp3"));
int b=0;
while((b=bis.read())!=-1)
{
bos.write(b);
}
}
catch (IOException e)
{
throw new RuntimeException("媒体文件复制失败!");
}
finally
{
try
{
if(bis!=null)
bis.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败!");
}
try
{
if(bos!=null)
bos.close();
}
catch (IOException e)
{
throw new RuntimeException("写出流关闭失败!");
}
}
}
}


        利用缓冲流进行文本文件的复制也是同样的道理:

import java.io.*;
class DuplicateText
{
public static void main(String[] args)
{
BufferedReader bufr=null;
BufferedWriter bufw=null;
try
{
bufr=new BufferedReader(new FileReader("E:\\1.txt"));//注意这里的对应关系就行了
bufw=new BufferedWriter(new FileWriter("2.txt"));
String line="";
while((line=bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();//这一步是必须的,否则文本文件数据将不会换行
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败!");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("输入流关闭失败!");
}
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("输出流关闭失败!");
}
}
}
}

3、装饰设计模式

        装饰设计模式是java中众多设计模式中的一种,上面的缓冲流的设计就用到了装饰设计模式。我们在学了缓冲流之后,了解到缓冲流是为了增强其他流而存在的,无论是字节流还是字符流,都可以用缓冲流来进行加强。大家可以查看缓冲流的API,发现它与FileInputStream、FileReader等流是同一级的,而不是采用的我们熟知的继承体系。其实用缓冲流来继承这些流也是可以实现功能的,只是如果给每种IO流都创建一个子类来作为缓冲流,那java中IO体系就会显得非常臃肿,因为无论是对文件流的操作还是后面将学到的各种流对象,用缓冲流进行加强的原理都是一样的,所以没有必须为每种IO流都各自定义一个子类来实现其缓冲效果了。故只需定义一组缓冲流就可以了,当某种IO流需要提高输入输出效率时,在创建流对象时,用缓冲流进行包装就可以了。这样既实现了功能增强,又不至于合java中IO流体系过于复杂。这就是装饰设计模式的好处。

二、IO流中的一些典型的应用

1.自定义实现BufferedReader类

        该类在继承Reader类的基础上,实现一个常用功能:行的读取readLine()方法
        基本思想:调用底层read()方法一个一个字节读取,以换行符(/r/n)作为循环结束,用StringBuilder类进行一行字符序列的存储。值得注意的细节是对于结束行没有换行符的情况,即在读取行,进行循环的过程中,read()方法返回了-1,循环退出,而StringBuilder中存储的最后一行数据没能按一般情况进行返回。解决方法是在循环之后,对StringBuilder对象进行长度检查,若非空,则返回一次。
具体代码如下:

import java.io.*;
class MyBufferedReader extends Reader
{
private Reader r=null;
public MyBufferedReader(Reader r)
{
this.r=r;
}
public String myReadLine()throws IOException
{
StringBuilder sb=new StringBuilder();
int ch=0;
while((ch=r.read())!=-1)
{
if(ch=='\r')//判断换行符
continue;
if(ch=='\n')
return sb.toString();
sb.append((char)ch);
}
if(sb.length()!=0)//必须进行的判断,否则如果最后一行没有加换行符,则最后一行读取不到
return sb.toString();
return null;
}
public int read(char[] buf,int offset,int len)throws IOException
{
return r.read(buf,offset,len);
}
public void close()throws IOException
{
r.close();
}
}
class MyBufferedReaderDemo
{
public static void main(String[] args)
{
MyBufferedReader bufr=null;
try
{
bufr=new MyBufferedReader(new FileReader("C:\\1.txt"));
String line=null;
while((line=bufr.myReadLine())!=null)
{
System.out.println(line);
}
}
catch (IOException e)
{
throw new RuntimeException("文件读取失败!");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("输入流关闭失败!");
}
}
}
}


2.将.java文件绝对路径存储到清单

        这是一个比较有用的小程序,具体功能是将一个目录中,所有.java类型的文件,包括目录下的目录中的文件,将这些文件的绝对路径列成一个清单文件,以备日后进行查看。
        主要思想:先将这些符合条件的.java文件的文件对象用一个集合进行存储,其中用到了迭代,之后取集合中的文件,调用文件对象的getAbsolutePath()方法,将路径写到输出流中。

import java.io.*;
import java.util.*;
class JavaFileList
{
public static void main(String[] args)
{
File src=new File("G:\\Java实验资料\\java基础实验代码");
File dst=new File("G:\\Java实验资料\\javalist.txt");
List<File> fileList=new ArrayList<File>();
storeToList(src,fileList);
storeToFile(dst,fileList);
}
public static void storeToList(File src,List<File> fileList)//存储符合条件的文件到集合
{
if(src.isFile())
{
if(src.getName().endsWith(".java"))
fileList.add(src);
}
else
{
File[] files=src.listFiles();
for(File file:files)
{
storeToList(file,fileList);
}
}
}
public static void storeToFile(File dst,List<File> fileList)//将集合中的文件绝对路径写到一个清单文件中
{
BufferedWriter bufw=null;
try
{
bufw=new BufferedWriter(new FileWriter(dst));
for(File file:fileList)
{
bufw.write(file.getAbsolutePath());
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("文件信息存储失败!");
}
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写出流关闭失败!");
}
}
}
}


3.自定义BufferedInputStream

        BufferedInputStream类中存在缓冲区,具体是将硬盘中的数据批量读取到缓冲区中,以提高读取效率。
        本例主要介绍myRead()方法的原理:利用InputStream中的read(byte[]
buf)将硬盘中的数据读到缓冲区中后,接着读取缓冲区,其中两个比较重要的成员是count计数器与pos位置指针。count用于记录缓冲区中剩余数据数量的记录,若减为0,则重新调用read(byte[]
buf)方法。在取数据时count--,pos++,当count为0,即代表缓存中的数据全部输出时,pos指针也要进行重置为0。
        需要注意的一点是,BufferedInputStream中myRead()方法的返回值是int类型的,而不是byte类型的,初学者可能对此不解。这里为什么不是byte类型的,是因为为了与文件结束符-1进行区分。因为读取到的数据很可以是1111
  1111,8个1,即-1的补码,致使文件结束。为了避免这样的问题,首先将byte型提升为了int型,但此时原来有8个1的数据变成了32个1,1111-1111
  1111-1111  1111-1111   1111-1111   ,依旧是-1,最后将其与0000-0000  0000-0000  0000-0000    1111-1111,即255相与,至此问题得以解决。

import java.io.*;
class MyBufferedInputStream
{
private InputStream is;
private byte[] buf=new byte[1024];
private int count=0,pos=0;
public MyBufferedInputStream(InputStream is)
{
this.is=is;
}
public int myRead()throws IOException
{
if(count==0)
{
count=is.read(buf);
if(count==-1)
return -1;
pos=0;
}
byte b=buf[pos];
pos++;//位置指针处境
count--;//可读取字节总数减少
return b&255;//必须进行的操作,将字节数据提升为int型
}
public void myClose()throws IOException
{
is.close();
}
}
class MyBufferedInputStreamDemo
{
public static void main(String[] args)
{
MyBufferedInputStream bis=null;
BufferedOutputStream bos=null;
try
{
bis=new MyBufferedInputStream(new FileInputStream("C:\\1.mp3"));
bos=new BufferedOutputStream(new FileOutputStream("2.mp3"));
int b=0;
while((b=bis.myRead())!=-1)
{
bos.write(b);
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败!");
}
finally
{
try
{
if(bis!=null)
bis.myClose();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败!");
}
try
{
if(bos!=null)
bos.close();
}
catch (IOException e)
{
throw new RuntimeException("写出流关闭失败!");
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: