Java之IO流

一、简介

Java的IO流是实现输入和输出的基础。Java把不同的输入/输出源(键盘、文件、网络)抽象为流, 因此可以说通过输入流读数据, 通过输出流来写数据。

二、流的分类

在这里插入图片描述

1.输入流和输出流
输入流: 只能从中读取数据
输出流: 只能用于写数据

2.字节流和字符流
字节流: 处理的数据单元是8位的字节
字符流: 处理的数据单元是16位的字符

1.0版本的IO流只支持8位的字节, 加入了字符流的原因之一是因为需要对unicode字符集的支持,。

3.节点流和包装流
节点流是指直接与IO设备交互的流,也称为低级流。
包装流是指通过对已存在的节点流进行包装, 然后使用包装过的流来实现读/写功能, 它不会直接连接到数据源。只要将节点流封装成包装流, 就可以使用相同的代码来向不同的数据源读取数据或向不同的设备输出数据。

三、抽象基类

1、InputStream与Reader

InputStream/Reader是抽象类, 不能实例化。但是它们是输入流的模板 , 所有的输入流都可调用它们的方法。
在这里插入图片描述
要读取数据, 主要有2个步骤:
1.与数据源进行关联。 数据源可以是网络传输的数据, 磁盘的上的文件, 用户的标准输入, 内存中已存在的字符串等。
2.调用方法读取数据

下面使用FileInputStream来进行说明。
构造方法
FileInputStream使用它的构造方法创建一个流, 并与一个数据源进行关联
—FileInputStream(String name)
—FileInputStream(File file)

它的两个参数类型分别为StringFile.
String类型通常是一个文件名,
而File表示的是已经与一个文件进行关联的的对象

读取数据方法
read()——每次读取一个字节
read(byte[])——每次读取一个数组长度的字节
read(byte b[], int off, int len)——读取数据到一个字节数组, 数据从off开始存放, 长度为len

// InputStream

public static void main(String[] args) throws IOException {
        // 创建一个字节输入流
        FileInputStream fin = new FileInputStream("test.txt");
        // 创建一个长度为1024的缓冲数组
        byte[] bytes = new byte[1024];
        // 保存实际读取的字节数
        int hasRead = 0;
        while ((hasRead = fin.read(bytes)) > 0) {
            // 将字节数组转成字符串输入
            System.out.println(new String(bytes, 0, hasRead));
        }
        //关闭输入流
        fin.close();
    }

Reder方法与InputStream类似

