JAVA IO 输入输出流

Posted 一睡万古

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA IO 输入输出流相关的知识,希望对你有一定的参考价值。

1、概要

1)java IO 中常用的类:

image

在整个Java.io包中最重要的就是5个类和一个接口。

5个类指的是  File、OutputStream、InputStream、Writer、Reader;

一个接口指的是Serializable;

2)Java I/O主要包括如下几个层次,包含三个部分:

1.流式部分――IO的主体部分;

2.非流式部分――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;

3.其他类--文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。

3)主要的类如下:

     1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。

     2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。

     3. OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

     4.Reader(文件格式操作):抽象类,基于字符的输入操作。

     5. Writer(文件格式操作):抽象类,基于字符的输出操作。

     6. RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作

4)Java中IO流的体系结构如图:

image

5)Java流类的类结构图:

6)Java流类的类结构图:

    流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象<Thinking in Java>

    流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

    流的作用:为数据源和目的地建立一个输送通道。

    Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流.

7)Java IO所采用的模型 

    Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,您可以动态装配这些Stream,以便获得您需要的功能。

    例如,您需要一个具有缓冲的文件输入流,则应当组合使用FileInputStream和BufferedInputStream。

8)IO流的分类

    根据处理数据类型的不同分为:字符流和字节流

    根据数据流向不同分为:输入流和输出流

    按数据来源(去向)分类:

    1、File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
    2、byte[]:ByteArrayInputStream, ByteArrayOutputStream
    3、Char[]: CharArrayReader,CharArrayWriter
    4、String:StringBufferInputStream, StringReader, StringWriter
    5、网络数据流:InputStream,OutputStream, Reader, Writer

 

2、字符流和字节流

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:

字节流:数据流中最小的数据单元是字节。

字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

字符流的由来: Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。因字符一般占用两个字节,字符流方法一般都会有缓冲区以缓冲字符数据。字节流类的方法就不一定有缓冲区(如:FileInputStream/FileOutputStream)。

1-1)输入字节流InputStream

image

IO 中输入字节流的继承图可见上图,可以看出:

1.    InputStream是所有的输入字节流的父类,它是一个抽象类

2.    ByteArrayInputStream、StringBufferInputStream(上图的StreamBufferInputStream)(已废弃)、FileInputStream是三种基本的介质流,它们分别从Byte数组、StringBuffer、和本地文件中读取数据。

3.    PipedInputStream是从与其它线程共用的管道中读取数据.

4.    ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。

5.    I/O类库提供StringBufferInputStream类的本意是把字符串转换为字节流,然后进行读操作,但是在这个类的实现中仅仅使用了字符编码的低8位,不能把字符串中的所有字符(比如中文字符)正确转换为字节,因此这个类已经被废弃,取而代之的是StringReader类,该类已属于字符流范围。

image 

InputStream中的三个基本的读方法
      abstract int read() :读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。
      intread(byte[]?b) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
      intread(byte[]?b, int?off, int?len) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。

      流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时。

其它方法
      long skip(long?n):在输入流中跳过n个字节,并返回实际跳过的字节数。
      int available() :返回在不发生阻塞的情况下,可读取的字节数。
      void close() :关闭输入流,释放和这个流相关的系统资源。
      voidmark(int?readlimit) :在输入流的当前位置放置一个标记,如果读取的字节数多于readlimit设置的值,则流忽略这个标记。
      void reset() :返回到上一个标记。
      booleanmarkSupported() :测试当前流是否支持mark和reset方法。如果支持,返回true,否则返回false。

1-2)输出字节流OutputStream

image

IO 中输出字节流的继承图可见上图,可以看出:

1.    OutputStream是所有的输出字节流的父类,它是一个抽象类。

2.    ByteArrayOutputStream、FileOutputStream是两种基本的介质流,它们分别向Byte数组、和本地文件中写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据。

3.    ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。

image

outputStream中的三个基本的写方法
     abstract void write(int?b):往输出流中写入一个字节。
     void write(byte[]?b) :往输出流中写入数组b中的所有字节。
     void write(byte[]?b, int?off, int?len) :往输出流中写入数组b中从偏移量off开始的len个字节的数据。
其它方法
     void flush() :刷新输出流,强制缓冲区中的输出字节被写出。
     void close() :关闭输出流,释放和这个流相关的系统资源。

