u04-流类基础

204 阅读8分钟

1. I/O流概念入门

概念: 大多数的应用程序都需要与外部设备(磁盘、硬盘和网络等)进行数据交换,I/O流指的就是应用程序和这些设备之间的各种各样的数据交换方式,我们可以将他们理解为一根根功能各异的"管道"。

  • 流的分类:
    • 按方向:输入流/输出流,都是站在程序代码的角度来辨别方向。
    • 按处理数据方式:字节流/字符流,字节流以二进制方式进行数据传输,字符流是以字符为单位进行数据传输。
    • 按功能:节点流/处理流,节点流是对数据直传输,处理流会在数据传输过程中对数据进行二次处理后再传输。
  • java中有很多负责I/O数据交换的类,都在 java.io 包中:
    • File:表示文件本身的类。
    • RandomAccessFile:表示允许随机位置访问的文件本身的类。
    • InputStream:表示输入字节流的抽象类。
    • OutputStream:表示输出字节流的抽象类。
    • Reader:表示输入字符流的抽象类。
    • Writer:表示输出字符流的抽象类。

带有中文的纯文本内容推荐使用字符流,图片,影音等二进制内容推荐使用字节流。

2. File

概念: File类是I/O包中唯一代表硬盘文件本身的类,通过它可以对文件进行创建,删除,重命名,设置读写权限等操作。

  • 构造器:
    • new File(String filename):filename是路径和文件名的整体字符串。
    • new File(String parent, String child):parent是目录字符串,child是文件字符串。
    • new File(File parent, String child):parent是目录对应的文件,child是文件字符串。
    • java种的路径支持 UNIXWINDOWS/DOS 约定的路径分隔符 /\\ 这两种写法,建议直接使用 File.separator 来动态获取。
  • 常用API方法:
    • boolean exists():判断文件/目录是否存在。
    • boolean mkdir():创建单层目录。
    • boolean mkdirs():创建多层目录。
    • void delete():删除文件/目录,彻底删除,不进回收站,删除文件夹的前提是,文件夹是空文件夹。
    • void deleteOnExit():在JVM终止时删除文件,只有在JVM正常终止时,才会尝试执行删除操作。
    • boolean createNewFile():创建一个文件。
    • String getName():得到文件/目录的名称。
    • String getPath():得到文件/目录的路径。
    • long length():得到文件字节数。
    • String getParent():返回父目录的路径名,没有父目录返回null。
    • long lastModified():返回文件最后一次被修改的时间戳,文件不存在或发生异常都会返回0L。
    • boolean canRead():判断文件是否可读(能查看但是不能操作和存储)。
    • boolean canWrite():判断文件是否可写(能写入但是看不见)。
    • boolean isFile():判断文件是否是文件。
    • boolean isDirectory():判断文件是否是目录。
    • boolean isHidden():返回文件是否是一个隐藏文件。
    • boolean isAbsolute:返回文件是否是绝对路径(从盘符出发就是绝对路径)。
    • String getAbsolutePath():返回文件的绝对路径。
    • boolean renameTo(File file):重命名为新的file实例的名字注意参数中的file实例不要创建出来。
    • boolean equals(File file):判断文件/目录是否是同一个。

源码: /javase-advanced/

  • src: c.y.io.FileTest

/**
 * @author yap
 */
public class FileTest {

    private File file;

    @Before
    public void before() throws IOException {
        String dirPath = "D:" + File.separator + "java-io";
        File dirs = new File(dirPath);
        if (!dirs.exists()) {
            System.out.println(dirs.mkdirs() ? "目录创建成功" : "目录创建失败");
        }
        String filePath = "start.txt";
        this.file = new File(dirPath, filePath);
        if (this.file.exists()) {
            System.out.println(this.file.delete() ? "文件删除成功" : "文件删除失败");
        }
        System.out.println(this.file.createNewFile() ? "文件创建成功" : "文件创建失败");
    }

    @Test
    public void fileApi() {
        System.out.println("文件名:" + file.getName());
        System.out.println("文件路径:" + file.getPath());
        System.out.println("文件大小:" + file.length());
        System.out.println("父目录:" + file.getParent());
        System.out.println("最后修改日期:" + new Date(file.lastModified()));
        System.out.println(file.canRead() ? "可读" : "不可读");
        System.out.println(file.canWrite() ? "可写" : "不可写");
        System.out.println(file.isFile() ? "是文件" : "不是文件");
        System.out.println(file.isDirectory() ? "是目录" : "不是目录");
        System.out.println(file.isHidden() ? "隐藏文件" : "非隐藏文件");
        System.out.println(file.isAbsolute() ? "是绝对路径" : "不是绝对路径");
        System.out.println("文件绝对路径:" + file.getAbsolutePath());
        File newFile = new File("D:\\java\\io\\build-1.txt");
        System.out.println(file.renameTo(newFile) ? "修改成功" : "修改失败");
        System.out.println(file.equals(newFile) ? "是同一个" : "非同一个");
    }
}

