(7) - IO流 (图)

---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------


一、IO流概述


IO流即输入(Input)输出(Output)流,流是种形象的说法,计算机数据(字节或字符)的转移都是大量、持续的,像水流般。输入指将制定位置的数据读入内存中,输出指将内存数据写到指定的位置上。那么IO流是用来处理设备间的数据传输的。

在java中:

(1)   对数据的操作是通过流的方式。

(2)   用于操作流的对象都在IO包中。

(3)   流按操作数据分为两种:字节流与字符流

(4)   流按流向分为:输入流和输出流。

 

IO流操作的异常处理:

由于IO流存在各方面的不确定,需要异常处理机制的支持,在常规处理时都建议采用捕获方式,下面IO流知识点的例子,为了突出知识点,简化了异常的处理,采用抛出方式,以便能使代码清晰些,常规处理应采用下列格式:

/*
IO异常的处理方式
*/
import java.io.*;
 
class IoExceptionDemo
{
public static void main(String[] args)
{
        //在外面建立引用对象,在try中构造初始化
        //colse也要求处理IO异常,所以也的进行try、
        //即3个方法都要try处理
        //如构造时抛出异常,将中断,但会执行finally中的
        //fw.close,因为构造失败fw=null,调用失败
        //需在前进行健壮性的判断,if(fw != null)
 
        FileWriter fw = null;
        try
        {
               fw =new FileWriter("Demo.txt");
 
               //write将内容写入流缓存中,并未写入文件中
               fw.write("写入文本内容");
 
               //加flush刷新后,流中内容写入文件中,但流并未关闭,可继续写
               fw.flush();
        }
        catch (IOException e)
        {
               System.out.println(e.toString());
        }
        finally
        {
               try
               {
                      //这步要有,安全判断
                      //因fw =new FileWriter("Demo.txt");可能会失败,比如在不存在的K盘下
                      if(fw != null)
                             fw.close();      //先刷新,后关闭流,不能再写入数据了
               }
               catch (IOException e)
               {
                      System.out.println(e.toString());
               }
        }
        System.out.println("文件创建成功!");
}
}

二、字符流

       在进行数据操作时,操作数据以字符为单元,该类体系为Reader和Writer。

1、 Writer和Reader

Writer为写入字符流的抽象类,其子类后缀名基本是Writer。其子类必须要实现父类的抽象方法为write(char[], int,int)、flush() 和 close()。当我们向文件write数据时,数据仅存在缓冲中,需用flush刷新后数据才从缓冲写入文件中。Close之前会进行一次缓冲的刷新。其实jvm是在调用系统内部的读写方法,因为系统会不同,jvm如都用自已的方式写入数据是不合适的。

 

Reader为读入字符流的抽象类,其子类后缀名基本是Reader,必须要复写的方法有read(char[],int, int) 和 close()。


2、 BufferedWriter和BufferedReader

为了提高对数据的读写效率,IO中定义了缓冲区,可以说是一个过渡区,将陆续的数据先写入缓冲区积攒,而后一股脑写入目标设备。就像我们写word,写一段后保存,而不是写一个字保存一下。所以不建议采用,读一字符,写一字符的方式,因为这使磁头读写操作频繁,建议先读一次性入流中,再将流数据写入目的文件中。    最后关闭读写流

特点:

①缓冲区的技术原理其实就是封装了数组。

②它的出现是为了提高流的操作效率存在的,所以在创建时,构造函数必须传入一个流对象。

③调用close关闭缓冲区时,就会关闭缓冲区的流对象,所以关闭了缓冲区就不需要写流对象关闭了。

④BufferedWriter和BufferedReader的类构造采用了装饰设计模式,对流的操作类进行了功能的加强,至于装饰模式,将在以后介绍。

 

BufferedWriter:字符流输出的缓冲区,提供了一个跨平台的换行方法newLine(),解决在不同系统中换行符不统一的问题。在向目标输出前记得先flush()刷新。

 

BufferedReader:字符流输入的缓冲区,同样提供了一个readLine方法,从文件中读取一行数据,返回给String,当读到文件尾时返回空,其本质还是用read一个一个字符读取的,只不过积攒一行返回。该类有个LineNumberReader子类,比BufferedReader多出行号的追踪功能。方法有setlineNumber();设置行号getReadLineNumber();读出行号

 