1-3)代码示例

  ByteArrayInputStream,FileInputStream区别在于操作对象不一样,前者操作byte数组,后者操作文件。read操作读byte数组、文件进入流,write操作写流数据进入byte数组、文件。

  这里以FileInputStream操作文件为例,且常用的也是FileInputStream类。

  前面讲FileInputStream,FileOutputStream是基本的介质流,用他们已经完全可以实现流的操作,其他子类(如bufferdOutStream )都是其装饰类,用之可有效率上的优化,不用亦可。

package com.javaTest.io;

import java.io.*;

public class IoTest {
    public static void main(String[] args) {
        File file = null;
        File ofile = null;
        InputStream in = null;
        OutputStream out = null;
        try {
            file = new File("D:\\\\test" + File.separator + "111.txt");
            ofile = new File("D:\\\\test" + File.separator + "222.txt");
            in = new FileInputStream(file);
            out = new FileOutputStream(ofile);
            Integer r = null;
            while ((r = in.read()) != -1) {
                out.write(r);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
View Code
2-1)字符输入流Reader

image

在上面的继承关系图中可以看出:

1.    Reader是所有的输入字符流的父类,它是一个抽象类。

2.    CharReader、StringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。PipedReader是从与其它线程共用的管道中读取数据。

3.    BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。

4.    FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。

5.    InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。我们可以从这个类中得到一定的技巧。Reader中各个类的用途和使用方法基本和InputStream中的类使用一致。后面会有Reader与InputStream的对应关系。

image

主要方法:

     (1) public int read() throws IOException; //读取一个字符,返回值为读取的字符

     (2) public int read(char cbuf[]) throws IOException; /*读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量*/
     (3) public abstract int read(char cbuf[],int off,int len) throws IOException; /*读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现*/

2-2)字符输出流Writer

image

在上面的关系图中可以看出:

1.    Writer是所有的输出字符流的父类,它是一个抽象类。

2.    CharArrayWriter、StringWriter是两种基本的介质流,它们分别向Char数组、String中写入数据。PipedWriter是向与其它线程共用的管道中写入数据,

3.    BufferedWriter是一个装饰器为Writer提供缓冲功能。

4.    PrintWriter和PrintStream极其类似,功能和使用也非常相似。

5.    OutputStreamWriter是OutputStream到Writer转换的桥梁,它的子类FileWriter其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream极其类似.

image

主要方法:

(1)  public void write(int c) throws IOException; //将整型值c的低16位写入输出流
(2)  public void write(char cbuf[]) throws IOException; //将字符数组cbuf[]写入输出流
(3)  public abstract void write(char cbuf[],int off,int len) throws IOException; //将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流
(4)  public void write(String str) throws IOException; //将字符串str中的字符写入输出流
(5)  public void write(String str,int off,int len) throws IOException; //将字符串str 中从索引off开始处的len个字符写入输出流

2-3)代码示例

由于文件编码的原因,以下代码输出的文件中文可能为乱码,要解决需要知道原文件编码并以字流输入,转化为字符流后可以以正确的编码方式输出。(代码见3、字符流和字节流的转换)

package com.javaTest.io;

import java.io.*;

public class IoTest3 {
    public static void main(String[] args) {
        Reader file = null;
        Writer ofile = null;
        try {
            file = new FileReader("D:\\\\test" + File.separator + "111.txt");
            ofile = new FileWriter("D:\\\\test" + File.separator + "222.txt");
            int r;
            while ((r = file.read()) != -1) {
                ofile.write(r);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
                ofile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

View Code

3、字符流和字节流的转换

1)转换流的特点:

1.    其是字符流和字节流之间的桥梁

2.    可对读取到的字节数据经过指定编码转换成字符

3.    可对读取到的字符数据经过指定编码转换成字节

2)何时使用转换流?

1.    当字节和字符之间有转换动作时;

2.    流操作的数据需要编码或解码时。

3)具体的对象体现:

转换流:在IO中还存在一类是转换流,将字节流转换为字符流,同时可以将字符流转化为字节流。

1.    InputStreamReader:字节到字符的桥梁

2.    OutputStreamWriter:字符到字节的桥梁

OutputStreamWriter(OutStreamout):将字节流以字符流输出。

InputStreamReader(InputStream in):将字节流以字符流输入。

这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来

4)字节流和字符流的区别

1.    读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

2.    处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。

5)代码示例

该段代码关键在读入文件时需要以正确的编码读入,测试时本机txt文件编码为GB2312,经测试以GB2312或GBK编码方式读入文件后输出UTF-8格式的文件中文均不会乱码。