3. RandomAccessFile

概念: RandomAccessFile可以从指定的位置开始访问一个文件,它具有一个位置指示器,负责指向当前读写处的位置,默认位置是文件的开头,我们可以自己设定指示器的初始位置,读写n个字节后,指示器指向n个字节后的下一个字节处。

  • 构造器:new RandomAccessFile(String name, String mode)
    • p1:文件路径,如果文件不存在会帮忙创建,但是如果目录不存在直接报错。
    • p2:文件读写模式,r 表示只读,rw 表示读写。
  • 常用API方法:
    • final void writeUTF(String str):写入一个字符串。
      • UTF-8编码下一个字符占3个字节,GBK编码下一个字符占2个字节。
      • writeUTF()会首先把指定字符串的总字节数写进前2个字节,故6个字节的数据其实占了8字节的空间。
    • final void writeInt(int v):写入一个int类型值。
    • void seek(long pos):设置位置指示器的位置。
    • void skipBytes(int n):位置指示器向后移动n个字节(跳过n个字节)。
    • final String readUTF():读取一个字符串。
    • final int readInt():读取一个int类型值。
    • void close():关闭RandomAccessFile,节省内存资源。

RandomAccessFile仅限于操作文件,不能访问其他IO设备,如网络,内存映像等。

源码: /javase-advanced/

  • src: c.y.io.RandomAccessFileTest
/**
 * 使用同一个RandomAccessFile对象对一个文件完成
 * 三个员工信息(姓名和年龄)的按顺序写和按顺序读,且在输出过程种,跳过赵四的年龄。
 *
 * @author yap
 */
public class RandomAccessFileTest {

    private RandomAccessFile randomAccessFile;

    @Before
    public void before() throws IOException {
        randomAccessFile = new RandomAccessFile("D:\\java\\io\\emp.txt", "rw");
        randomAccessFile.writeUTF("赵四");
        randomAccessFile.writeInt(18);
        randomAccessFile.writeUTF("刘能");
        randomAccessFile.writeInt(28);
        randomAccessFile.writeUTF("广坤");
        randomAccessFile.writeInt(38);
    }

    @After
    public void after() throws IOException {
        randomAccessFile.close();
    }

    @Test
    public void write() throws IOException {

        // 位置指示器重置于0号位
        randomAccessFile.seek(0);
        System.out.print(randomAccessFile.readUTF() + "\t");

        // 跳过赵四的年龄
        randomAccessFile.skipBytes(4);
        System.out.print(randomAccessFile.readUTF() + "\t");
        System.out.print(randomAccessFile.readInt() + "\t");
        System.out.print(randomAccessFile.readUTF() + "\t");
        System.out.print(randomAccessFile.readInt() + "\n");
    }

    @Test
    public void work01() throws IOException {
        randomAccessFile.seek(24);
        System.out.print(randomAccessFile.readUTF() + "\t");
        System.out.print(randomAccessFile.readInt() + "\t");
        randomAccessFile.seek(12);
        System.out.print(randomAccessFile.readUTF() + "\t");
        System.out.print(randomAccessFile.readInt() + "\t");
        randomAccessFile.seek(0);
        System.out.print(randomAccessFile.readUTF() + "\t");
        System.out.print(randomAccessFile.readInt() + "\n");
    }
}

java.io.EOFException 异常在某个文件已经到末尾了,你仍然在读取时爆发。

4. 文件节点流

概念: 文件节点流是对硬盘文件上的数据的直传输,中间不涉及任何数据的处理。

4.1 FileInputStream

概念: FileInputStream是InputStream的一个子类,可以以字节的方式读取文件内容。

  • 构造器:
    • FileInputStream(String name)
      • p1:文件路径和文件名的组合字符串。
    • FileInputStream(File file)
      • p1:文件对象。
  • 常用API方法:
    • int available():查看可见(还剩下的)总字节数。
    • int read():从此输入流中读取一个字节的数据,如果文件已经读完,返回-1。

源码: /javase-advanced/

  • src: c.y.io.FileSeriesTest.fileInputStream()
/**
 * @author yap
 */
public class FileSeriesTest {

    private String filePath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.java";

