java IO详解


Java中IO操作主要是指使用Java进行输入,输出操作,Java中所有的操作类都存放在Java.io包中,在使用时需要导入此包。

   在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable.掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。

   一、java.io包中流的分类

     Java.io包中定义了多个流类型类实现输入输出的功能,从不同的角度可以分为:

  ~按照数据流方向的不同可以分为输入流输出流

  ~按照按照数据处理单位的不同可以分为字节流字符流

    J2Sdk所提供的所有流类型位于Java.io包内部分别都继承以下四种抽象流类型,如图:

     

 

       

二、Java中IO流的体系结构

  下面根据数据处理单位不同来分析Java中流的体系结构

   如图:

          

 

三、IO流中5大类的特征

    1、InputStream

    InputStream  为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能,此抽象类是表示字节输入流的所有类的超类。 继承自InputStream  的流都是向程序中输入数据的,且数据单位为字节(8bit);下面是InputStream所属的子类:    

    ~ FileInputStream : 从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。

package IO_TEST;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class File_test {
    public static void createFile(){
        File f = new File("/Users/zhengchao/cctv/File_test.txt"); 
        try {
            f.createNewFile();
        } catch (IOException ex) {
            Logger.getLogger(File_test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("文件名  "+f.getName());  //返回由此抽象路径名表示的文件或目录的名称。  
        System.out.println("文件父目录字符串 "+f.getParent());// 返回此抽象路径名父目录的路径名字符串 
        //f.delete();  //删除创建的文件
    }
    public static void main(String[] args){
        createFile();
    }
}

package IO_TEST;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

public class InputStream_test {
    public static void main(String[] args){
        int count = 0;
        InputStream streamReader = null;//文件输入流
        try {
            streamReader = new FileInputStream(new File("/Users/zhengchao/cctv/File_test.txt"));
            while(streamReader.read()!=-1) {  //读取文件字节,并递增指针到下一个字节  
             count++;  
          }  
          System.out.println("---长度是: "+count+" 字节");  
        } catch (IOException ex) {
            Logger.getLogger(InputStream_test.class.getName()).log(Level.SEVERE, null, ex);
        }finally{  
          try{  
             streamReader.close();//FileInputStream是有缓冲区的,所以用完之后必须关闭,否则可能导致内存占满,数据丢失。 
          }catch (IOException e) {  
          }  
      }  
    }
}

上面的程序存在问题是,每读取一个自己我都要去用到FileInputStream,我输出的结果是“---长度是: 64982 字节”,那么进行了64982次操作!可能想象如果文件十分庞大,这样的操作肯定会出大问题,所以引出了缓冲区的概念。可以将streamReader.read()改成streamReader.read(byte[]b)此方法读取的字节数目等于字节数组的长度,读取的数据被存储在字节数组中,返回读取的字节数。


  2、OutputStream

        为字节输出流,是整个IO包中字节输出流的最大父类,OutputStream类也是一个抽象类,要使用此类必须通过子类实例化对象。

   其子类有:

        

示例:

package IO_TEST;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class OutputStream_test {
    public static void main(String[] args) {  
        byte[] buffer=new byte[512];   //一次取出的字节数大小,缓冲区大小  
        int numberRead=0;  
        InputStream input=null;  
        OutputStream out =null;  
        try {  
           input = new FileInputStream("/Users/zhengchao/cctv/File_test.txt");  
           out = new FileOutputStream("/Users/zhengchao/cctv/File_test_out.txt"); //如果文件不存在会自动创建  

           while ((numberRead=input.read(buffer))!=-1) {  //numberRead的目的在于防止最后一次读取的字节小于buffer长度,  
              out.write(buffer, 0, numberRead);           //否则会自动被填充0  
           }  
        } catch (final IOException e) {  
            e.printStackTrace();  
        }finally{  
           try {  
              input.close();  
               out.close();  
           } catch (IOException e) {  
              e.printStackTrace();  
           }  
        }  
    }  
}



读写对象:ObjectInputStream 和ObjectOutputStream
package IO_TEST;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ObjetStream_test {  
   public static void main(String[] args) {  
      ObjectOutputStream objectwriter=null;  
      ObjectInputStream objectreader=null;  
       
      try {  
         objectwriter=new ObjectOutputStream(new FileOutputStream("/Users/zhengchao/cctv/File_objectwriter_out.txt"));  
         objectwriter.writeObject(new Student("gg", 22));  
         objectwriter.writeObject(new Student("tt", 18));  
         objectwriter.writeObject(new Student("rr", 17));  
         objectreader=new ObjectInputStream(new FileInputStream("/Users/zhengchao/cctv/File_objectwriter_out.txt"));  
         for (int i = 0; i < 3; i++) {  
            System.out.println(objectreader.readObject());  
         }  
      } catch (IOException | ClassNotFoundException e) {    
         e.printStackTrace();  
      }finally{  
         try {  
            objectreader.close();  
            objectwriter.close();  
         } catch (IOException e) {   
            e.printStackTrace();  
         }  
      }  
   }  
}  
class Student implements Serializable{  
   private String name;  
   private int age;  
   public Student(String name, int age) {  
      super();  
      this.name = name;  
      this.age = age;  
   }  
   @Override  
   public String toString() {  
      return "Student [name=" + name + ", age=" + age + "]";  
   }  
}  
Student类必须实现Serializable接口,并重写toString()方法,否则 objectwriter.writeObject(new Student("gg", 22)); 执行会报错。

SequenceInputStream:
        有些情况下,当我们需要从多个输入流中向程序读入数据。此时,可以使用合并流,将多个输入流合并成一个SequenceInputStream流对象。SequenceInputStream会将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 合并流的作用是将多个源合并合一个源。其可接收枚举类所封闭的多个字节流对象。
package IO_TEST;  
   
import java.io.*;  
import java.util.Enumeration;  
import java.util.Vector;  
   
public class NewClass {  
  /** 
   * @param args 
    *   SequenceInputStream合并流,将与之相连接的流集组合成一个输入流并从第一个输入流开始读取, 
    *   直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 
    *   合并流的作用是将多个源合并合一个源。可接收枚举类所封闭的多个字节流对象。 
   */  
  public static void main(String[] args) {  
     doSequence();  
  }  
   
  private static void doSequence() {  
     SequenceInputStream sis = null;  // 创建一个合并流的对象  
     BufferedOutputStream bos = null;   // 创建输出流。  
     try {  
        // 构建流集合。  
        Vector<InputStream> vector = new Vector<InputStream>();  
        vector.addElement(new FileInputStream("/Users/zhengchao/cctv/File_1.txt"));  
        vector.addElement(new FileInputStream("/Users/zhengchao/cctv/File_1.txt"));  
        Enumeration<InputStream> e = vector.elements();  
        sis = new SequenceInputStream(e);  
        bos = new BufferedOutputStream(new FileOutputStream("/Users/zhengchao/cctv/File_OUT.txt"));  
        // 读写数据  
        byte[] buf = new byte[1024];  
        int len = 0;  
        while ((len = sis.read(buf)) != -1) {  
           bos.write(buf, 0, len);  
           bos.flush();  
        }  
     } catch (FileNotFoundException e1) {  
        e1.printStackTrace();  
     } catch (IOException e1) {  
        e1.printStackTrace();  
     } finally {  
        try {  
           if (sis != null)  
              sis.close();  
        } catch (IOException e) {  
           e.printStackTrace();  
        }  
        try {  
           if (bos != null)  
              bos.close();  
        } catch (IOException e) {  
           e.printStackTrace();  
        }  
     }  
  }  
}  


 3、Writer

       写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 其子类如下:

   

    ~BufferedWriter   :

将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。


4、Reader

   用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 子类有:

      


示例:
1、使用BufferedReader和BufferedWriter 字符处理流实现文件复制
import java.io.*;  
class IODemo {  
    public static void main(String[] args){  
        try {  
        //使用BufferedReader和BufferedWriter进行文件复制(操作的是字符,以行为单位读入字符)  
        FileReader fr=new FileReader("a.txt");  
        BufferedReader br=new BufferedReader(fr);  
        FileWriter fw=new FileWriter("d.txt");  
        BufferedWriter bw=new BufferedWriter(fw);  
  
        String s=br.readLine();  
            while(null!=s) {  
                bw.write(s);  
                //由于BufferedReader的rendLIne()是不读入换行符的,所以写入换行时须用newLine()方法  
                bw.newLine();  
                //read=fis.read(b);  
                s=br.readLine();  
            }  
            br.close();  
            bw.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }   
    }  
}  




2、混合

/*   
管道流: 
PipedInputStream 
void connect(PipedOutputStream src)  
使此管道输入流连接到管道输出流 src 
 
PipedOutputStream 
void connect(PipedInputStream snk)   
 
在JDK我们看到PipedInputStream中有管道缓冲区,用来接收数据 
 
管道流内部在实现时还有大量的对同步数据的处理 
管道输出流和管道输入流执行时不能互相阻塞,所以一般要开启独立线程分别执行 
顺便复习了多线程操作 
 
[示例]:管道流 
*/  
import java.io.*;  
  
class Demo              
{  
  public static void main(String[] args)  throws Exception  
  {  
    PipedInputStream pin = new PipedInputStream();  
    PipedOutputStream pout = new PipedOutputStream();  
    pin.connect(pout);  //输入流与输出流连接  
      
    ReadThread readTh   = new ReadThread(pin);  
    WriteThread writeTh = new WriteThread(pout);  
    new Thread(readTh).start();  
    new Thread(writeTh).start();  
  }  
    
  public static void sop(Object obj) //打印  
  {  
    System.out.println(obj);  
  }  
}  
  
class ReadThread implements Runnable  
{  
  private PipedInputStream pin;  
  ReadThread(PipedInputStream pin)   //  
  {  
    this.pin=pin;  
  }  
    
  public void run() //由于必须要覆盖run方法,所以这里不能抛,只能try  
  {  
    try  
    {  
      sop("R:读取前没有数据,阻塞中...等待数据传过来再输出到控制台...");  
      byte[] buf = new byte[1024];  
      int len = pin.read(buf);  //read阻塞  
      sop("R:读取数据成功,阻塞解除...");  
        
      String s= new String(buf,0,len);  
      sop(s);    //将读取的数据流用字符串以字符串打印出来  
      pin.close();       
    }  
    catch(Exception e)  
    {  
      throw new RuntimeException("R:管道读取流失败!");  
    }     
  }  
    
  public static void sop(Object obj) //打印  
  {  
    System.out.println(obj);  
  }  
}  
  
class WriteThread implements Runnable  
{  
  private PipedOutputStream pout;  
  WriteThread(PipedOutputStream pout)  
  {  
    this.pout=  pout;  
  }  
    
  public void run()  
  {  
    try  
    {  
      sop("W:开始将数据写入:但等个5秒让我们观察...");  
      Thread.sleep(5000);  //释放cpu执行权5秒  
      pout.write("W: writePiped 数据...".getBytes());  //管道输出流  
      pout.close();  
    }  
    catch(Exception e)  
    {  
      throw new RuntimeException("W:WriteThread写入失败...");  
    }  
  }  
    
  public static void sop(Object obj) //打印  
  {  
    System.out.println(obj);  
  }  
}  

编译成功,class文件运行结果:
R:读取前没有数据,阻塞中...等待数据传过来再输出到控制台...
W:开始将数据写入:但等个5秒让我们观察...
R:读取数据成功,阻塞解除...
W: writePiped 数据...

3、来看一个做下载的例子:包含了哪些内容?
    @GET
    @Path("/download/settleLoan")
    public Response downloanSettleLoan(@QueryParam("loanStatus") LoanStatus status) throws IOException {
        
        String contentDisposition = "attachment; filename*=UTF-8''" + URLEncoder.encode(status.getKey().toString().concat("_借款列表"), "UTF-8") + ".csv";
        final PipedOutputStream output = new PipedOutputStream();
        PipedInputStream input = new PipedInputStream(output);

        Runnable writer = new Runnable() {
            @Override
            public void run() {
                try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output, "GBK"));) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("投标数").append(CSV_SEPERATOR)
                      .append("投标金额(元)");
                    bw.append(sb.toString());
                    bw.newLine();
                    for (int i = 0;i<10; i++ ) {
                        sb = new StringBuilder();
                        sb.append(String.valueOf(i)).append(CSV_SEPERATOR)
                          .append(String.valueOf(i*100000)); 
                        bw.append(sb.toString());
                        bw.newLine();
                    }
                    bw.flush();
                } catch (Exception ex) {
                    logger.error("Exception happened when write CSV for user list.", ex);
                }
            }
        };
        Thread thread = new Thread(writer);
        thread.start();
        return Response.ok(input, "text/csv").encoding("GBK").header("Content-Disposition", contentDisposition).build();
    }

