Java基础09-IO流

概述

Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

层次图

一个流被定义为一个数据序列。
输入流用于从源读取数据,输出流用于向目标写数据。
下图是一个描述输入流和输出流的类层次图:

Java IO流层次图

FileInputStream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。
有多种构造方法可用来创建该对象。

  • 可以使用字符串类型的文件名来创建一个输入流对象来读取文件:
InputStream ins = new FileInputStream("C:/java/hello");
  • 也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象
File file = new File("C:/java/hello");
InputStream is = new FileInputStream(file);

创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作:

InputStream常用方法

FileOutputStream

该类用来创建一个文件并向文件中写数据。
如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。
有两个构造方法可以用来创建该对象。

  • 使用字符串类型的文件名来创建一个输出流对象:
OutputStream os = new FileOutputStream("C:/java/hello")
  • 也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:
File file = new File("C:/java/hello");
OutputStream os = new FileOutputStream(file);

创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。

OutputStream常用方法

实例:

import java.io.*;
 
public class fileStreamTest {
    public static void main(String args[]) {
        try {
            byte bWrite[] = { 11, 21, 3, 40, 5 };
            OutputStream os = new FileOutputStream("test.txt");
            for (int x = 0; x < bWrite.length; x++) {
                os.write(bWrite[x]); // writes the bytes
            }
            os.close();
 
            InputStream is = new FileInputStream("test.txt");
            int size = is.available();
 
            for (int i = 0; i < size; i++) {
                System.out.print((char) is.read() + "  ");
            }
            is.close();
        } catch (IOException e) {
            System.out.print("Exception");
        }
    }
}

上面的程序首先创建文件test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上。
以上代码由于是二进制写入,可能存在乱码,你可以使用以下代码实例来解决乱码问题:

import java.io.*;
 
public class fileStreamTest2 {
    public static void main(String[] args) throws IOException {
 
        // 构建FileOutputStream对象,文件不存在会自动新建
        File f = new File("a.txt");
        FileOutputStream fop = new FileOutputStream(f);
 
        // 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk
        OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
        
        // 写入到缓冲区
        writer.append("中文输入");
        
        // 换行
        writer.append("\r\n");
        
         // 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入
        writer.append("English");
        
        // 关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉
        writer.close();
       
        // 关闭输出流,释放系统资源
        fop.close();
       
        // 构建FileInputStream对象
        FileInputStream fip = new FileInputStream(f);
       
        // 构建InputStreamReader对象,编码与写入相同
        InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
 
        StringBuffer sb = new StringBuffer();
        while (reader.ready()) {
            // 转成char加到StringBuffer对象中
            sb.append((char) reader.read());
        }
        System.out.println(sb.toString());
        // 关闭读取流
        reader.close();
        
        // 关闭输入流,释放系统资源
        fip.close();
    }
}

FileReader

FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。可以通过以下几种构造方法创建需要的对象。

  • 在给定从中读取数据的 File 的情况下创建一个新 FileReader:
FileReader(File file)
  • 在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader:
FileReader(FileDescriptor fd) 
  • 在给定从中读取数据的文件名的情况下创建一个新 FileReader:
FileReader(String fileName) 

创建FileReader对象成功后,可以参照以下列表里的方法操作文件:

FileReader常用方法

实例如下:

import java.io.*;
 
public class FileRead {
    public static void main(String args[]) throws IOException {
        File file = new File("Hello1.txt");
        // 创建文件
        file.createNewFile();
        // creates a FileWriter Object
        FileWriter writer = new FileWriter(file);
        // 向文件写入内容
        writer.write("This\n is\n an\n example\n");
        writer.flush();
        writer.close();
        // 创建 FileReader 对象
        FileReader fr = new FileReader(file);
        char[] a = new char[50];
        fr.read(a); // 读取数组中的内容
        for (char c : a)
            System.out.print(c); // 一个一个打印字符
        fr.close();
    }
}

运行结果 :

This
is
an
example

FileWriter

FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。可以通过以下几种构造方法创建需要的对象:

  • 在给出 File 对象的情况下构造一个 FileWriter 对象:
FileWriter(File file)
  • 在给出 File 对象的情况下构造一个 FileWriter 对象:
FileWriter(File file, boolean append)

参数:
file:要写入数据的 File 对象。
append:如果 append 参数为 true,则将字节写入文件末尾处,相当于追加信息。如果 append 参数为 false, 则写入文件开始处。

  • 构造与某个文件描述符相关联的 FileWriter 对象:
FileWriter(FileDescriptor fd)
  • 在给出文件名的情况下构造 FileWriter 对象,它具有指示是否挂起写入数据的 boolean 值:
FileWriter(String fileName, boolean append)