网上搜到很多可以判断输入文件编码格式的java代码,可供使用,只是不知其原理,待日后探索

package com.javaTest.io;

import java.io.*;

public class IoTest2 {
    public static void main(String[] args) {
        File file = null;
        File ofile = null;
        InputStream in = null;
        OutputStream out = null;
        InputStreamReader reader = null;
        OutputStreamWriter writer = null;
        try {
            file = new File("D:\\\\test" + File.separator + "111.txt");
            ofile = new File("D:\\\\test" + File.separator + "222.txt");
            in = new FileInputStream(file);
            out = new FileOutputStream(ofile);
            reader = new InputStreamReader(in,"GB2312");
            writer = new OutputStreamWriter(out,"UTF-8");
            int r;
            while ((r = reader.read()) != -1) {
                writer.write(r);
                writer.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
                writer.close();
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

View Code

4、缓冲流(基本介质流的包装类)

1-1)字节缓冲流BufferedInputStream

BufferedInputStream继承于FilterInputStream,提供缓冲输入流功能。缓冲输入流相对于普通输入流的优势是,它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快。

1-2)方法介绍
BufferedInputStream

BufferedInputStream:字节缓冲输入流,提高了读取效率。

//构造方法
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
//下一字节是否可读
synchronized int available()
//关闭
void close()
//标记, readlimit为mark后最多可读取的字节数
synchronized void mark(int readlimit)
//是否支持mark, true
boolean markSupported()
//读取一个字节
synchronized int read()
//读取多个字节到b
synchronized int read(byte[] b, int off, int len)
//重置会mark位置
synchronized void reset()
//跳过n个字节
synchronized long skip(long n)
 
BufferedOutputStream

BufferedOutputStream:字节缓冲输出流,提高了写出效率。

 构造方法:
 // 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
 BufferedOutputStream(OutputStream out)
 // 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
 BufferedOutputStream(OutputStream out, int size)
 常用方法:
 // 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流
 void write(byte[] b, int off, int len)
 // 将指定的字节写入此缓冲的输出流
 void write(int b)
 // 刷新此缓冲的输出流
 void flush()
 

1-3)代码示例

package com.javaTest.io;

import java.io.*;

public class IoTest {
    public static void main(String[] args) {
        File file = null;
        File ofile = null;
        InputStream in = null;
        OutputStream out = null;
        BufferedInputStream input = null;
        BufferedOutputStream output = null;
        try {
            file = new File("D:\\\\test" + File.separator + "111.txt");
            ofile = new File("D:\\\\test" + File.separator + "222.txt");
            in = new FileInputStream(file);
            out = new FileOutputStream(ofile);
            input = new BufferedInputStream(in,1024);
            output = new BufferedOutputStream(out);
            Integer r = null;
            while ((r = input.read()) != -1) {
                output.write(r);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
                input.close();
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

View Code

2-1)字符缓冲流BufferedReader

BufferedReader

BufferedReader:字符缓冲流,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

构造方法:
    // 创建一个使用默认大小输入缓冲区的缓冲字符输入流
    BufferedReader(Reader in)
    // 创建一个使用指定大小输入缓冲区的缓冲字符输入流
    BufferedReader(Reader in, int sz)
 特有方法:
    // 读取一个文本行
    String readLine()
 
BufferedWriter

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

构造方法:
    // 创建一个使用默认大小输出缓冲区的缓冲字符输出流
    BufferedWriter(Writer out)
    // 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
    BufferedWriter(Writer out, int sz)
 特有方法:
    // 写入一个行分隔符
    void newLine() 
2-3)代码示例
package com.javaTest.io;

import java.io.*;

public class IoTest3 {
    public static void main(String[] args) {
        Reader file = null;
        Writer ofile = null;
        BufferedReader breader = null;
        BufferedWriter bwrite = null;
        try {
            file = new FileReader("D:\\\\test" + File.separator + "111.txt");
            ofile = new FileWriter("D:\\\\test" + File.separator + "222.txt");
            breader = new BufferedReader(file, 1024);
            bwrite = new BufferedWriter(ofile);
            int r;
            while ((r = breader.read()) != -1) {
                bwrite.write(r);
                bwrite.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
                ofile.close();
                breader.close();
                bwrite.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

View Code
3).FileReader、FileWriter
 FileReader:InputStreamReader类的直接子类,用来读取字符文件的便捷类,使用默认字符编码。
 FileWriter:OutputStreamWriter类的直接子类,用来写入字符文件的便捷类,使用默认字符编码。

5、非流式文件类--File类

image

从定义看,File类是Object的直接子类,同时它继承了Comparable接口可以进行数组的排序。

File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等,以下是文件操作常用的函数。

image

File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。

构造函数:
1)File (String   pathname)  
     例:File  f1=new File("FileTest1.txt"); //创建文件对象f1,f1所指的文件是在当前目录下创建的FileTest1.txt
2)File (String  parent  ,  String child)
     例:File f2=new  File(“D:\\\\dir1","FileTest2.txt") ;//  注意:D:\\\\dir1目录事先必须存在,否则异常
3)File (File    parent  , String child)
     例:File  f4=new File("\\\\dir3");
         File  f5=new File(f4,"FileTest5.txt");  //在如果 \\\\dir3目录不存在使用f4.mkdir()先创建

        一个对应于某磁盘文件或目录的File对象一经创建, 就可以通过调用它的方法来获得文件或目录的属性。   
       1)public boolean exists( ) 判断文件或目录是否存在
       2)public boolean isFile( ) 判断是文件还是目录
       3)public boolean isDirectory( ) 判断是文件还是目录
       4)public String getName( ) 返回文件名或目录名
       5)public String getPath( ) 返回文件或目录的路径。
       6)public long length( ) 获取文件的长度
       7)public String[ ] list ( ) 将目录中所有文件名保存在字符串数组中返回。
       File类中还定义了一些对文件或目录进行管理、操作的方法,常用的方法有:
       1) public boolean renameTo( File newFile );    重命名文件
       2) public void delete( );   删除文件
       3)  public boolean mkdir( ); 创建目录

