详解 Java I/O(笔记)

什么是I/O?

IO(输入/输出)是比较抽象的,看不到明显的运行效果,但输入和输出是所有程 序都必需的部分。使用输入机制,允许程序读取外部数据(包括来自磁盘、光盘等存 储设备的数据、来自网络的数据)、用户输入数据;使用输出机制,允许程序记录运 行状态,将程序数据输出到磁盘、光盘等存储设备中和网络其他设备中。

Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入流、输 出流两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字 节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。除此之 外Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流, 其中节点流用于和底层的物理存储节点直接关联(不同的物理节点获取节点流的方式 可能存在一定的差异),但程序可以把不同的物理节点流包装成统一的处理流,从而 允许程序使用统一的输入、输出代码来读取不同的物理存储节点的资源。

File 类

File类是java.io包下代表与平台无关的文件和目录的类。在程序中操作文件和目 录,都可以通过File类来完成。需要注意的是,不管是文件还是目录都是使用File来操 作的,File能新建、删除、重命名文件和目录,但是File不能访问文件内容本身。如果 需要访问文件内容本身,则需要使用输入/输出流。

构造器:

构造器 说明
File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实 例。
File(String parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实 例。
File(File parent, String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实 例。
File(URI uri) 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。

常用方法:

分类 返回值 方法名 说明
访问名称与路径相关 String getName() 返回由此抽象路径名表示的文件或目录的名 称。
  String getPath() 将此抽象路径名转换为一个路径名字符串。
  String getParent() 返回此抽象路径名父目录的路径名字符串; 如果此路径名没有指定父目录,则返回 null。
  File getParentFile() 返回此抽象路径名父目录的抽象路径名;如 果此路径名没有指定父目录,则返回 null。
  File getAbsoluteFile() 返回此抽象路径名的绝对路径名形式。
  String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
检测相关方法 boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
  boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。
  boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。
  boolean canExecute() 测试应用程序是否可以执行此抽象路径名表示的文件。
  boolean isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。
  boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。
  boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目 录。
  boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。
获取文件信息 long length() 返回由此抽象路径名表示的文件的长度。
  long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。
操作相关 boolean createNewFile() 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
  static File createTempFile(String prefix, String suffix) 在默认临时文件目录中创建一个空文件,使 用给定前缀和后缀生成其名称。
  static File createTempFile(String prefix, String suffix, File directory) 在指定目录中创建一个新的空文件,使用给 定的前缀和后缀字符串生成其名称。
  boolean delete() 删除此抽象路径名表示的文件或目录。
  void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表 示的文件或目录。
  boolean renameTo(File dest) 重新命名此抽象路径名表示的文件。
  boolean mkdir() 创建此抽象路径名指定的目录。
  String[] list() 返回一个字符串数组,这些字符串指定此抽 象路径名表示的目录中的文件和目录。
  String[] list(FilenameFilter filter) 返回一个字符串数组,这些字符串指定此抽 象路径名表示的目录中满足指定过滤器的文 件和目录。
  File[] listFiles() 返回一个抽象路径名数组,这些路径名表示 此抽象路径名表示的目录中的文件。
  File[] listFiles(FileFilter filter) 返回抽象路径名数组,这些路径名表示此抽 象路径名表示的目录中满足指定过滤器的文 件和目录。
  File[] listFiles(FilenameFilter filter) 返回抽象路径名数组,这些路径名表示此抽 象路径名表示的目录中满足指定过滤器的文 件和目录。
  static File[] listRoots() 列出可用的文件系统根

遍历目录例子:

import java.io.File;

public class FileDemo {
    public static void main(String[] args) {
        File file = new File("文件路径");
        printDirectory(file,0);
    }
    public static void printDirectory(File file,int count){
        if (file.exists()){
            StringBuffer buffer=new StringBuffer();
            if (count!=0) {
                buffer.append("|");
            }
            for (int i = 0; i < count; i++) {
                buffer.append("‐‐");
            }
            if (file.isDirectory()){
                System.out.println(buffer.toString()+"["+file.getName()+"]");
                File[] files=file.listFiles();
                for (File file2:files) {
                    printDirectory(file2,count+1);
                }
            }else{
                System.out.println(buffer.toString()+">"+file.getName());
            }
        }
    }
}

I/O流

Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在 Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述 为“流”(stream),通过流的方式允许Java程序使用相同的方式来访问不同的输入输出源。 stream是从起源(source)到接收(sink)的有序数据。 Java把所有传统的流类型(类或抽象类)都放在java.io包中,用以实现输入输出功能。

流的分类

  1. 输入流和输出流

    按照流的流向来分,可以分为输入流和输出流。输入、输出都是从程序运行所在 内存的角度来划分的。

    • 输入流:只能从中读取数据,而不能向其写入数据。由 InputStreamReader 作为基类

    • 输出流:只能向其写入数据,而不能从中读取数据。由 OutputStream Writer 作为基类

  2. 字节流和字符流

    字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单 元不同。

    • 字节流操作的数据单元是8位的字节,由 InputStreamOutputStream 作为基类。

    • 字符流操作的数据单元是16位的字符,由 ReaderWriter 作为基类

  3. 节点流和处理流

    按照流的角色来分,可以分为节点流和处理流。

    • 节点流:可以从向一个特定的IO设备(如磁盘、网络)读/写数据的流。也 被称为低级流。

    • 处理流:用于对一个已存在的流进行连接或封装,通过封装后的流来实现数 据读/写功能。也称为高级流。

流的概念模型

详解 Java I/O(笔记)_第1张图片

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串     StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流     InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream    
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流   PrintStream   PrintWriter
推回输入流 PushbackInputStream   PushbackReader  
特殊流 DataInputStream DataOutputStream    

Java的IO流共涉及40多个类,这些类看上去芜杂而凌乱,但实际上非常规则,而且彼 此之间存在非常紧密的联系Java的IO流的40多个类都是从如下4个抽象基类派生的。

  • InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。

  • OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流

节点流和处理流

Java io 分类方式有很多,根据是否直接处理数据,Java io又分为节点流和处理流,节点流是真正直接处理数据的;处理流是装饰加工节点流的。

节点流

  • 文件流:FileInputStream,FileOutputStrean,FileReader,FileWriter,它们都会直接操作文件,直接与 OS 底层交互。因此他们被称为节点流 ,注意:使用这几个流的对象之后,需要关闭流对象,因为 java 垃圾回收器不会主动回收。不过在 Java7 之后,可以在 try() 括号中打开流,最后程序会自动关闭流对象,不再需要显示地 close。

  • 数组流:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter,对数组进行处理的节点流。

  • 字符串流:StringReader,StringWriter,其中 StringReader 能从 String 中读取数据并保存到 char 数组。

  • 管道流:PipedInputStream,PipedOutputStream,PipedReader,PipedWrite,对管道进行处理的节点流。

处理流

处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。

处理流的构造方法总是要带一个其他的流对象做参数。

常用处理流(通过关闭处理流里面的节点流来关闭处理流)

  • 缓冲流 :把数据从原始 流成块读入或把数据积累到一个大数据块后再成批写出,通过减少通过资源的读写次 数来加快程序的执行。BufferedImputStream,BufferedOutputStream,BufferedReader ,BufferedWriter,需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用 flush 方法,另外,BufferedReader 提供一个 readLine( ) 方法可以读取一行,而 FileInputStream 和 FileReader 只能读取一个字节或者一个字符,因此 BufferedReader 也被称为行读取器。

  • 转换流:InputStreamReader,OutputStreamWriter,要 inputStream 或 OutputStream 作为参数,实现从字节流到字符流的转换,我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类。其中InputStreamReader将字节输入流转换成字符输入流, OutputStreamWriter将字 节输出流转换成字符输出流

    例:

    public static void main(String[] args) throws IOException {
         StringBuilder sb = new StringBuilder();
     ​
         //读取文本文件
         InputStream in = new FileInputStream("F:/temp.txt");
         InputStreamReader inReader = new InputStreamReader(in);
     ​
         char[] chars = new char[50];
         int count = 0;
         while ((count = inReader.read(chars,0,chars.length))!=-1){
             sb.append(chars,0,count);
         }
         inReader.close();
     ​
         //输出读取的内容
         System.out.println(sb.toString());
     ​
         //将读取到的内容写入文本
         OutputStream os =  new FileOutputStream("F:/test.txt");
         OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os);
         outputStreamWriter.write(sb.toString());
         outputStreamWriter.flush();
         outputStreamWriter.close();
     }

     

  • 数据流:DataInputStream,DataOutputStream,提供将基础数据类型写入到文件中,或者读取出来。

字节流

字节输入流 InputStream

下面是 IO 中输入字节流的继承关系:

InputStream

  • ByteArrayInputStream

  • FileInputStream

  • FilterInputStream

  • PushbackInputStream

  • DataInputStream

  • BufferedInputStream

  • LineNumberInputStream

  • ObjectInputStream

  • PipedInputStream

  • SequenceInputStream

  • StringBufferInputStream

总结:

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

  2. PushbackInputStream、DataInputStream 和 BufferedInput Stream都是处理流,他们的父类是 FilterInputStream。

  3. ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从 Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据。

InputStream 中的方法:

修饰符/返回值类型 方法名 说明
int available() 返回此输入流下一个方法调用可以不受阻塞地从此输入流读 取(或跳过)的估计字节数。
void close() 关闭此输入流并释放与该流关联的所有系统资源。
void mark(int readlimit) 在此输入流中标记当前的位置。
boolean markSupported() 测试此输入流是否支持 mark 和 reset 方法。
abstract int read() 读取一个字节数据,并返回读到的数据,如果返回 -1,表示读到了输入流的末尾
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。如果返回-1,表示读到了输入流的末尾。
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。如果返回 -1,表示读到了输入流的末尾。off 指定在数组 b 中存放数据的起始偏移位置;len 指定读取的最大字节数。
void reset() 将此流重新定位到最后一次对此输入流调用 mark 方法时的 位置。
long skip(long n) 跳过和丢弃此输入流中数据的 n 个字节

字节输出流 OutputStream

下面是 IO 中输出字节流的继承关系:

OutputStream

  • ByteArrayOutputStream

  • FileOutputStream

  • FilterOutputStream

  • BufferedOutputStream

  • DataOutputStream

  • PrintStream

  • ObjectOutputStream

  • PipedOutputStream

总结:

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

  2. ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向 Byte 数组、和本地文件中写入数据。

  3. PipedOutputStream 是向与其它线程共用的管道中写入数据。

  4. BufferedOutputStream、DataOutputStream 和 PrintStream 都是处理流,他们的的父类是 FilterOutputStream。

outputStream 中的方法:

修饰符/返回值类型 方法名 说明
void close() 关闭此输出流并释放与此流有关的所有系统资源。
void flush() 刷新此输出流并强制写出所有缓冲的输出字节。
void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出 流。
void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写 入此输出流。
abstract void write(int b) 将指定的字节写入此输出流。

 

字符流

字符输入流 Reader

下面是 IO 中输入字符流的继承关系:

Reader

  • BufferedReader

  • LineNumberReader

  • CharArrayReader

  • FilterReader

  • PushbackReader

  • InputStreamReader

  • FileReader

  • PipedReader

  • StringReader

总结:

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

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

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

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

  5. InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。

Reader 中基本方法:

修饰符/返回值类型 方法名 说明
abstract void close() 关闭该流并释放与之关联的所有资源。
void mark(int readAheadLimit) 标记流中的当前位置。
boolean markSupported() 判断此流是否支持 mark() 操作。
int read() 读取单个字符。
int read(char[] cbuf) 将字符读入数组。
abstract int read(char[] cbuf, int off, int len) 读取 len 个字符,从数组 cbuf[] 的下标 off 处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现
int read(CharBuffer target) 试图将字符读入指定的字符缓冲区。
boolean ready() 判断是否准备读取此流。
void reset() 重置该流
long skip(long n) 跳过字符

InputStream和Reader的方法基本一致,只是InputStream读取的是字节,使用的参数是byte数组(byte[]),而Reader读取的是字符,使用的参数是char数组(char[])

字符输出流 Writer

下面是 IO 中输出字符流的继承关系:

Writer

  • BufferedWriter

  • CharArrayWriter

  • FilterWriter

  • OutputStreamWriter

  • FileWriter

  • PipedWriter

  • PrintWriter

  • StringWriter

总结(和字节输出流对应):

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

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

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

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

  5. OutputStreamWriter 是 OutputStream 到 Writer 转换的桥梁,它的子类 FileWriter 其实就是一个实现此功能的具体类。

Writer 中的基本写法:

修饰符/返回值类型 方法名 说明
Writer append(char c) 将指定字符添加到此 writer。
Writer append(CharSequence csq) 将指定字符序列添加到此 writer。
Writer append(CharSequence csq, int start, int end) 将指定字符序列的子序列添加到此 writer.Appendable。
abstract void close() 关闭此流,但要先刷新它。
abstract void flush() 刷新该流的缓冲。
void write(char[] cbuf) 写入字符数组。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
void write(int c) 写入单个字符。
void write(String str) 写入字符串。
void write(String str, int off, int len) 写入字符串的某一部分。

I/O 常见用法

1、读取键盘输入,打印到控制台

在刷题网站刷算法题的时候,在程序开头都需要和键盘进行交互,常常用到行夺取器 BufferedReader 和转换流 InputStreamReader。

public static void main(String[] args) throws IOException {
    PrintWriter pw = null;
    BufferedReader br = null;
    try{
        System.out.println("请输入:");
        
        pw = new PrintWriter(System.out, true);
        br = new BufferedReader(new InputStreamReader(System.in));
        
        String line = null;
        while ((line = br.readLine())!=null){
            if (line.equals("exit")){
                System.exit(1);
            }
            pw.println(line);
        }
        
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        pw.close();
        br.close();
    }
}

运行结果:

详解 Java I/O(笔记)_第2张图片

 

2、用字节流读写文件

因为是用字节流来读媒介,所以对应的流是 InputStream 和 OutputStream,并且媒介对象是文件,所以用到子类是 FileInputStream 和 FileOutputStream,这里还可以通过 BufferedInputStream 用缓冲流来读取文件。

public static void main(String[] args) throws IOException {
    InputStream is = null;
    OutputStream os = null;
    try{
        is = new FileInputStream("F:\\temp.txt");
        os = new FileOutputStream("F:\\FileOutputStream.txt");
        
        byte[] bytes = new byte[4];
        int hasRead = 0;
        while ((hasRead = is.read(bytes))> 0){
            os.write(bytes,0,hasRead);
        }
        
        System.out.println("success");
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        os.close();
        is.close();
    }
}

3、用字符流进行读写操作

(1)FileReader 和 FileWriter

public static void main(String[] args) throws IOException {
    Reader reader = null;
    Writer writer = null;
    try{
        File rf = new File("F:/temp.txt");
        reader = new FileReader(rf);

        File wf = new File("F:/test.txt");
        writer = new FileWriter(wf);

        char[] chars = new char[(int) rf.length()];
        int size = reader.read(chars);

        System.out.println("大小:" + size + "个字符\n内容:" + new String(chars));
        writer.write(chars);

    }catch (Exception e){
        e.printStackTrace();
    }finally {
        reader.close();
        writer.close();
    }
}

运行结果:

 

 

详解 Java I/O(笔记)_第3张图片

详解 Java I/O(笔记)_第4张图片

(2)StringReader 和 StringWriter

StringReader:用来将字符串转换成字符输入流。然后使用字符输入流提供的方式进 行操作,也可以提供给其他高级字符输入流来使用。如可以将该字符输入流提供给 BufferedReader输入流使用。

StringWriter:在内存中缓存读取到的所有字符串,然后使用通过toString方法一次性 全部输出字符串。

public static void main(String[] args) throws IOException {
    StringReader sr = null;
    StringWriter sw = null;
    try{
        String str = "好好学习,天天向上;";
        char[] chars = new char[32];
        int hasRead = 0;

        // StringReader将以String字符串为节点读取数据
        sr = new StringReader(str);
        while ((hasRead = sr.read(chars))>0){
            System.out.println(new String(chars,0,hasRead));
        }

        // 由于String是一个不可变类,因此创建StringWriter时,实际上是以一个StringBuffer作为输出节点
        sw = new StringWriter();
        sw.write("光阴似箭,日月如梭;");

        // toString()返回sw节点内的数据
        System.out.println(sw.toString());

    }catch (Exception e){
        e.printStackTrace();
    }finally {
        sw.close();
        sr.close();
    }
}

运行结果:

详解 Java I/O(笔记)_第5张图片

4、字节流转换为字符流

在例 3 中用字符流读文件时,打印到控制台的中文会乱码,使用转换流可以解决这一问题。

public static void main(String[] args) throws IOException {
    InputStream is = null;
    Reader reader = null;
    try{
        File file = new File("F:/temp.txt");
        is = new FileInputStream(file);

        reader = new InputStreamReader(is,"utf-8");//或者gbk

        char[] chars = new char[(int) file.length()];
        int size = reader.read(chars);

        System.out.println("大小:" + size + "个字符\n内容:" + new String(chars));

    }catch (Exception e){
        e.printStackTrace();
    }finally {
        reader.close();
        is.close();
    }
}

运行结果:

详解 Java I/O(笔记)_第6张图片

5、随机读写文件

RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供 了众多的方法 来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。 RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意地方来读 写数据。

RandomAccessFile四种访问模式:

  • "r":以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法, 都将抛出 IOException 异常。

  • "rw":以读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。

  • "rws”:以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元 数据的每个更新都同步写入到底层存储设备。

  • "rwd": 以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容的每 个更新都同步写入到底层存储设备。

元数据是文件的附加属性,如文件大小、创建时间、所有者等信息。

示例1:使用 RandomAccessFile 可以实现对文件的随机读取,主要是通过 seek() 方法实现指针偏移。

public static void main(String[] args) throws IOException {
    RandomAccessFile randomAccessFile = null;
    try{
        // 创建一个RandomAccessFile对象
        randomAccessFile = new RandomAccessFile("F:/temp.txt","rw");
        // 通过seek方法来移动指针
        randomAccessFile.seek(10);
        // 获取当前指针
        long pointerBegin = randomAccessFile.getFilePointer();
        // 从当前指针开始读
        byte[] bytes = new byte[10];//再读10,也就是20
        randomAccessFile.read(bytes);

        long pointerEnd = randomAccessFile.getFilePointer();
        System.out.println("pointerBegin:" + pointerBegin + "\npointerEnd:" + pointerEnd + "\n" + new String(bytes));
       
        //RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件中原有的内容。
 		randomAccessFile.seek(20);
        // 获取当前指针
        long begin = randomAccessFile.getFilePointer();
        randomAccessFile.write(bytes);
        long end = randomAccessFile.getFilePointer();
        System.out.println("begin:" + begin + "\nend:" + end);

    }catch (Exception e){
        e.printStackTrace();
    }finally {
        randomAccessFile.close();
    }
}

运行结果:

操作前的文件内容:

详解 Java I/O(笔记)_第7张图片

操作后的控制台输出:

详解 Java I/O(笔记)_第8张图片

操作后的文件:

详解 Java I/O(笔记)_第9张图片

示例2

public class RandomAccessFileDemo {
    public static void main(String[] args) throws Exception {
        demo1();
        //demo2();
        //demo3();
    }

    /**
     * RandomAccessFile 读取(只读)
     * @throws Exception
     */
    public static void demo1() throws Exception {
        // 实例化RandomAccessFile类,指定只读模式
        RandomAccessFile accessFile = new RandomAccessFile("F:/temp.txt","r");
        // 调到指定位置开始读
        accessFile.seek(5);
        // 读取文件
        byte[] bytes = new byte[1024];
        int count = 0;
        while ((count = accessFile.read(bytes, 0, bytes.length)) != -1){
            System.out.println(new String(bytes,0,count));
        }
        //关闭流
        accessFile.close();
    }

    /**
     * 在文件最后追加
     * @throws Exception
     */
    public static void demo2() throws Exception {
        // 读写模式
        RandomAccessFile accessFile = new RandomAccessFile("F:/temp.txt", "rw");
        // 跳到文件末尾位置
        accessFile.seek(accessFile.length());
        accessFile.write("你好,RandomAccessFile.".getBytes(Charset.forName("UTF-8")));
        //关闭流
        accessFile.close();
    }
     /** 如果需要向指定位置插入内容,
     * 程序需要先把插入点后面的内容读入缓冲区,
     * 等把需要插入的数据写入文件后,
     * 再将缓冲区的内容追加到文件后面。
     */
    public static void demo3() throws Exception {
        //存放读取到的 插入点后面的内容
        ByteArrayOutputStream arrayout = new ByteArrayOutputStream();

        RandomAccessFile accessFile = new RandomAccessFile("F:/temp.txt", "rw");

        // 调到指定位置
        accessFile.seek(5);

        // 读取指定位置后的内容到 arrayout
        byte[] bytes = new byte[1024];
        int count = 0;
        while ((count = accessFile.read(bytes, 0, bytes.length)) != -1) {
            arrayout.write(bytes, 0, count);
        }

        // 调到指定位置
        accessFile.seek(5);
        // 插入想插入的内容
        accessFile.write("(你好)".getBytes(Charset.forName("UTF-8")));
        // 读取arrayout 里面的内容,追加会源文件
        accessFile.write(arrayout.toByteArray());
        //关闭流
        accessFile.close();
    }
}

输出结果:

文件:

详解 Java I/O(笔记)_第10张图片

demo1:

详解 Java I/O(笔记)_第11张图片

demo2:

详解 Java I/O(笔记)_第12张图片

demo3:

详解 Java I/O(笔记)_第13张图片

6、读写管道

管道流是用来在多个线程之间进行信息传递的Java流,被号称是最难使用的流,被使用的频率比较低

它提供了多线程间信息传输的一种有效手段。 管道流包括四个类 PipedOutputStream/PipedWriter 和 PipedInputStream/PipedReader。 其中 PipedOutputStream/PipedWriter 是写入者/生产者/发送者;PipedInputStream/PipedReader是读取者/消费者/接收者。

在使用管道流之前,需要注意以下要点:

  • 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁;

  • 管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或 者connect函数进行连接;

  • 一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可 以使用带有参数的构造器;

  • 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时, 写操作阻塞;

  • 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;

  • 管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到­1 值。

示例1:管道流要成对使用

public static void main(String[] args) throws IOException {
    final PipedOutputStream pos = new PipedOutputStream();
    final PipedInputStream pis = new PipedInputStream(pos);

    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                pos.write("Hello world, pipe!".getBytes());
                pos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });

    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                int data = pis.read();
                while (data != -1){
                    System.out.print((char) data);
                    data = pis.read();
                }
                pis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    });

    thread1.start();
    thread2.start();
}