创建FileWriter对象成功后,可以参照以下列表里的方法操作文件:

FileWriter常用方法

实例:

import java.io.*;
 
public class FileRead {
    public static void main(String args[]) throws IOException {
        File file = new File("Hello1.txt");
        // 创建文件
        file.createNewFile();
        // creates a FileWriter Object
        FileWriter writer = new FileWriter(file);
        // 向文件写入内容
        writer.write("This\n is\n an\n example\n");
        writer.flush();
        writer.close();
        // 创建 FileReader 对象
        FileReader fr = new FileReader(file);
        char[] a = new char[50];
        fr.read(a); // 从数组中读取内容
        for (char c : a)
            System.out.print(c); // 一个个打印字符
        fr.close();
    }
}

运行结果:

This
is
an
example

缓冲流BufferedInputStream、BufferedOutputStream

先看一下继承关系图:

BufferedInputStream
BufferedOutputStream

有了InputStream为什么还要有BufferedInputStream?
     BufferedInputStream和BufferedOutputStream这两个类分别是FilterInputStream和FilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。
     不带缓冲的操作每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!
     同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReader和BufferedWriter两个类。
     BufferedInputStream和BufferedOutputStream类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。
     BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

BufferedInputStream API
  • 源码关键字段
//内置缓存字节数组的大小 8KB
private static int defaultBufferSize = 8192;

//内置缓存字节数组
protected volatile byte buf[];  

//当前buf中的字节总数、注意不是底层字节输入流的源中字节总数
protected int count;    

//当前buf中下一个被读取的字节下标
protected int pos;     

//最后一次调用mark(int readLimit)方法记录的buf中下一个被读取的字节的位置
protected int markpos = -1; 

 //调用mark后、在后续调用reset()方法失败之前云寻的从in中读取的最大数据量、用于限制被标记后buffer的最大值
protected int marklimit;   
  • 构造函数
BufferedInputStream(InputStream in) //使用默认buf大小、底层字节输入流构建bis 

BufferedInputStream(InputStream in, int size) //使用指定buf大小、底层字节输入流构建bis  
  • 常用方法
int available();  //返回底层流对应的源中有效可供读取的字节数      

void close();  //关闭此流、释放与此流有关的所有资源  

boolean markSupport();  //查看此流是否支持mark

void mark(int readLimit); //标记当前buf中读取下一个字节的下标  

int read();  //读取buf中下一个字节  

int read(byte[] b, int off, int len);  //读取buf中下一个字节  

void reset();   //重置最后一次调用mark标记的buf中的位置

long skip(long n);  //跳过n个字节、 不仅仅是buf中的有效字节、也包括in的源中的字节 
BufferedOutputStream API
  • 源码关键字段
protected byte[] buf;   //内置缓存字节数组、用于存放程序要写入out的字节  
protected int count;    //内置缓存字节数组中现有字节总数 
  • 构造函数
//使用默认大小、底层字节输出流构造bos。默认缓冲大小是 8192 字节( 8KB )
BufferedOutputStream(OutputStream out); 
//使用指定大小、底层字节输出流构造bos  
BufferedOutputStream(OutputStream out, int size);  
  • 常用方法
//在这里提一句,`BufferedOutputStream`没有自己的`close`方法
//当他调用父类`FilterOutputStrem`的方法关闭时,会间接调用自己实现的`flush`方法
//将buf中残存的字节flush到out中再`out.flush()`到目的地中,DataOutputStream也是如此。 


//将写入bos中的数据flush到out指定的目的地中、注意这里不是flush到out中
//因为其内部又调用了out.flush()  
void  flush();  

//将一个字节写入到buf中  
write(byte b);      

// 将b的一部分写入buf中 
write(byte[] b, int off, int len);    

那么什么时候flush()才有效呢?
答案是:当OutputStream是BufferedOutputStream时。

当写文件需要flush()的效果时,需要
FileOutputStream fos = new FileOutputStream(“c:\a.txt”);
BufferedOutputStream bos = new BufferedOutputStream(fos);
也就是说,需要将FileOutputStream作为BufferedOutputStream构造函数的参数传入,然后对BufferedOutputStream进行写入操作,才能利用缓冲及flush()。

查看BufferedOutputStream的源代码,发现所谓的buffer其实就是一个byte[]。
BufferedOutputStream的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。
而另一种触发磁盘写入的办法就是直接调用flush()了。
需要注意的是:
1.BufferedOutputStream在close()时会自动flush()
2.BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush()
应用实例:
使用缓存流将F盘根目录里面名字为:123.png 图片复制成 abc.png