3、 InputStreamReader和OutputStreamWriter

转换流:由于某些需求,需要在字符流和字节流间互相转换,java便将该操作封装成类,以供需求,如他们的子类FileWriter和FileReader,我们直接可以用其子类进行字符的操作,而不必去考虑复杂的字节。

 

InputStreamReader为字节流转换成字符流,这样便可利用字节流的BufferedReader缓冲区操作提高效率,要求在创建时向构造函数传入InputStream的字节流对象,也可以用构造函数的Charset参数指定编码格式。InputStreamReader构造函数中传入System.in流参数,便可接手键盘录入数据。

 

OutputStreamWriter字符流转换成字节流,同理,只不过参数输入改输出,如接收System.out打印在控制台。

 

4、 FileWriter与FileReader

他们分别属于InputStreamReader和OutputStreamWriter的子类,专门对字符数据的文件进行操作。可接收路径字串和File对象等。

 

FileWriter:用来写入字符文件的便捷类,有默认的字符编码和缓冲区。可进行对文本的写入和文本的追加,追加文本的构造函数只要将参数2用上,初始成true即可,写入前需刷新。创建对象时,该类会打开指定文件,若文件已打开则创建失败。

 

FileReader:用于读取字符文件的便捷类,同样具有默认编码和缓冲区。由read()读取单个字符,返回一个整数,读到文件尾返回-1。

 

IO流操作会抛出异常,需处理,以应对文件已打开或文件不存在等异常,异常处理稍后介绍。下面是1、2、3的一个综合例子:将键盘录入,写入文件中。

                     

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
 
/*
 * 需求:将键盘录入写入文件中
 *
 * 说明这里不重点体现异常处理,所以简单的抛出了
 * 正规开发,建议用try进行捕获
 * */
public class CopyText
{
                     //由于异常处理不是这里要体现的,所以采取抛出异常,正常编码建议捕捉
                     public static voidmain(String[] args) throws IOException
                     {
                            //标准的键盘录入方式,将输入转字符交给缓冲区。
                            BufferedReader bufr= new BufferedReader(new InputStreamReader(System.in));
                           
                            //FileWriter读取字符文件,交给缓冲,会抛出异常,简单抛出了,平时建议捕捉
                            BufferedWriter bufw= new BufferedWriter(new FileWriter("Test.txt"));
                           
                            String strLine =null;
                            while((strLine =(bufr.readLine()))!=null)
                            {
                                   //输入over时,结束
                                   if("over".equals(strLine))
                                          break;
                                   //写入文本
                                   bufw.write(strLine);
                                   bufw.newLine();//换行
                                   bufw.flush();//刷新
                            }
                            bufr.close();
                            bufw.close();
                     }
}

5、 CharArrayReader和CharArrayWriter

提供在缓冲区和内存间的操作方式,用于操作字符数组,内部拥有可自动延长的内存空间,由于是操作内存,最后不需关闭。

CharArrayReader,需接收一个字符数组型的数据源,将内存的数据以数组的方式存入传入的数组源中,无需关闭。

CharArrayWriter,同理。


6、 PrintWriter

向文本输出流打印对象的格式化表示形式,为其他字符流类添加了输出功能,在构建时,将构造函数参数2置true,还可带自动刷新功能,利用println()能实现换行,等于代替了上述代码中的 bufw.write(strLine);bufw.newLine();bufw.flush();三句。

构造函数能接收的文件源可以是,File对象,字符串路径,字节输出流。

 

7、 PipedWriter和PipedReader

管道流,它具有将一个程序的输出当做另一个程序的输入的能力,这个涉及线程,也就是我们线程通信的另一种方式,利用管道流通信,它需要利用connect将两者连接,即:读.connect(写)。

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
 
/*
 * 字符管道流的测试*/