6、RandomAccessFile类

image

这个是JDK上的截图,我们可以看到它的父类是Object,没有继承字节流、字符流家族中任何一个类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。

注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。

 

RandomAccessFile包含两个方法来操作文件记录指针:

  • long getFilePointer():返回文件记录指针的当前位置
  • void seek(long pos):将文件记录指针定位到pos位置(初始位置)

RandomAccessFile类在创建对象时,除了指定文件本身,还需要指定一个mode参数,该参数指定RandomAccessFile的访问模式,该参数有如下四个值:

  • r:以只读方式打开指定文件。如果试图对该RandomAccessFile指定的文件执行写入方法则会抛出IOException
  • rw:以读取、写入方式打开指定文件。如果该文件不存在,则尝试创建文件
  • rws:以读取、写入方式打开指定文件。相对于rw模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备,默认情形下(rw模式下),是使用buffer的,只有cache满的或者使用RandomAccessFile.close()关闭流的时候儿才真正的写到文件
  • rwd:与rws类似,只是仅对文件的内容同步更新到磁盘,而不修改文件的元数据

 

代码示例:

package com.javaTest.io;

import java.io.*;

public class IoTest4 {
    public static void main(String[] args) {
        File file = null;
        File ofile = null;
        RandomAccessFile acinfile = null;
        RandomAccessFile acoutfile = null;
        try {
            file = new File("D:\\\\test" + File.separator + "111.txt");
            ofile = new File("D:\\\\test" + File.separator + "222.txt");
            acinfile = new RandomAccessFile(file, "r");
            acoutfile = new RandomAccessFile(ofile, "rw");
            byte[] b = new byte[8];
            int len;
            int point = acinfile.getFilePointer();//获取文件读写位置
            acinfile.seek(8);//设置文件从第8个字节开始读写
            acoutfile.seek(0);//设置文件从第0个字节开始读写
            len = acinfile.read(b, 0, 8);//从读写位置开始读取文件8个字节
            acoutfile.write(b, 0, len);//从读写位置开始写入读取的字节数
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                acinfile.close();
                acoutfile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
View Code

 

内容来源:https://www.cnblogs.com/ylspace/p/8128112.html

以上是关于JAVA IO 输入输出流的主要内容,如果未能解决你的问题,请参考以下文章

java内存流:java.io.ByteArrayInputStreamjava.io.ByteArrayOutputStreamjava.io.CharArrayReaderjava.io(代码片段

使用java的输入,输出流将一个文本文件的内容按行读出,每读一行就顺序添加行号,并写入到另一个文件

Java IO中的数据输出流(DataOutputStream)和普通输出流(OutputStre

Java IO流-- JAVA流式输入/输出原理 & 输入输出流分类 & 节点流和处理流 & IO流总结

java中输入输出流如何把数据输出为Excel表格形式

java中输入输出流如何把数据输出为Excel表格形式