Java 高级_IO流

187 阅读24分钟

IO流

File类的使用

  • java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
  • File能新建、删除、重命名文件和目录,但 File不能访问文件内容本身;如果要访问文件内容本身,需要使用输入、输出流
  • 想要在 Java程序中表示一个真实存在的文件或目录,那么必须有一个 File对象,但是 Java程序中的一个 File对象,可能没有一个真实存在的文件或目录
  • File对象可以作为参数传递给流的构造器

常用构造器

  • public File(String pathname):以 pathname为路径创建 File对象,可以是绝对路径或者是相对路径,如果pathname是相对路径,则默认的当前路径在系统属性 user.dir中存储

    • 绝对路径:是一个固定的路径,从盘符开始
    • 相对路径:是相对于某个位置开始
  • public File(String parent, String child):以 parent为父路径,child为子路径创建 File对象

  • public File(File parent, String child):根据一个父 File对象和子文件路径创建 FIle对象

路径分隔符

  • 路径中的每一级目录之间用一个 路径分隔符隔开
  • 路径分隔符和系统有关:
    • windows和 DOS系统默认使用 ''来表示
    • UNIX和URL使用 '/'来表示
  • Java程序支持跨平台运行,因此路径分隔符要慎用
  • 为了解决这个隐患,File提供了一个常量:
    • public static final String separator 根据操作系统,动态的提供分隔符
  • 举例:
    • File file = new File("d:" + File.separator + "java" + File.separator + "info.txt");
package com.atguigu.java3;

import org.junit.Test;

import java.io.File;

/**
 * @author lv
 * @create 2021-01-19 17:25
 *
 * File类的使用
 *
 * 1.File类的一个对象,代表一个文件或一个文件目录(文件夹)
 * 2.File类声明在 java.io包下
 * 3.File类中涉及到关于文件目录的创建、删除、重命名、修改时间、文件大小等方法,
 * 并未涉及到写入或读取文件件内容等操作;如需读取或写入文件内容,需要使用IO流来完成
 * 4.后续 File类的对象常会作为参数传递到流的构造器中,指明读取或写入的 "终点"
 */
public class FileTest {
    /**
     * 1.创建 File类的实例
     * 2.
     *  相对路径:相对于当前文件的 module路径下
     *  绝对路径:包含盘符在内的文件或文件目录的路径
     */
    @Test
    public void test () {
//        构造器1
//        相对路径:相对于当前 module的路径下
        File file = new File("hello.txt");
//        hello.txt

//        绝对路径:
        File file1 = new File("E:\\java_workspace\\JavaSenior\\day08\\he.txt");
//        E:\java_workspace\JavaSenior\day08\he.txt

//        以上只是内存层面的对象
        System.out.println(file);
        System.out.println(file1);

//        构造器2
        File file2 = new File("E:\\java_workspace\\JavaSenior", "java");
        System.out.println(file2);
//        E:\java_workspace\JavaSenior\java

//        构造器3
        File file3 = new File(file2, "hi.txt");
        System.out.println(file3);
//        E:\java_workspace\JavaSenior\java\hi.txt

    }
}

常用方法

File类的获取功能

  • public String getAbsolutePath():获取绝对路径
  • public String getPath():获取路径
  • public String getName():获取名称
  • public String getParent():获取上层文件目录路径,或返回 null
  • public long length():获取文件长度(字节数);不能获取目录的长度
  • public long lastModified():获取最后一次的修改时间,毫秒值
  • public String[] list():获取指定目录下的所有文件或文件目录的名称数组
  • public File[] listFiles():获取指定目录下的所有文件或文件目录的 File数组
    @Test
    public void test1 () {
        File file1 = new File("hi.txt");
        File file2 = new File("E:\\java_workspace\\JavaSenior");

        File absoluteFile1 = file1.getAbsoluteFile();
        System.out.println(absoluteFile1); // E:\java_workspace\JavaSenior\day08\hi.txt
        String path = file1.getPath();
        System.out.println(path); // hi.txt
        String file1Name = file1.getName();
        System.out.println(file1Name); // hi.txt
        String file1Parent = file1.getParent();
        System.out.println(file1Parent); // null
        long length = file1.length();
        System.out.println(length); // 0
        long lastModified = file1.lastModified();
        System.out.println(lastModified); // 0

//        如下两个方法适用于文件目录
        String[] list = file2.list();
        for (String s : list) {
            System.out.println(s); // .idea day01 ...
        }
        System.out.println(list); // null
        File[] files = file2.listFiles();
        System.out.println(files); // null
    }