class Read implements Runnable
{
private PipedReader pr;
Read(PipedReader pr)
{
        this.pr = pr;
}
public void run()
{
        try
        {
               //构建接收数组
               char[] chs = new char[1024];
               //读取连接的写入管道流,多句用循环
               int len = pr.read(chs);
               System.out.println(newString(chs,0,len));
               pr.close();
        }catch(Exception e)
        {
               throw new RuntimeException("读取失败!");
        }
}
}
class Write implements Runnable
{
private PipedWriter pw;
Write(PipedWriter pw)
{
        this.pw = pw;
}
public void run()
{
        try
        {
               //直接写出
               pw.write("这是管道流数据!");
              //多句要pw.flush()刷新,这里再close前自动刷新了                    
              pw.close();
        }catch(Exception e)
        {
               throw new RuntimeException("写入失败!");
        }
}
}
public class PipedDemo
{
public static void main(String[] args) throws IOException
{
      PipedWriter pw = new PipedWriter();
      PipedReader pr = new PipedReader();
     //需连接,读的连接写的
     pr.connect(pw);
     new Thread(new Read(pr)).start();
     new Thread(new Write(pw)).start();
}
}


8、RandomAccessFile

该类不算是IO体系中子类,而是直接继承自Object。随机访问文件,自身具备读写方法。通过skipBytes(int x),seek(int x)来达到随机访问,即含有文件指针,可跳跃。

但是它是java.IO包中成员,因为它具备读和写功能,

它内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置。同时可以通过seek改变指针的位置。

其实完成读写的原理就是内部封装了字节输入流和输出流。

其构造函数只能接收文件对象,而且可以指定模式,如’r’,只读。

 

局限性:通过构造函数可已看出,该类只能操作文件。而且操作文件要用模式。

      

三、字节流

在进行数据操作时,操作数据以字节为单元,该类体系为InputStream和OutputStream,字节流的输出是直接从内存写入,不需要转字符的过程,因此没有字符流刷新的过程。


1、InputStream和OutputStream

InputStream为写出字节流的抽象类,其子类后缀名基本是InputStream。提供read()方法。

OutputStream为读入字节流的抽象类,其子类后缀名基本为OutputStream。提供write()方法。


2、FileOutputStream和FileInputStream

分别为InputStream和OutputStream的子类,复写了read()方法,主要用于读取诸如图像数据之类的原始字节流。没有缓冲区,自己构建byte类型的数组。

FileOutputStream:以字节流的形式输出到文件中,复写了write方法,一次写入一个字节,若要已向文件结尾处追加字节,在创建时的构造函数中初始第二个参数为true。

 

FileInputStream:将字节流读入到内存中,复写了read方法,每次只能读一个字节,返回一个int型数据,当读到文件尾时返回-1。他有个特有的方法available(),返回文件剩余的字节数。如果文件较小,可以先读出大小,声明足够的byte数组,不用循环,但是文件不能过大,虚拟机不可能开辟很大的内存空间,比如视频文件,new一个数组不会成功,所以应视情况使用available()。

             

其格式类似与FileWriter与FileReader,在这里不写例子了,稍作改动即可,将字符读入改为字节流的byte,不用刷新等。


3、ByteArrayOutputStream与ByteArrayInputStream

       与字符流的CharArrayReader和CharArrayWriter同理,只不过数据源为byte数组。


4、PipedOutputStream与PipedInputStream

与字符流的PipedReader和PipedWriter同理,只不过一切按字节流的操作方式。


5、ObjectInputStream与ObjectOutputStream

对象序列化流,用于对象的序列化(持久化),即能将对象存入硬盘中,保存对象和对象数据。由ObjectOutputStream将java对象的基本数据类型和图形写入OutputStream(流)中,使用ObjectOutputStream可读取重构该对象。被操作的对象所属类必须实现Serializable接口,该接口没有要复写的方法,我们叫这种类型的接口为标记接口。实现该接口,其实就是给每给要保存的类一个ID号,简称UID,若要保存对象的类不实现该接口使用对象序列化流,将会报NotserializableException异常。

默认对象序列化后,不能修改其类,因为序列化文件中记录的UID变化了,使用时或报错,为防止修改类时,保存文件变化UID而导致不可用,我们采用自定义UID,

如public static final long srrialVersionUID = 42L;