package com.tp.pandora;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class IoTest {
    public static void main(String[] args) throws IOException {

        String filePath = "F:/123.png";
        String filePath2 = "F:/abc.png";
        File file = new File(filePath);
        File file2 = new File(filePath2);
        copyFile(file, file2);
    }

    /**
     * 复制文件
     *
     * @param oldFile
     * @param newFile
     */
    public static void copyFile(File oldFile, File newFile) {
        InputStream inputStream = null;
        BufferedInputStream bufferedInputStream = null;

        OutputStream outputStream = null;
        BufferedOutputStream bufferedOutputStream = null;

        try {
            inputStream = new FileInputStream(oldFile);
            bufferedInputStream = new BufferedInputStream(inputStream);

            outputStream = new FileOutputStream(newFile);
            bufferedOutputStream = new BufferedOutputStream(outputStream);

            byte[] b = new byte[1024];   //代表一次最多读取1KB的内容

            int length = 0; //代表实际读取的字节数
            while ((length = bufferedInputStream.read(b)) != -1) {
                //length 代表实际读取的字节数
                bufferedOutputStream.write(b, 0, length);
            }
            //缓冲区的内容写入到文件
            bufferedOutputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
如何正确的关闭流

在处理流关闭完成后,我们还需要关闭节点流吗?
close()方法的作用是关闭流,并且释放系统资源 ,BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。

缓冲流BufferedReader、BufferedWriter

BufferedReader

BufferedWriter

BufferedReader

  • BufferedReader构造函数
BufferedReader(Reader in, int sz) //创建一个使用指定大小输入缓冲区的缓冲字符输入流。 

BufferedReader(Reader in) //创建一个使用默认大小输入缓冲区的缓冲字符输入流。
  • 常用方法
int  read()  //读取单个字符。
int  read(char[] cbuf, int off, int len)  //将字符读入数组的某一部分。
String  readLine()  //读取一个文本行。
boolean  ready()  //判断此流是否已准备好被读取。
void  reset()  //将流重置到最新的标记。
long  skip(long n)  //跳过字符。
void  close() //关闭该流并释放与之关联的所有资源。
void  mark(int readAheadLimit) //标记流中的当前位置。
boolean  markSupported() //判断此流是否支持 mark() 操作(它一定支持)。

BufferedWriter

  • BufferedWriter构造函数
BufferedWriter(Writer out, int sz) //创建一个使用给定大小输出缓冲区的新缓冲字符输出流。

BufferedWriter(Writer out) //建一个使用默认大小输出缓冲区的缓冲字符输出流。
  • 常用方法
void  close()  // 关闭此流,但要先刷新它。
void  flush()  //刷新该流的缓冲。
void  newLine() //写入一个行分隔符。
void  write(char[] cbuf, int off, int len) //写入字符数组的某一部分。
void  write(int c) //写入单个字符。
void  write(String s, int off, int len) //写入字符串的某一部分。

实例:复制F盘里面的一个txt文本

package com.tp.pandora;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

public class IoTest {
    public static void main(String[] args) {

        String filePath = "F:/123.txt";
        String filePath2 = "F:/abc.txt";

        File file = new File(filePath);
        File file2 = new File(filePath2);
        copyFile(file, file2);
    }

    private static void copyFile(File oldFile, File newFile) {
        Reader reader = null;
        BufferedReader bufferedReader = null;

        Writer writer = null;
        BufferedWriter bufferedWriter = null;
        try {
            reader = new FileReader(oldFile);
            bufferedReader = new BufferedReader(reader);

            writer = new FileWriter(newFile);
            bufferedWriter = new BufferedWriter(writer);

            String result = null; //每次读取一行的内容
            while ((result = bufferedReader.readLine()) != null) {
                bufferedWriter.write(result);  //把内容写入文件
                bufferedWriter.newLine();  //换行,result 是一行数据,所以没写一行就要换行 
            }

            bufferedWriter.flush();  //强制把数组内容写入文件

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedWriter.close();  //关闭输出流
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                bufferedReader.close();  //关闭输入流
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • 五、IO流 1、IO流概述 (1)用来处理设备(硬盘,控制台,内存)间的数据。(2)java中对数据的操作都是通过...
    佘大将军阅读 507评论 0 0
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,378评论 0 4
  • 1、IO流 1.1、概述 之前学习的File类它只能操作文件或文件夹,并不能去操作文件中的数据。真正保存数据的是文...
    Villain丶Cc阅读 2,668评论 0 5
  • 一.介绍 在我们学习字节流与字符流的时候,大家都进行过读取文件中数据的操作,读取数据量大的文件时,读取的速度会很慢...
    走着别浪阅读 341评论 0 2
  • 【有一个网球教练对学生说:“如果一个网球掉进草堆,应该如何找?”有人说:“从草堆中心线开始找。”有人说:“从草堆的...
    贺小桶阅读 379评论 0 1