File类的重命名功能

  • public Boolean renameTo(File dest):将文件重命名为指定的文件路径
    /**
     * file.renameTo(File dest):重命名
     * 注意:保证成功,需要 file1在硬盘中是存在的,且 file2中的文件不存在
     */
    @Test
    public void test2 () {
        File file = new File("hii.txt");
        File file1 = new File("E:\\java_workspace\\JavaSenior\\day08\\hi.txt");
        boolean b = file.renameTo(file1);
        System.out.println(b);
    }

File类的判断功能

  • public boolean isDirectory():判断是否是文件目录
  • public boolean isFile():判断是否是文件
  • public boolean exists():判断是否存在
  • public boolean canRead():判断是否可读
  • public boolean canWrite():判断是否可写
  • public boolean isHidden():判断是否隐藏
    /**
     * File类判断功能
     *
     */
    @Test
    public void test3 () {
        File file = new File("hi.txt");
        System.out.println(file.isDirectory()); // false
        System.out.println(file.isFile()); // true
//        先判断文件是否存在
        System.out.println(file.exists()); // true
        System.out.println(file.canRead()); // true
        System.out.println(file.canWrite()); // true
        System.out.println(file.isHidden()); // false

    }

File类的创建功能

  • public boolean createNewFile():创建文件;若文件存在则不创建,返回 false

  • public boolean mkdir():创建文件目录;如果此文件目录存在或上层文件目录不存在,就不创建了

  • public boolean mkdirs():创建文件目录;如果上层文件目录不存在,一并创建

  • 注意:如果创建的文件或者文件目录没有写盘符路径,那么,默认在项目路径下

File类的删除功能

  • public boolean delete():删除文件或者文件夹

  • 注意:Java中的删除不走回收站;要删除一个文件目录,请注意该文件目录内不能包含问及那或文件目录

    /**
     * 创建硬盘中对应的文件或文件目录
     *
     * 删除硬盘中的文件或文件目录
     *
     */
    @Test
    public void test4 () throws IOException {

//        文件的创建 createNewFile()
        File file = new File("hello.txt");
        if (!file.exists()) {
            file.createNewFile();
            System.out.println("create Success!");
        } else {
            file.delete();
            System.out.println("file is Delete!");
        }
//        文件目录的创建
        File file1 = new File("E:\\java_workspace\\JavaSenior\\day08\\test1\\test3");
        boolean mkdir = file1.mkdir();
        if (mkdir) {
            System.out.println("mkdir Success!");
        }
        File file2 = new File("E:\\java_workspace\\JavaSenior\\day08\\test2\\test3");
        boolean mkdirs = file2.mkdirs();
        if (mkdirs) {
            System.out.println("mkdirs Success!");
        }
    }

IO流原理及流的使用

Java IO原理

  • I/O是input/output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输;如 读写文件、网络通讯等
  • Java程序中,对于数据的输入输出操作以 “流(stream)”的方式进行
  • java.io包下提供了各种 “流”类和接口,用于获取不同种类的数据,并通过标准的方法输入、输出数据

基于内存的角度,看输入输出流:

  • 输入input:读取外部数据(磁盘、光盘等存储设备中)到程序(内存)中
  • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

流的分类

  • 按操作数据单位不同:字节流(8bit(图片、视频...)),字符流(16bit(.txt))
  • 按数据的流向不同:输入流,输出流
  • 按流的角色不同:节点流(直接作用在文件上的流),处理流(在已有的流上新添加的)
  • 以后缀判断流的分类

抽象基类 字节流 字符流

输入流 InputStream Reader

输出流 OutputStream Writer


  1. Java的 IO流共涉及 40多个类,实际上非常规范,都是从如上四个抽象基类派生的
  2. 由这四个类排生出来的子类名称都是以其父类名作为子类名后缀

节点流(或文件流)

FileInputStream、FileOutputStream

  • read(byte[] buffer)

  • write(byte[] buffer, 0, len)

  • 对于文本文件(.txt、.java、.c、.cpp)使用字符流处理

  • 对于非文本文件(图片、音频、视频、.ppt、.doc、...),使用字节流处理

FileInputStream

FileOutputStream

实例


// 图片加密、解密
package com.atguigu.exer;

import org.junit.Test;

import java.io.*;

/**
 * @author lv
 * @create 2021-01-27 19:10
 */
public class PicTest {
    /**
     * 图片的解密
     */
    @Test
    public void test1 () {
        File fr = new File("io流体系secret.png");
        File fw = new File("io流体系01.png");

        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream = new FileInputStream(fr);
            fileOutputStream = new FileOutputStream(fw);

            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) != -1) {
//                数据流解密
                for (int i = 0; i < len; ++i) {
//                    异或 5
                    bytes[i] = (byte) (bytes[i] ^ 5);
                }
                fileOutputStream.write(bytes, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null)
                    fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fileOutputStream != null)
                    fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    /**
     * 图片的加密:对原有字节进行调整
     */
    @Test
    public void test () {
        File fr = new File("io流体系.png");
        File fw = new File("io流体系secret.png");

        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream = new FileInputStream(fr);
            fileOutputStream = new FileOutputStream(fw);

            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) != -1) {
//                数据流加密
                for (int i = 0; i < len; ++i) {
//                    异或 5
                    bytes[i] = (byte) (bytes[i] ^ 5);
                }
                fileOutputStream.write(bytes, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null)
                    fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fileOutputStream != null)
                    fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