// Reader
public static void main(String[] args) {
        // 字符输入流
        try (FileReader fr = new FileReader("test01.txt")) {
            char[] cbuf = new char[32];
            // 实际读取的字符数
            int hasread;
            while((hasread = fr.read(cbuf)) > 0){
                // 将字符数组转成字符串输入
                System.out.println(new String(cbuf, 0, hasread));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.OutputStream/Writer

OutputStream/Writer是输出流的模板, 输出流都可调用它们
在这里插入图片描述
要写数据通常需要以下步骤:
1.创建一个输入流, 然后读取数据
2.创建一个输出流, 将读取到的数据写到一个文件

创建一个输出流, 以FileOutputStream为例。

构造方法
FileOutputStream(String name) / FileOutputStream(String name, boolean append)
FileOutputStream(File file) / FileOutputStream(File file, boolean append)

FileOutputStream的参数表示要写到哪一个文件。append参数表示是否写到文件的末尾, 通常情况下为false, 表示直接覆盖文件。

写数据
write(int b)——每次写一个字节
write(byte b[])——从数组b中获取字节, 并写到输出流
write(byte b[], int off, int len)——从数组b中获取字节, 从索引off开始, 长度为len, 并写到输出流

// OutputStream
public static void main(String[] args) {
        try (// 创建字节输入流
             FileInputStream fin = new FileInputStream(args[0]);
             // 创建字节输出流
             FileOutputStream fout = new FileOutputStream(args[1])) {
            byte[] bytes = new byte[32];
            // 实际读取的字节
            int hasread;
            while ((hasread = fin.read(bytes)) != -1) {
                fout.write(bytes, 0, hasread);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

与OutputStream不同的是, Writer可以直接将字符串写入文件.

// Writer

public static void main(String[] args) {
        try (FileWriter fw = new FileWriter("poetry.txt")) {
            // windows平台的换行符
            fw.write("锦瑟无端五十弦, 一弦一柱思华年。\r\n");
            fw.write("庄生晓梦迷蝴蝶, 望帝春心托杜鹃。\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

通常情况下计算机的文件可分为二进制文件文本文件, 但其实都是二进制文件。 因此都可以用字节流来处理。假如是文本文件使用字节流读取, 那么还需要转换成字符才能显示。因此,
1.若是处理二进制文件, 考虑使用字节流。
2.若是处理文本文件, 应该考虑使用字符流。

四、包装流

判断一个流是是否包装流: 它的构造器参数是否是另外一个流。
FileInputStream与FileReader都是节点流, 它们直接与源数据文件交互。

使用包装流有以下好处:
(1) 包装流通过对不同的节点流进行包装, 从而可以使用相同的代码对不同的数据源进行操作。
(2) 包装流提供了更方便的方法进行操作

通常我们使用System.out.println方法将内容输出到屏幕。 由于System.outPrintStream的对象, 因此System.out.println也等价于PrintStream.println.

但是我们可以使用PrintStream对一个输出流进行包装, 从而使其可以将内容输出到文件。

public static void main(String[] args) {
        try (FileOutputStream fout = new FileOutputStream("test.txt");
             PrintStream ps = new PrintStream(fout)) {
            ps.print("普通字符串");
            ps.print(new PrintStreamTest());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

五、转换流

转换流用于将字节流转换成字符流。转换流的类有2个:
InputStreamReader
OutputStreamWriter

public static void main(String[] args) {
        try (  // 将System.in转换为Reade对象
                InputStreamReader reader = new InputStreamReader(System.in);
                //将reader包装成缓冲流
                BufferedReader br = new BufferedReader(reader)) {
            String line = null;
            // 每次读取一行文本
            while ((line = br.readLine()) != null) {
                if (line.equals("exit")) {
                    System.exit(1);
                }
                // 打印读取的内容
                System.out.println("输出的内容为:" + line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

System.in表示标准输入(键盘输入), 它表示的是InputStream对象。

六、推回输入流

推回输入流的类为:
PushbackReader
PushbackInputStream

它们都提供了如下3个方法:
void unread(byte[] b) / void unread(char cbuf[])
void unread(byte[] b, int off, int len) / void unread(char cbuf[], int off, int len)
void unread(int b)

当使用unread方法时, 该流会将读取的内容推回缓冲区, 从而允许重复读取刚刚读取的内容。
当调用推回输入流的read方法时, 会先从缓冲区读取数据, 只有读取完缓冲区的数据且还没有装满read方法所需的数组时, 才会从原输入流中读取。

缓冲区可以看成是一个临时的区域, 用于存储unread方法读取的数据。

在创建推回输入流时可指定缓冲区大小, 缓冲区默认长度为1.
public PushbackInputStream(InputStream in, int size)
PushbackReader(Reader in, int size)

该方法实现了打印目标字符串前面的内容——

try (PushbackReader pbr = new PushbackReader(new FileReader("poetry.txt"), 64)) {
            char[] buf = new char[32];
            // 用于保存上次读取的字符串
            String lastContent = "";
            int hasread = 0;
            while ((hasread = pbr.read(buf)) > 0) {
                // 将读取的内容转成字符串
                String content = new String(buf, 0, hasread);
                int targetIndex = 0;
                if ((targetIndex = (lastContent + content).indexOf("鹃")) > 0) {
                    // 将本次内容和上次内容退回缓冲区
                    pbr.unread((lastContent + content).toCharArray());
                    if(targetIndex > 32){
                        buf = new char[targetIndex];
                    }
                    pbr.read(buf, 0, targetIndex);
                    // 打印读取的内容
                    System.out.println(new String(buf, 0, targetIndex));
                    System.exit(0);
                }else{
                    // 打印上次输入的内容
                    System.out.println(lastContent);
                    // 将本次读取的内容设为上次读取的内容
                    lastContent = content;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

七、重定向标准输入/输出

Java使用System.in和System.out表示标准输入和标准输出, 标准输入和标准输出通常情况下表示键盘和显示器。System类中提供了如下方法用于重定向标准输入/输出:
setIn(InputStream in)
setOut(PrintStream out)
setErr(PrintStream err

// 重定向输入
public static void main(String[] args) {
        try (
            FileInputStream fin = new FileInputStream("C:\\Users\\peter1950\\Desktop\\T\\out.txt")) {
            // 将标准输入重定向到fin输入流
            System.setIn(fin);
            // 使用System.in创建Scanner对象, 用于获取标准输入
            Scanner sc = new Scanner(System.in);
            sc.useDelimiter("\n");
            while(sc.hasNext()){
                System.out.println("键盘输入的内容是: " + sc.next());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

上面的代码将标准输入重定向到一个输入流。 按我的理解就是数据源发生了变化, 原本是通过键盘输入获取数据, 现在改为从另一个输入流获取数据, 也就是说输入流中的数据被当成了标准输入。

// 重定向输出
public static void main(String[] args) {
        try(
            PrintStream ps = new PrintStream(new FileOutputStream("C:\\Users\\peter1950\\Desktop\\T\\out.txt"))){
            // 将标准输出重定向到ps输出流
            System.setOut(ps);
            // 向标准输出输出一个字符串
            System.out.println("普通字符串");
            System.out.println(new RedirectOut());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

上面的代码将标准输出重定向到输出流, That means, 本来要输出到屏幕的数据被输出到另一个文件。


八、RandomAccessFile——读取文件&输出数据

RandomAccessFile即可以读取数据, 也可以输出数据。并且RandomAccessFile可以跳转到文件的任意位置, 然后从该位置开始进行访问。 因此若只需要访问文件的部分内容, 推荐使用RandomAccessFile。

RandomAccessFile通过移动指针来对位置进行定位, 它有两个方法对指针进行操作:
getFilePointer()——返回指针的当前位置
seek(long pos)——将指针移动到pos位置, pos表示字节.

构造方法
RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)

mode参数表示的是访问模式, 有以下几种模式:
“r”——以只读方式打开文件, 不能进行写操作
“rw”——以读、写方式打开文, 若文件不存在则尝试创建新文件
“rws”——
“rwd”——

1.读取文件——

public static void main(String[] args) {
        // 以只读模式创建
        try(RandomAccessFile rf = new RandomAccessFile("out.txt", "r")){
            System.out.println("RandomAccessFile的文件指针的初始位置 :" + rf.getFilePointer());
            // 移动指针的位置
            rf.seek(6);
            byte[] bbuf = new byte[1024];
            int hasread = 0;
            while((hasread = rf.read(bbuf)) > 0){
                System.out.println(new String(bbuf, 0, hasread));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

// 输出:
RandomAccessFile的文件指针的初始位置 :0
g

文件内容为abcdefg


追加内容到文件结尾——

try (RandomAccessFile rf = new RandomAccessFile("out.txt", "rw")) {
            // 将指针移动到out.txt的最后
            rf.seek(rf.length());
            rf.write("输出的内容!\r\n".getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

向指定位置插入数据——
假若直接向指定位置插入数据, 那么新数据就会从指定位置开始覆盖原数据. 那么可使用这样的思路:
(1) 先将指定位置往后的数据(该数据包括该位置本身的数据)存放到一个临时文件
(2) 将指针重新定位到指定位置. 在第1步时指针已进行了移动
(3) 将需要插入的数据从指定位置写入
(4) 从临时文件读取数据, 并写入文件

public static void insert(String filename, long pos, String insertContent) throws IOException {
        // 创建临时文件
        File tmp = File.createTempFile("tmp", null);
        tmp.deleteOnExit();

        try (RandomAccessFile rf = new RandomAccessFile(filename, "rw");
             // 使用临时文件保存插入点后的数据
             FileOutputStream tmpOut = new FileOutputStream(tmp);
             FileInputStream tmpIn = new FileInputStream(tmp);
        ) {
            rf.seek(pos);

            // 以下代码用于向临时文件保存pos位置后的数据
            byte[] bbuf = new byte[64];
            int hasread = 0;
            while((hasread = rf.read(bbuf)) > 0){
                tmpOut.write(bbuf, 0, hasread);
            }

            // 以下代码用于插入内容
            // 将指针重新定位到pos位置
            rf.seek(pos);
            // 追加需要插入的内容
            rf.write(insertContent.getBytes());
            // 从临时文件读取保存的内容
            while((hasread = tmpIn.read(bbuf)) > 0){
                rf.write(bbuf, 0, hasread);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        String filename = "C:\\Users\\peter1950\\Desktop\\T\\out.txt";
        insert(filename, 2, "ee");
    }

九、压缩

classdesc
GZIPInputStream解压用GZIP 文件格式保存的数据
GZIPOutputStream将数据压缩成GZIP 文件格式
ZipInputStream解压用zip 文件格式保存的数据
ZipOutputStream将数据压缩成zip文件格式
CheckedInputStream用于解压文件的校验
CheckedOutputStream用于压缩文件的校验

(1) 单文件压缩


public static void main(String[] args) throws IOException {
        // 用于读文件内容的输入流
        BufferedReader in = new BufferedReader(
                new FileReader("C:\\Users\\peter1950\\Desktop\\T\\out.txt")
        );
        // 用于压缩文件的输出流
        BufferedOutputStream out = new BufferedOutputStream(
                new GZIPOutputStream(
                        new FileOutputStream("C:\\Users\\peter1950\\Desktop\\T\\test.gz")
                )
        );
        System.out.println("Writing file");
        int c;
        while((c = in.read()) != -1){
            out.write(c);
        }
        in.close();
        out.close();


        // 读取压缩文件内容
        System.out.println("Reading file");
        BufferedReader in2 =
                new BufferedReader(
                        new InputStreamReader(
                                new GZIPInputStream(
                                        new FileInputStream("C:\\Users\\peter1950\\Desktop\\T\\test.gz"))));
        String s;

         // 每次读取一行
        while((s = in2.readLine()) != null)
            System.out.println(s);
        in2.close();
    }

在读取文件的时候, 使用了转换流InputStreamReader, 以便能够创建一个字符缓冲流。

(1) 多文件压缩

public static void main(String[] args) throws IOException {
        // 以下代码为压缩文件
        // 创建节点流
        FileOutputStream f = new FileOutputStream("C:\\Users\\peter1950\\Desktop\\T\\test.zip");
        // CheckedOutputStream 用于需要确保压缩文件在压缩跟解压的无误操作
        CheckedOutputStream csum = new CheckedOutputStream(
                f, new Adler32());
        // 创建输出流
        ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(csum));
        // 创建zip文件注释
        out.setComment("A test of Java Zipping");

        for (int i = 0; i < args.length; i++) {
            System.out.println("Writting file" + args[i]);
            BufferedReader in = new BufferedReader(new FileReader(args[0]));
            // 对于要加入压缩档的每一个文件,都必须调用putNextEntry()
            // 表示开始写入新的zip文件条目,并将流定位到条目数据的开头
            out.putNextEntry(new ZipEntry(args[i]));
            int c;
            while((c=in.read()) != -1){
                out.write(c);
            }
            in.close();
        }
        out.close();
        System.out.println("Checksum" +
        csum.getChecksum().getValue());

        // 以下代码读取zip文件内容
        System.out.println("Reading file");
        // 创建节点流
        FileInputStream  fi = new FileInputStream("C:\\Users\\peter1950\\Desktop\\T\\test.zip");
        CheckedInputStream csumi = new CheckedInputStream(fi, new Adler32());
        ZipInputStream in2 = new ZipInputStream(new BufferedInputStream(csumi));
        ZipEntry ze;
        System.out.println("Checksum" + csumi.getChecksum().getValue());
        // 读取下一个zip文件条目
        while((ze = in2.getNextEntry()) != null){
            System.out.println("Reading File " + ze);
            int x;
            while((x = in2.read()) != -1){
                // 输出字符流
                System.out.write(x);
            }
        }
        in2.close();

        // 以下代码为读取文件列表
        ZipFile zf = new ZipFile("C:\\Users\\peter1950\\Desktop\\T\\test.zip");
        Enumeration e = zf.entries();
        // 文件列表
        while(e.hasMoreElements()){
            ZipEntry ze2 = (ZipEntry) e.nextElement();
            System.out.println("File: " + ze2);
        }
    }

CheckedInputStreamCheckedOutputStream 用于实现带验证的操作, 确保文件在解压和压缩时无误。 校验的方式可使用:Adler32(速度要快一些)和 CRC32(慢一些,但更准确)

ZipEntry用于表示压缩文件的条目, 它提供众多方法来获取文件的属性。

ZipFile 可用于读取文件列表, 它的entries()方法返回一个Enumeration, 类似于迭代器(Iterator)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值