静态成员不会被序列化,他再方法区,而对象在堆中。若不想某成员序列化,加tansient修饰该成员。

这两个类必须成对使用,用ObjectOutputStream写,就用ObjectInputStream。

如下例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectStreamDemo
{
  public static void main(String[] args) throws Exception
  {
         writeObject();
         readObject();
  }
 
  public static void writeObject() throwsException
  {
         //这里为简化代码,异常省略捕捉处理
         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
         //写入一个Person对象
         oos.writeObject(new Person("张三"));
         oos.close();
  }
  public static void readObject() throws Exception
  {
         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
         //从文件中读取序列化的对象
         Person p = (Person)ois.readObject();
         System.out.println(p);
         ois.close();
  }
}
//要序列化的对象类,必须向实现Serializable
classPerson  implements Serializable
{
  //自定义一个标记号,防止修改二影响序列化的对象不可用。
  public static final long serialVersionUID =42L;
  private String name=null;
  Person(String name)
  {
         this.name = name;
  }
  public String toString()
  {
         return "姓名:" + name;
  }
}

6、SequenceInputStream

     SequenceInputStream对多个流进行合并。从第一个流,读完,再接着读取第二流,依次下去。可以将多个流的操作数据写入到一个目标中。比如将三个文件的数据写入到一个文件中。构造函数中传入Enumeration对象,由Enumeration保存多个流对象。Enumeration时旧版本中Vector的类型。

     先准备三个文件1.txt,2.txt,3.txt,将内容合并到4.txt中如下例子:

    

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
 
public class SequenceInputStreamDemo
{
     //这里为代码简练省略捕捉处理异常
     public static void main(String[] args)throws IOException
     {
       //由于SequenceInputStream的构造函数要传Enumeration类型,这里用Vector
       Vector<FileInputStream> vct = new Vector<FileInputStream>();
      
       //准备合并的三个文件
       vct.add(new FileInputStream("1.txt"));
       vct.add(new FileInputStream("2.txt"));
       vct.add(new FileInputStream("3.txt"));
      
       //获取三个文件的流对象
       Enumeration<FileInputStream> en =vct.elements();
       //合并
       SequenceInputStream sis = new SequenceInputStream(en);
      
       //合并后写入另一个文件
       FileOutputStream fos = new FileOutputStream("4.txt");
      
       byte[] buf = new byte[1024];
      
       int len = 0;
       while((len = sis.read(buf))!=-1)
       {
              fos.write(buf,0,len);
       }
       fos.close();
       sis.close();
     }
}

 

切割文件:

利用自定义byte[]数组限制读入大小,将文件分批存入多个新建的文件中。

 但是在切割较大的视频的文件时,就不能new过大的byte数组了,

应定义1024*1024(1M)大小的数组向一文件存入数据,再采用一计数器记录装入了多少数据,达到要求时,再新建一文件,继续装入下一个。无太多技术要求,纯算法,这里不再举例。

 

8、 FilterInputStream与FilterInputStream的子类

(1)   BufferedInputStream与BufferedInputStream

功能与字符流的BufferedReader与BufferedWriter缓冲区类似,早构造时可以指定缓冲大小

下面写一个拷贝图片的例子:

import java.io.*;
 
public class CopyPicDemo
{
//简化代码,没做异常的捕捉
public static void main(String[] args) throws IOException
{
        //字节流缓冲区
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("2.jpg"));
       
        int by = 0;;
        while((by = bis.read())!=-1)
        {
               bos.write(by);
        }
        bis.close();
        bos.close();
}
}


(2)   DataOutputStream与DataIntputStream

可以对基本数据类型输入输出,构造时需传入流对象,DataOutputStream使用writeInt(int)、writeBoolen(bool)等方法向文本写入基本数据类型,用DataIntputStream的readInt()、readBoolean()等方法读出,有严格的字节顺序,读错顺序后面的数据将不能正常读出。

(3)   PrintStream

类似于字符流的PrintWriter类。它为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。与其他输出流不同,PrintStream 永远不会抛出IOException;而是,异常情况仅设置可通过checkError 方法测试的内部标志,通过checkError()方法获得。


---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值