    /**
     * 将 `HelloWorld.java` 内容输入到程序中并打印在控制台,并计算总字节数
     */
    @Test
    public void fileInputStream() {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            System.out.println("total bytes:" + fis.available());

            // temp var
            int b;
            while ((b = fis.read()) != -1) {
                System.out.print((char) b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.2 FileOutputStream

概念: FileOutputStream是OutputStream的一个子类,可以以字节的方式将内容写入指定文件。

  • 构造器:
    • FileOutputStream(String name)
      • p1:文件路径和文件名的组合字符串。
    • FileOutputStream(File file)
      • p1:文件对象。
    • FileOutputStream(String name, boolean append)
      • p1:文件路径和文件名的组合字符串。
      • p2:是否追加内容,默认false。
    • FileOutputStream(File file, boolean append)
      • p1:文件对象。
      • p2:是否追加内容,默认false。
    • 要输出的文件路径如果没有I/O会帮忙创建(目录不存在会报错)。
  • 常用API方法:
    • void write(int b):将指定的字节b写入此文件输出流。
    • void flush():刷新此输出流并强制任何缓冲的输出字节被写出来。

源码: /javase-advanced/

  • src: c.y.io.FileSeriesTest.fileOutputStream()
/**
 * @author yap
 */
public class FileSeriesTest {

    private String filePath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.java";

    /**
     * 将 `HelloWorld.java` 内容copy到 `HelloWorld.txt`
     */
    @Test
    public void fileOutputStream() {
        String destPath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.txt";
        try (FileInputStream fis = new FileInputStream(filePath);
             FileOutputStream fos = new FileOutputStream(destPath, true)) {
            int b;
            while ((b = fis.read()) != -1) {
                fos.write(b);
            }
            fos.flush();
            System.out.println("copy over...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.3 FileReader

概念: FileReader是Reader的一个子类,可以以字符的方式将内容写入指定文件。

  • 构造器:
    • FileReader(String name)
      • p1:文件路径和文件名的组合字符串。
    • FileReader(File file)
      • p1:文件对象。
  • 常用API方法:
    • String getEncoding():获取当前输入流的编码。

源码: /javase-advanced/

  • src: c.y.io.FileSeriesTest.fileReader()
/**
 * @author yap
 */
public class FileSeriesTest {

    private String filePath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.java";

    /**
     * 将包含中文的 `HelloWorld.java` 输入到程序中并打印在控制台
     */
    @Test
    public void fileReader() {
        try (FileReader fr = new FileReader(filePath)) {
            System.out.println(fr.getEncoding());
            int b;
            while ((b = fr.read()) != -1) {
                System.out.print((char) b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.4 FileWriter

概念: FileWriter是Writer的一个子类,可以以字符的方式将内容写入指定文件。

  • 构造器:
    • FileWriter(String fileName)
      • p1:文件路径和文件名的组合字符串。
    • FileWriter(File file)
      • p1:文件对象。
    • FileWriter(String fileName, boolean append)
      • p1:文件路径和文件名的组合字符串。
      • p2:是否追加内容,默认false。
    • FileWriter(File file, boolean append)
      • p1:文件对象。
      • p2:是否追加内容,默认false。
  • 常用API方法:
    • void write(int c):向输出流中写入一个字符。

源码: /javase-advanced/

  • src: c.jy.io.FileSeriesTest.fileWriter()
/**
 * @author yap
 */
public class FileSeriesTest {
    /**
     * 输出一个文件 `Unicode.dat`,包含65536个Unicode字符
     */
    @Test
    public void fileWriter() {
        String destPath = "D:" + File.separator + "java-io" + File.separator + "Unicode.dat";
        try (FileWriter fw = new FileWriter(destPath)) {
            int unicodeMaxLength = 65535;
            for (int i = 0; i < unicodeMaxLength; i++) {
                fw.write(i);
            }
            fw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. 字节数组节点流

概念: 字节数组节点流是对内存上的数据的直传输,中间不涉及任何数据的处理。

  • ByteArrayInputStream:字节数组输入字节流,是InputStream的子类。
    • 构造器:ByteArrayInputStream(byte buf[]):仅支持byte[]格式的参数。
  • ByteArrayOutputStream:字节数组输出字节流,是OutputSteam的子类。
    • 构造器:
      • ByteArrayOutputStream():默认在内存中生成一个无名的字节数组。
      • ByteArrayOutputStream(int size):size可以指定初始字节数组大小,默认32。
    • 常用API方法:
      • byte toByteArray()[]:将流中的数据转换成为一个字节数组类型并返回。
      • int size():返回字节数组的连续内存空间长度。

源码: /javase-advanced/

  • src: c.y.io.ByteArraySeriesTest.byteArrayStream()
/**
 * @author yap
 */
public class ByteArraySeriesTest {

    /**
     * 在内存中创造一个虚拟的字节数组,向字节数组中练习写入 `100`, `101`, `102`,计算长度,并读出所有数。
     */
    @Test
    public void byteArrayStream() {
        ByteArrayOutputStream baos = null;
        ByteArrayInputStream bais = null;
        try {
            baos = new ByteArrayOutputStream();
            baos.write(100);
            baos.write(101);
            baos.write(102);
            System.out.println("the length of baosData:" + baos.size());
            baos.flush();

            byte[] byteArray = baos.toByteArray();
            bais = new ByteArrayInputStream(byteArray);
            System.out.println(bais.read());
            System.out.println(bais.read());
            System.out.println(bais.read());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (bais != null) {
                    bais.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}