我们看这段代码大的知识点:
1、new Runnable(): 多线程
2、BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output, "GBK"));:设计模式:应用了 装饰模式
3、管道流


其中,第二点: BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output, "GBK"));设计模式我们就不说了,我们讨论,字节流和字符流的转换。

BufferedWriter类封装了OutputStreamWriter类;

BufferedReader类封装了InputStreamReader类;

封装格式:

BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out));

BufferedReader in= new BufferedReader(new InputStreamReader(System.in);


1:字节输入流转换为字符输入流:

InputStreamReader是字节流向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符,它使用的字符集可以由名称指定或显示给定。根据InputStream的实例创建InputStreamReader的方法有4种:

InputStreamReader(InputStream in)//根据默认字符集创建

InputStreamReader(InputStream in,Charset cs)//使用给定字符集创建

InputStreamReader(InputStream in,CharsetDecoder dec)//使用给定字符集解码器创建

InputStreamReader(InputStream in,String charsetName)//使用指定字符集创建

2:字节输出流转换为字符输出流

OutputStreamWriter是字符流通向字节流的桥梁,它使用指定的charset将要写入流中的字符编码成字节,它使用的字符集可以由名称指定或显示给定,否则将接受默认的字符集:

根据根据InputStream的实例创建OutputStreamWriter的方法有4种:

OutputStreamWriter(outputstream out)//根据默认的字符集创建

OutputStreamWriter(outputstream out,charset cs)//使用给定的字符集创建

OutputStreamWriter(outputstream out,charsetDecoder dec)//使用组定字符集创建

OutputStreamWriter(outputstream out,String charsetName)//使用指定字符集创建


Java.io包中操作文件内容的主要有两大类:字节流、字符流,两类都分为输入和输出操作。在字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。(这四个都是抽象类)

java中提供了专用于输入输出功能的包Java.io,其中包括:
     InputStream,OutputStream,Reader,Writer
     InputStream 和OutputStream,两个是为字节流设计的,主要用来处理字节或二进制对象,
     Reader和 Writer.两个是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串.


字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点
     所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列

      字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以
       字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的 但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的 

==================我们还可以看到:============
Reader类的read()方法返回类型为int :作为整数读取的字符(占两个字节共16位),范围在 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1


inputStream的read()虽然也返回int,但由于此类是面向字节流的,一个字节占8个位,所以返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。因此对于不能用0-255来表示的值就得用字符流来读取!比如说汉字.










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值