输出结果:

详解 Java I/O(笔记)_第14张图片

示例2:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;

public class PipedStreamDemo {
    public static void main(String[] args) throws IOException {
        PipedWriter pw = new PipedWriter();
        PipedReader pr = new PipedReader();
        //连接两个线程的管道流
        pw.connect(pr);

        ThreadWriter a = new ThreadWriter(pw);
        ThreadReader b = new ThreadReader(pr);

        a.start();
        b.start();
    }
}
class ThreadWriter extends Thread{
    //声明管道流
    private PipedWriter writer;
    //构造器
    public ThreadWriter(PipedWriter writer) {
        super();
        this.writer = writer;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                writer.write("这是ThreadWriter! count:"+i+"\r\n");
                writer.flush();
            }
            writer.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}
class ThreadReader extends Thread{
    //声明管道流 发送着和接收者
    private PipedReader reader;
    //构造器
    public ThreadReader(PipedReader reader) {
        super();
        this.reader = reader;
    }

    @Override
    public void run() {
        try {
            String str="";
            BufferedReader bReader=new BufferedReader(reader);
            while ((str=bReader.readLine())!=null) {
                System.out.println("ThreadReader:"+str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

详解 Java I/O(笔记)_第15张图片

7、将多个输入流当成一个输入流依次读取

public static void main(String[] args) throws IOException {
    FileInputStream fis1 = new FileInputStream("F:/test1.txt");
    FileInputStream fis2 = new FileInputStream("F:/test2.txt");

    SequenceInputStream sis = new SequenceInputStream(fis1,fis2);

    FileOutputStream fos = new FileOutputStream("F:/test3.txt");

    int temp;
    while ((temp= sis.read())!=-1){
        System.out.print((char) temp);
        fos.write(temp);
    }
    fos.close();
    sis.close();
    fis2.close();
    fis1.close();
}

文件:

详解 Java I/O(笔记)_第16张图片

运行结果:

详解 Java I/O(笔记)_第17张图片

详解 Java I/O(笔记)_第18张图片

8、推回输入流使用实例

一般情况下使用输入流从磁盘,网络或者其它的物理介质读取数据都是顺序读取的, 在流的内部会维护一个指针,读取数据的同时,指针会向后移动,直到读完为止。

在一些实际常见中,如果读出来的数据不是想要的又不能再放回去怎么办?

可以使用io提供的推回输入流。使用普通的IO输入流如果读取到不想要的数据,只能 在程序里面处理掉,而使用IO里面的推回输入流读取数据则可以把数据给推回到输入 流的缓冲区中。

推回输入流主要为两个类: PushbackInputStream 和 PushbackReader

public static void main(String[] args) throws IOException {
    // 创建推回输入流 指定推回缓冲区大小为64,
    //如果不指定,默认缓冲区大小为1
    PushbackReader pr = new PushbackReader(new FileReader("F:/test1.txt"),64);
    char[] buf = new char[32];//临时数组
    String lastContent = "";//记录上次读取的字符串
    int hasRead = 0;// 读取的字符数

    while ((hasRead = pr.read(buf))>0){
        // 本次读取内容
        String content = new String(buf, 0, hasRead);
        int targetIndex = 0;
        // (上次+本次读取内容 ‐‐ 避免 要查找的字符串被截断).查找目标字符串 返回目标出现位置
        if ((targetIndex = (lastContent + content).indexOf("A")) > 0){
            System.out.println(targetIndex);
            // 找到目标字符串
            // 将本次内容和上次内容一起推回缓冲区
            // *****推回缓冲区的内容大小不能超过缓冲区的大小
            pr.unread((lastContent + content).toCharArray());
            // 判断 targetIndex 是否 > 32(临时数组大小)
            if (targetIndex > 32) {
                buf = new char[targetIndex];
            }
            // 再次读取指定长度的内容(就是目标字符串之前的内容)
            pr.read(buf, 0, targetIndex);
            System.out.println(new String(buf, 0, targetIndex));
            System.out.println(new String(buf, targetIndex, buf.length-targetIndex));
            System.exit(0);
        }else{
            System.out.println(lastContent);
            lastContent = content;
        }
    }

}

运行结果:

详解 Java I/O(笔记)_第19张图片

详解 Java I/O(笔记)_第20张图片

 

你可能感兴趣的:(java,I/O)