package com.atguigu.java;

import org.junit.Test;

import java.io.*;

/**
 * @author lv
 * @create 2021-01-25 20:44
 */
public class FileInputOutputStreamTest {
    /**
     * 实现对图片的复制操作
     */
    @Test
    public void testIO () {
//        1.创建 File类对象,指明写入和写出文件
        File fInput = new File("io流体系.png");
        File fOutput = new File("io流体系1.png");

//        2.创建处理流对象
        FileInputStream fr = null;
        FileOutputStream fw = null;
        try {
            fr = new FileInputStream(fInput);
            fw = new FileOutputStream(fOutput);

//            3.读取数据
            byte[] bbuf = new byte[6];
            int len;
            while ((len = fr.read(bbuf)) != -1) {
//                3.写入数据
                fw.write(bbuf, 0, len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            4.关闭流
            try {
                if (fr != null)
                    fr.close();
                System.out.println("数据读取完毕!");
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fw != null)
                    fw.close();
                System.out.println("数据写入完毕!");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileReader、FileWriter

  • read(char[] cbuf)
  • write(char[] cbuf, 0, len)

共分为以下四步:

FileReader

  • read():的理解:返回读入的一个字符,如果达到文件末尾,返回 -1
  • 异常的处理:为保证流资源一定可以执行关闭操作,需要使用 try-catch-finall
  • 读入的文件一定要存在,否则就会报异常 FileNotFoundException
package com.atguigu.java;


import org.junit.Test;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/**
 * @author lv
 * @create 2021-01-21 20:27
 */
public class FileReaderWriterTest {
    public static void main(String[] args) {
        File file = new File("day09\\hello.txt");
        System.out.println(file.getAbsolutePath());
        // E:\java_workspace\JavaSenior\day09\hello.txt
    }

    /**
     * 将 dau09下的 hello.txt的内容读入到程序中,并输出到控制台
     */
    @Test
//    public void testFileRWTest () throws IOException {
    public void testFileRWTest () {
//        1.实例化 file类的对象,指明被操作的文件
        FileReader fr = null;
        try {
            File file = new File("hello.txt");
            System.out.println(file.getAbsolutePath());
            // E:\java_workspace\JavaSenior\day09\hello.txt

//        2.提供对应的流
            fr = new FileReader(file);

//        3.数据的读入
//        read():返回读入的一个字符,如果达到文件末尾,返回-1

//        方式一:
//        int data = fr.read();
//        while (data != -1) {
//            System.out.print((char)data); // helloworld
//            data = fr.read();
//        }
//        方式二:语法上的修改
            int data;
            while ((data = fr.read()) != -1) {
                System.out.println((char)data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //        4.流的关闭操作(一定要关闭)
            try {
//                避免 fr实例化时报异常,导致的空指针问题
                if (fr != null) {
                    fr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }
}

FileWriter

  • 对应的 File可以不存在,如果不存在,在输出的过程中,会自动创建此文件
  • 如果 File存在,true表示在原有文件内容的基础上进行添加,false表示对原有文件内容进行覆盖
    /**
     * 从内存中写出数据到硬盘的文件中
     */
    @Test
    public void testFileWTest () {
//        1.提供 File类的对象,指明写出到的文件
        File file = new File("hello1.txt");
//        2.提供 xxx流的对象,用于数据的写出
        FileWriter fw = null;
        try {
//            append: true 在原有文件内容上进行添加
//            fw = new FileWriter(file, true);

//            append: false 对原有文件的内容进行覆盖
            fw = new FileWriter(file, false);
            //        3.写出操作
            fw.write("I have a dream! ");
            fw.write("You need to have a dream!");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            关闭 xxx流资源
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

实例

    /**
     * 文件的复制
     */
    @Test
    public void testRW () {
//        1.创建 File类对象,指明读入和写出文件
//        inputFile 此对象的源文件必须存在
        File inputFile = new File("hello.txt");
//        outputFile 此对象的源文件可以不存在
        File outputFile = new File("hello1.txt");

//        2.创建 输入、输出流的对象
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(inputFile);
            fw = new FileWriter(outputFile);
            //        3.读取、写入流数据

            char[] cbuf = new char[6];
            int len;
            while ((len = fr.read(cbuf)) != -1) {
//                方法三:
//                for (int i = 0; i < len; ++i) {
//                    fw.write(cbuf[i]);
//                }
//                方法一:
//                String str = new String(cbuf, 0, len);
//                fw.write(str);
//                方法二:
                fw.write(cbuf, 0, len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //        4.关闭流
            try {
                if (fw != null) {
                    fw.close();
                    System.out.println("写入完毕!");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (fr != null) {
                    fr.close();
                    System.out.println("读取完毕!");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

缓冲流

  • 作用:提高文件的读写效率
  • 实际开发中应优先考虑使用缓冲流

BufferedInputStream、BufferedOutputStream

  • read(byte[] buffer)
  • write(byte[] buffer, 0, len)

实例

package com.atguigu.java;

import org.junit.Test;

import java.io.*;

/**
 * @author lv
 * @create 2021-01-26 19:39
 *
 * 缓冲流的使用
 *
 * 1.字节:BufferedInputStream、BufferedOutputStream
 *
 * 2.字符:BufferedReader、BufferedWriter
 *
 * 3.提高 流读取和写入的速度
 *  速度提高的原因:内部提供了读写的缓冲区
 *
 * 4.处理流就是 "套接" 在已有的 流之上
 *
 *
 */
public class BufferedTest {
    @Test
    public void bufferedIOStreamTest () {
//        1.创建 File对象,获取文件
        File fInput = new File("F:\\Java\\java_base\\595\\48144058_595_0.flv");
        File fOutput = new File("F:\\Java\\java_base\\595\\48144058_595_copy01.flv");

//        2.创建 xxx流对象
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        BufferedInputStream bInput = null;
        BufferedOutputStream bOutput = null;
        long start = 0L;
        long end;
        try {
            start = System.currentTimeMillis();
            fileInputStream = new FileInputStream(fInput);
            fileOutputStream = new FileOutputStream(fOutput);
            bInput = new BufferedInputStream(fileInputStream);
            bOutput = new BufferedOutputStream(fileOutputStream);

//            3.读取数据
            byte[] bytes = new byte[1024];
            int len;
            while ((len = bInput.read(bytes)) != -1) {
                bOutput.write(bytes, 0, len);

//                bOutput.flush(); 手动刷新缓冲区
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            4.关闭流:由外向内关闭
            try {
                if (bInput != null)
                    bInput.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bOutput != null)
                    bOutput.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
//            说明:关闭外层流的同时,内层流也会自动关闭,所以内层流的关闭可以省略
//            try {
//                if (fileInputStream != null)
//                    fileInputStream.close();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//            try {
//                if (fileOutputStream != null)
//                    fileOutputStream.close();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }

            end = System.currentTimeMillis();
            System.out.println("花费的时间为:" + (end - start)); // 491
        }


    }

}

BufferedReader、BufferedWriter

  • read(char[] cbuf) / readline()
  • write(char[] cbuf, 0, len) / write(String str)

实例

    @Test
    public void bufferedRWTest () {
//        1.创建 File对象,获取文件
        File fr = new File("hello.txt");
        File fw = new File("helloBuffer.txt");

//        2.创建 xxx流对象
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            fileReader = new FileReader(fr);
            fileWriter = new FileWriter(fw);

            br = new BufferedReader(fileReader);
            bw = new BufferedWriter(fileWriter);

//              3.读写文件
//            方式一:char[]
//            char[] chars = new char[1024];
//            int len;
//            while ((len = br.read(chars)) != -1) {
//                bw.write(chars, 0, len);
//            }

//            方式二:String
            String str;
//            readline():一次读一行,不包括换行符
            while ((str = br.readLine()) != null) {
//                方法一:
//                bw.write(str + "\n"); // 不包括换行符
//                方法二:
                bw.write(str); // 不包括换行符
                bw.newLine(); // 添加换行符
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            4.关闭流
            try {
                if (br != null)
                    br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bw != null)
                    bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

转换流

  • 转换流提供了在字节流和字符流之间的转换

  • Java API提供了两个转换流(字符流):

    • InputStreamReader:将 InputStream转换为 Reader(将字节的输入流转换为字符的输入流)
    • OutputStreamWriter:将 Writer转换为 OutputStream(将字符的输出流转换为字节的输出流)
  • 字节流中的数据都是字符时,转成字符流操作更高效

  • 很多时候,我们使用转换流来处理文件乱码问题,实现编码和解码的功能

解码与编码:

  • 解码:字节、字节数组 --> 字符数组、字符串

  • 编码:字符数组、字符串 --> 字节、字节数组

  • 字符集:见下

InputStreamReader、OutputStreamWriter

package com.atguigu.java;

import org.junit.Test;

import java.io.*;

/**
 * @author lv
 * @create 2021-01-28 19:36
 */
public class IORWTest {
    /**
     * InputStreamReader、OutputStreamWriter的混合使用
     *
     */
    @Test
    public void test1 () {
//        1.指定文件
        File fr = new File("hello.txt");
        File fw = new File("hello_gbk.txt");

//        创建对应的流
//        字节流
        FileInputStream fis = null;
        FileOutputStream fos = null;

//        字符流
        InputStreamReader isr = null;
        OutputStreamWriter osw = null;

        try {
            fis = new FileInputStream(fr);
            fos = new FileOutputStream(fw);

//            指定文件的字符集,忽略大小写
            isr = new InputStreamReader(fis, "UTF-8");
            osw = new OutputStreamWriter(fos, "gbk");
//            3.读写流
            char[] chars = new char[20];
            int len;
//            输入流:字节转字符(选择字符集)
            while ((len = isr.read(chars)) != -1) {
//                输出流:字符转字节(选择字符集)
                osw.write(chars, 0, len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            4.关闭流
            try {
                if (isr != null)
                    isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (osw != null)
                    osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * InputStreamReader的使用
     * 解码操作
     */
    @Test
    public void test () {
        FileInputStream fis = null;
        InputStreamReader isr = null;
        try {
//            字节流
            fis = new FileInputStream("hello.txt");
//            isr = new InputStreamReader(fis, "UTF-8");
//            字符集指定依据:根据源文件保存时的字符集为依据

//            字符流
            isr = new InputStreamReader(fis); // 默认字符集

            char[] cbuf = new char[20];
            int len;
            while ((len = isr.read(cbuf)) != -1) {
                String str = new String(cbuf, 0, len);
                System.out.print(str);
            }

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

字符集

  • 分类
    1. ASCII:美国标准信息交换码,用一个字节的 7位表示
    2. ISO8859-1:拉丁码,欧洲码表,用一个字节的 8位表示
    3. GB2312:中国的中文编码表,最多两个字节编码所有字符
    4. GBK:中文编码表升级,融合了更多的中文文字符号,最多两个字节编码
    5. Unicode:国际标准码,融合了目前人类所有字符,所有的文字都用两个字节来表示
    6. UTF-8:变长的编码方式,可用 1-4个字节来表示一个字符

标准输入、输出流 -

概念:

  • System.in和 System.out分别代表了系统标准的输入和输出设备
  • 默认输入设备是:键盘;输出设备是:显示器
  • System.in的类型是 InputStream
  • System.out的类型是 printStream,其是 OutputStream的子类 FilterOutputStream的子类
  • 重定向:通过 System类的 setIn()、setOut方法对默认设备进行改变
    1. public static void setIn(InputStream is)
    2. public static void setOut(PrintStream out)

示例:

package com.atguigu.java;

import org.junit.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * @author lv
 * @create 2021-02-02 23:09
 *
 * 其它流的使用
 *
 * 1.标准的输入、输出流
 * 2.打印流
 * 3.数据流
 *
 *
 */
public class OtherStreamTest {
    /**
     * 标准的输入、输出流
     * 分类:
     * 1.System.in:标准的输入流,默认从键盘输入
     * 2.System.out:标准的输出流,默认从控制台输出
     * 方法:
     * System类的 setIn(InputStream is) / setOut(PrintStream ps)方法
     * 重新指定输入和输出的流
     *
     * 练习:
     * 从键盘输入字符串,要求将读取到的整行字符串转换成大写输出;
     * 然后继续进行输入操作,直至但输入 ‘e’或者 ‘exit’时,退出程序
     *
     * 方法一:使用 Scanner实现,使用 next()返回一个字符串
     *
     * 方法二:使用 标准输入、输出流 System.in --> 转换流 --> BufferedReader 的 readLine()
     *
     */
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            InputStream in = System.in;
//            System.out.println("in:" + in); 1
            InputStreamReader isr = new InputStreamReader(in);
            br = new BufferedReader(isr);

            while (true) {
                System.out.print("请输入字符串:");
                String str = br.readLine();
//            if (str.equalsIgnoreCase("e") || str.equalsIgnoreCase("exit")) {
//            更好的避免空指针的问题
                if ("e".equalsIgnoreCase(str) || "exit".equalsIgnoreCase(str)) {
                    System.out.println("程序结束");
                    break;
                }
                String upperCase = str.toUpperCase();
                System.out.println(upperCase);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null)
                    br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    @Test
    public void test () {
    }
}

打印流 -

PrintStream、PrintWriter

概念:

  • 实现将基本数据类型的数据格式转化为字符串输出

  • 打印流:PrintStream、PrintWriter

    • 提供了一系列重载的 print()和 print()方法,用于多种数据类型的输出
    • PrintStream和 PrintWriter的输出不会抛出 IOException
    • PrintStream和 PrintWriter有自动 flush功能
    • PrintStream打印的所有字符都使用平台的默认字符编码转化为字节;在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter类
    • System.out返回的是 PrintStream的实例

示例:

    /**
     * 打印流:PrintStream和 PrintWriter
     * 提供了一系列重载的 print()和 println()
     *
     * System.setOut应用(重新指定打印位置):
     * 将打印的数据不是在控制台输出而是保存在 指定文件中
     *
     */
    @Test
    public void test2 () {
        File file = new File("hello_fos.txt");
        FileOutputStream fos = null;
        PrintStream ps = null;
        try {
            fos = new FileOutputStream(file);
//            创建打印输出流,设置为自动刷新模式(写入换行符或字节'\n'时都会刷新输出缓冲区)
            ps = new PrintStream(fos);
            if (ps != null) {
//                将标准输出流(控制台输出)改成文件
                System.setOut(ps);
            }
//            输出 ASCII字符
            for (int i = 0; i <= 255; ++i) {
                System.out.print((char)i);
//                每 50个数据换行
                if (i % 50 == 0) {
                    System.out.println();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (null != ps)
                ps.close();
        }
    }

数据流 -

DataInputStream、DataOutputStream

概念:

  • 作用:用于 读入或 写出 基本数据类型的 变量或 字符串

  • 为了方便地操作 Java语言的 基本数据类型和 String的数据,可以使用数据流

  • 数据流有两类:(用于读取和写出基本数据类型、String类的数据)

    • DataInputStream、DataOutputStream
    • 分别 ‘套接’在 InputStream和 OutputStream子类的流上
  • DataInputStream中的方法

    1. boolean readBoolean()
    2. char readChar()
    3. double readDouble()
    4. long readLong()
    5. String readUTF()
    6. byte readByte()
    7. float readFloat()
    8. short readShort()
    9. int readInt()
    10. void readFully(byte[] b):字节数组
  • DataOutputStream中的方法

    • 将上述方法的 read改为对应的 write即可

示例:

    /**
     * 数据流
     * DataInputStream和 DataOutputStream
     *
     * 作用:用于读入或写出基本数据类型的变量或字符串
     *
     */
    @Test
    public void test3 () {
//        写入:将变量、字符串数据保存在文件中
        File file = new File("hello_Data.txt");
        FileOutputStream os = null;
        DataOutputStream dos = null;
        try {
            os = new FileOutputStream(file);
            dos = new DataOutputStream(os);
            dos.writeUTF("Tom");
            dos.flush(); // 显示的刷新到对应的文件中
            dos.writeInt(23);
            dos.flush();
            dos.writeBoolean(true);
            dos.flush();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != dos)
                dos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
//        读取:将文件中的变量、字符串读入到内存中
        File fr = new File("hello_Data.txt");
        FileInputStream fis = null;
        DataInputStream dis = null;
        try {
            fis = new FileInputStream(fr);
            dis = new DataInputStream(fis);
            // 读取顺序和上方的写入顺序一致
            String name = dis.readUTF();
            int age = dis.readInt();
            boolean bool = dis.readBoolean();
            System.out.println("name: " + name + ", age: " + age + ", gender: " + bool);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != dis)
                    dis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

对象流*

ObjectInputStream、ObjectOutputStream

  • 用于存储和读取基本数据类型数据或对象的处理流;它的强大之处就是可以把Java中的对象写入到数据源中,也可以把对象从数据源中还原回来

序列化:内存中的对象 -> 存储中的文件、网络传输

  • 用 ObjectOutputStream类保存基本类型数据或对象的机制

反序列化:存储中的文件、网络传输 -> 内存中的对象

  • 用 ObjectInputStream类读取基本类型数据或对象的机制

ObjectOutputStream和 ObjectInputStream不能序列化 statictransient修饰的成员变量

对象的序列化

允许把内存中的 Java对象转换成与平台无关的二进制流,从而把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点;当其它程序获取了这种二进制流,就可以恢复原来的 Java对象

  • 序列化的好处在于可将任何实现了 Serializable接口的对象转化为 字节数据,使其在保存和传输时可被还原

  • 序列化是 RMI(Remote Method Invoke - 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI是 JavaEE的基础;因此序列化机制是 JavaEE平台的基础

  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一,否则,会抛出 NotSerializableException异常

    • Serializable
    • Externalizable
  • 凡是实现 Serializable接口的类都必须有一个表示序列化版本标识符的静态I变量:

    • public static final long serialVersionUID = xxxL;
    • serialVersionUID用来表示类的不同版本间的兼容性;其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
    • 如果没有显示定义,Java运行时环境根据内部细节自动生成,建议显示声明
  • 类的可序列化注意事项:

    1. 需要实现接口:Serializable 标识接口
    2. 为当前类提供一个常量:serialVersionUID
    3. 除了当前 Person类需要实现 serialVersionUID接口之外,还必须保证其内部属性也必须是可序列化的(默认,基本数据类型可序列化)

实例

package com.atguigu.java;

import java.io.Serializable;

/**
 * @author lv
 * @create 2021-02-13 16:19
 *
 * 标识接口:没有抽象方法
 *
 * 1.需要实现接口:Serializable 标识接口
 * 2.为当前类提供一个常量:serialVersionUID
 * 3.除了当前 Person类需要实现 serialVersionUID接口之外,
 * 还必须保证其内部属性也必须是可序列化的(默认,基本数据类型可序列化)
 */
public class Person implements Serializable {
//    serialVersionUID:序列版本号,用于标识
    public static final long serialVersionUID = 51215452461L;
//    private static String name; // 不能序列化 static、transient修饰的成员变量
//    private transient int age;

    private String name;
    private int age;
    private Account acct;

    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", acct=" + acct +
                '}';
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, int age, Account acct) {
        this.name = name;
        this.age = age;
        this.acct = acct;
    }

    public Account getAcct() {
        return acct;
    }

    public void setAcct(Account acct) {
        this.acct = acct;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

class Account implements Serializable {

    public static final long serialVersionUID = 51215822461L;

    private double balance;

    public Account() {
    }

    public Account(double balance) {
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }
}
package com.atguigu.java;

import org.junit.Test;

import java.io.*;

/**
 * @author lv
 * @create 2021-02-13 15:22
 *
 * 对象流的使用
 * 1.ObjectInputStream和 ObjectOutputStream
 * 2.作用:用于存储和读取基本数据类型数据或对象的处理流
 */
public class ObjectInputOutputTest {
    /**
     * 反序列化过程:读取磁盘文件中的 对象、数据
     *
     */
    @Test
    public void testObjectInputStream () {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("oosTest.dat"));
//            读:
            Object strObj = ois.readObject();
            String str = (String)strObj;
            System.out.println(strObj.toString() + " : " + str);
//            Person类
            Object obj = ois.readObject();
            Person p1 = (Person)obj;
            System.out.println(p1.toString());
            Person p2 = (Person)ois.readObject();
            System.out.println(p2);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != ois)
                    ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 序列化过程:将内存中的 Java对象保存到磁盘中或通过网络传输出去
     */
    @Test
    public void testObjectOutputStream () {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("oosTest.dat"));
//            写:
            oos.writeObject(new String("我爱你,北京"));
            oos.flush(); // 主动刷新

            oos.writeObject(new Person("Tom", 22));

            oos.flush(); // 主动刷新

            oos.writeObject(new Person("Tom1", 25, new Account(123.0)));
            oos.flush();

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

任意(随机)存取文件流

可用于多线程断点下载文件功能

RandomAccessFile类

RandomAccessFile声明在 java.io包下,但直接继承于 java.lang.Object类;并且它实现了 DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写

  • RandomAccessFile类支持 ‘任意访问’ 的方式,程序可以直接跳到文件的任意地方来 读、写 文件

    • 支持只访问文件的部分内容
    • 可以向已存在的文件后追加内容
  • 如果 RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建;如果写出到的文件存在,则会对源文件内容进行覆盖(默认,从头覆盖)

  • RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置;RandomAccessFile类对象可以自由移动记录指针:

    • long getFilePointer():获取文件记录指针的当前位置
    • void seek(long pos):将文件记录指针定位到 pos 位置

构造器

  • public RandomAccessFile(File file, String mode)
  • public RandomAccessFile(String name, String mode)
  • 创建 RandomAccessFile类实例需要指定一个 mode参数,该参数指定 RandomAccessFile的访问模式:
    • r:以只读方式打开
    • rw:打开以便读取和写入
    • rwd:打开以便读取和写入;同步文件内容的更新
    • rws:打开以便读取和写入;同步文件内容和元数据的更新
  • 模式为只读r,不会创建文件,只会读取已经存在的文件,文件不存在会报异常;模式为读写rw,如果文件不存在会创建文件

实例

package com.atguigu.java;

import org.junit.Test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author lv
 * @create 2021-02-14 15:55
 *
 * RandomAccessFile
 *
 * 1.实现了 DataInput和 DataOutput接口
 * 2.以输出流方式,在已存在文件内容的开头位置进行覆盖
 *
 */
public class RandomAccessFileTest {
    /**
     * 使用 RandomAccessFile 实现插入功能
     */
    @Test
    public void test3 () {
        File file = new File("rafTest.txt");
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(file, "rw");

            raf.seek(3);

//            StringBuilder 用于保存指针3后的所有数据
//            思考:将 StringBuilder替换为 ByteArrayOutputStream
            StringBuilder sb = new StringBuilder((int)file.length());
            byte[] bytes = new byte[20];
            int len;
            while ((len = raf.read(bytes)) != -1) {
                sb.append(new String(bytes, 0, len));
            }

//            调回指针,写入数据 “xyz”
            raf.seek(3);
            raf.write("xyz".getBytes());

//            将 StringBuilder保存的数据放回到文件中
            raf.write(sb.toString().getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != raf)
                    raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * RandomAccessFile 输出时,对文件内容的覆盖
     * 1.在已存在文件内容的开头位置进行覆盖(默认)
     */
    @Test
    public void test2 () {
        RandomAccessFile raf = null;
        File file = new File("rafTest.txt");
        try {
            raf = new RandomAccessFile(file, "rw");
            // 将指针调到角标为 3的位置

//            raf.seek(3);
//            file.length() 获取文件内容长度
            raf.seek(file.length());
            raf.write("xyz".getBytes());
//            raf.write
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != raf)
                    raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    @Test
    public void test1 () {

        RandomAccessFile raf = null;
        RandomAccessFile raf1 = null;
        try {
            raf = new RandomAccessFile(new File("io流体系.png"), "r");
            raf1 = new RandomAccessFile(new File("io流体系1.png"), "rw");

            byte[] bytes = new byte[1024];
            int len;
            while ((len = raf.read(bytes)) != -1) {
                raf1.write(bytes, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != raf)
                    raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (null != raf1)
                    raf1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

NIO.2中 Path、Paths、Files类的使用

Java NIO概述

  • Java NIO(New IO,Non-Blocking IO)是从 Java 1.4版本开始引入的一套新的 IO API,可以替代标准的 Java IO API;NIO与原来的 IO有同样的作用和目的,不同是,NIO支持面向缓冲区的(IO是面向流的)、基于通道的 IO操作;NIO将以更加高效的方式进行文件的读写操作
  • Java API中提供了两套 NIO,一套针对标准输入输出 NIO,另一套就是网络编程 NIO
    • java.nio.channels.Channel

NIO.2

随着 JDK7的发布,Java对 NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称之为 NIO.2;目前 NIO.2已经称为文件处理中越来越重要的部分

Path、Paths和 Files核心API

  • 早期的 Java只提供了一个 File类来访问文件系统,但 File类的功能比较有限,提供的方法性能不高;而且,大多数方法在出错后仅返回失败,并不会提供异常信息

  • NIO.2为了弥补此不足,引入了 Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置;Path可以看做 File类的升级版,实际引用的资源也可以不存在

  • 以前 IO操作的写法:

import java.io.File;
File file = new File("xxx.html");
  • 在 Java7中,我们可以这样写:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("xxx.html");
  • 同时,NIO.2在 java.nio.file包下还提供了 Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回 Path的静态工厂方法

  • Paths类提供的静态 get()方法用来获取 Path对象:

    • static Path get(String first, String ...more):用于将多个字符串串连成路径
    • static Path get(URL url):返回指定 url对应的 Path路径

Path接口

Path常用方法:

  • ...

Files类

  • java.nio.file.Files 用于操作文件或目录的工具类

Files常用方法:

  • ...

练习

说明流的三种分类方式:

  • 流向:输入流、输出流
  • 数据单位:字符流、字节流
  • 流的角色:节点流、处理流

写出 4个 IO流的抽象基类,4个文件流,4个缓冲流

抽象基类:

  • InputStream、OutputStream
  • Reader、Writer

文件流:

  • FileInputStream、FileOutputStream
  • FileReader、FileWriter

缓冲流:

  • BufferedInputStream、BufferedOutStream
  • BufferedReader、BufferedWriter

字节流与字符流的区别与使用情景

字节流:

主要用于处理非文本文件,图片、视频、doc、ppt...等文件

字符流:

主要用于处理文本文件,txt...等

转换流是哪两个,分别的作用是,请分别创建两个类的对象

// 将输入的字节流转换为输入的字符流
new InputStreamReader(new FileInputStream("hello.txt"), "UTF-8")
// 将输出的字符流转换为输出的字节流
new OutputStreamWriter(new FileOutputStream("hello_copy.txt"), "GBK")

输入、输出 标准化过程

输入过程

  • 创建 File类对象,指明读取的数据的来源(此文件一定要存在)

  • 创建相应的输入流,将 File类的对象作为参数,传入流的构造器中

  • 具体的读入过程

    • 创建相应的 byte[] 或 char[]
  • 关闭流资源

  • 程序中的异常使用 try-catch-finally处理

输出过程

  • 创建 File类对象,指明 写出的数据的位置(此文件不一定要存在)
  • 创建相应的输出流,将 File类的对象作为参数,传入流的构造器中
  • 具体的写出过程:
    • write(char[] / byte[] buffer, 0, len)
  • 关闭流资源
  • 程序中的异常使用 try-catch-finally处理