目 录CONTENT

文章目录

BIO的典型应用

FatFish1
2024-11-10 / 0 评论 / 0 点赞 / 81 阅读 / 0 字 / 正在检测是否收录...

场景1 缓冲读入文件,在内存中存储和输出

  • 读入文件,无特殊需求可以使用节点流ileReader,缓冲需求可以使用BufferedReader进行装饰

  • 读取结果是String,通过StringBuilder进行构造,得到完整的内容

  • 若想进一步对String进行处理,可以使用StringReader做进一步读取

  • 使用try-with-resource语法确保流关闭

public void readFileForSout(String fileName) throws IOException {
    StringBuilder sb = new StringBuilder();
    try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line).append("\n");
        }
    }
    try (StringReader sr = new StringReader(sb.toString())) {
        int c;
        while ((c = sr.read()) != -1) {
            System.out.print((char) c);
        }
    }
}

场景2 格式化输入

参考DataInputStream/DataOutputStream

场景3 文件输出

选用PrintWriter进行文件输出:

public void writeForFile(String fileName, String content) throws IOException {
    try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fileName)))) {
        pw.println(content);
    }
}

也可以通过PrintWriter的另一个构造函数快速构造,其中已经包含了对缓冲流的封装:

public PrintWriter(File file) throws FileNotFoundException {
    this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
         false);
}

场景4 读取二进制文件

public static byte[] readBinaryFile(File bFile) throws IOException {
    BufferedInputStream bf = new BufferedInputStream(new FileInputStream(bFile));
    try {
        byte[] data = new byte[bf.available()];
        bf.read(data);
        return data;
    } finally {
        bf.close();
    }
}

一个简化后的读取二进制文件的方法

场景5 标准输入输出

使用java提供的标准I/O模型

System.inSystem.out是每个学java的同学写的第一行代码。除了这两个,还有System.err ,这些是java提供的标准io,实际上都是对BIO的封装。基于源码就可以看到

既然它们是流,就可以进一步装饰使用,例如以下案例:

// 从标准输入中读取
public static void main(String [] args) throws IOException {
    try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
        
        String s;
        while ((s = br.readLine()) != null) {
            System.out.println(s);
        }
    }
}

// 将标准输出转换为PrintWriter
public static void main(String [] args) throws IOException {
    PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
    out.println("Hello World");
}

标准I/O重定向

正常情况下,System.out 会默认输出到console,当我们想把输出内容全部存到一个文件中去,可以通过输出流构造一个文件,并且将System.out重定向到文件中

public static void main(String [] args) throws IOException {
    PrintStream console = System.out;
    PrintStream out = new PrintStream("test.out");
    System.setOut(out);
    System.setErr(out);
    System.out.println("Hello World");
}

场景6 进程控制

通过BIO可以实现java程序控制系统进程

public static void command(String command) throws IOException {
    boolean err = false;
    try {
        Process process = Runtime.getRuntime().exec(command);
        BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String s;
        while ((s = input.readLine()) != null) {
            System.out.println(s.concat("\n"));
        }
        BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        while ((s = error.readLine()) != null) {
            System.out.println(s.concat("\n"));
            err = true;
        }
    } catch (Exception e) {
        if (!command.startsWith("CMD /C")) {
            command("CMD /C " + command);
        } else {
            throw e;
        }
        if (err) {
            throw e;
        }
    }
}

需要注意的是,在windows系统下执行指令,需要在CMD中执行的命令前面加"CMD /C"

场景7 压缩

压缩类库是基于字节流InputStream和OutputStream派生而来的,因为压缩是基于字节的。因此当对字符有压缩需求的时候,可以使用InputStreamReader和OutputStreamWriter做字节流和字符流的转化

压缩类库的分类为:

  • 基类DeflaterOutputStream

    • 实现类GZIPOutputStream

    • 实现类ZipOutputStream

  • 基类InflaterIutputStream

    • 实现类GZIPInputStream

    • 实现类ZipInputStream

GZIP压缩

GZIPInputStream和GZIPOutputStream是简单压缩,仅针对单个数据流压缩,即一个gz中一个文件的压缩

public class MyTest {

    public static void main(String[] args) throws IOException {
        MyTest test = new MyTest();
        test.gzipCompress(new File("D:\\0Project\\test\\gzipcompress.txt"));
        test.gzipDecompress(new File("D:\\0Project\\test\\gzipcompress.gz"));
    }

    public void gzipCompress(File file) throws IOException {
        BufferedReader in = new BufferedReader(new FileReader(file));

        // 这里选用append模式为false,不追加,重名文件直接覆盖掉
        BufferedOutputStream out = 
            new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("D:\\0Project\\test\\gzipcompress.gz", false)));
        int i;
        while ((i = in.read()) != -1) {
            out.write(i);
        }
        in.close();
        out.close();
    }

    public void gzipDecompress(File file) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file))));
        String s;
        while ((s = bufferedReader.readLine()) != null) {
            System.out.println(s);
        }
        bufferedReader.close();
    }
}

ZIP压缩

Zip格式支持的java库能力更加全面,使用Zip格式的好处在于:

  • 有丰富的库和拓展功能支持,例如apache的zip包

  • 提供了组织压缩包架构的能力,即ZipEntry类

  • 提供了计算和校验能力,即Checksum,支持Adler32和CRC32两种算法,其中前者更快,后者更准确

使用zip包完成一个两个文件的压缩任务,其目录结构如下:

-- zipcompress1.txt

-- zipcompress2.txt

public class MyTest {

    public static void main(String[] args) throws IOException {
        MyTest test = new MyTest();
        String s = test.zipCompress("D:\\0Project\\test\\zipcompress1.txt", "D:\\0Project\\test\\zipcompress2.txt");
        test.zipDecompress(s);
    }

    public String zipCompress(String path1, String path2) throws IOException {
        String output = "D:\\0Project\\test\\zipcompress.zip";
        // 构造ZipOutputStream
        ZipOutputStream zos = new ZipOutputStream(new CheckedOutputStream(new FileOutputStream(output, false), new Adler32()));
        // 读path1写zip中的entry1
        BufferedReader in1 = new BufferedReader(new FileReader(path1));
        // 向zip中增加成员Entry
        zos.putNextEntry(new ZipEntry("entry1.txt"));
        int i;
        while ((i = in1.read()) != -1) {
            zos.write(i);
        }
        in1.close();
        // 使用closeEntry表示这一个文件已经写完了,但是整个zip还没有写完
        zos.closeEntry();
        // 读path2写zip中的entry2
        BufferedReader in2 = new BufferedReader(new FileReader(path2));
        // 向zip中增加zip档案
        zos.putNextEntry(new ZipEntry("entry2.txt"));
        int o;
        while ((o = in2.read()) != -1) {
            zos.write(o);
        }
        in1.close();
        // 使用closeEntry表示这一个文件已经写完了,但是整个zip还没有写完
        zos.closeEntry();
        // 最终关闭整个zip流
        zos.close();
        return output;
    }

    public void zipDecompress(String path) throws IOException {
        ZipInputStream zis = new ZipInputStream(new CheckedInputStream(new FileInputStream(new File(path)), new Adler32()));
        ZipEntry ze;
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(zis));
        // 解压和压缩一样,一个entry一个来,读完一个关一个
        while ((ze = zis.getNextEntry()) != null) {
            String s;
            while ((s = bufferedReader.readLine()) != null) {
                System.out.println(s);
            }
            zis.closeEntry();
        }
        zis.close();
        bufferedReader.close();
    }
}

apache zip压缩 - org.apache.commons.compress.archivers.zip

apache.commons包在java.util.zip的基础上额外封装了一下,让组件更好用,更安全

apache.commons包在压缩方面提供了几个常用类:

  • ZipArchiveOutputStream:是ZipOutputStream的包装类

  • ZipArchiveEntry:是ZipEntry的包装类

  • ZipFile:用于映射zip文件,可以快速获取Enumeration<ZipArchiveEntry>InputStream

使用apache.commons包完成一个三个文件的压缩任务,其目录结构如下:

-- zipcompress1.txt

-- directory

-- -- zipcompress2.txt

-- -- zipcompress3.txt

public class MyTest {

    public static void main(String[] args) throws IOException {
        MyTest test = new MyTest();
        String zipname = "D:\\0Project\\test\\archieve.zip";
        String basePath = "";

        try(ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(new File(zipname))) {
            test.zipCompress(basePath, "D:\\0Project\\test\\zipcompress", zipArchiveOutputStream);
        }
        test.zipDecompress(zipname, "D:\\0Project\\test\\archieveresult");
    }

    public void zipCompress(String basePath, String path, ZipArchiveOutputStream zos) throws IOException {
        // 解析path分析path是文件夹还是文件
        File file = new File(path);
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                if (f.isDirectory()) {
                    String newBasePath = basePath + f.getName();
                    zipCompress(newBasePath, f.getPath(), zos);
                } else {
                    addEntry(basePath, f, zos);
                }
            }
        } else {
            addEntry(basePath, file, zos);
        }
    }

    private void addEntry(String basePath, File file, ZipArchiveOutputStream zipArchiveOutputStream) throws IOException {
        FileInputStream fs = null;
        try {
            fs = new FileInputStream(file);
            String name = "".equals(basePath) ? file.getName() : basePath + "\\" + file.getName();
            zipArchiveOutputStream.putArchiveEntry(new ZipArchiveEntry(name));
            IOUtils.copy(fs, zipArchiveOutputStream, 4096);
            zipArchiveOutputStream.closeArchiveEntry();
        } finally {
            if (fs != null) {
                fs.close();
            }
        }
    }

    public void zipDecompress(String path, String targetPath) throws IOException {
        ZipFile zipFile = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            zipFile = new ZipFile(new File(path), "UTF-8");
            Enumeration<ZipArchiveEntry> iter = zipFile.getEntries();

            while (iter.hasMoreElements()) {
                ZipArchiveEntry zipArchiveEntry = iter.nextElement();
                String itemName = zipArchiveEntry.getName();
                // 判断路径安全性
                if (!PathTool.isSafePath(itemName)) {
                    return;
                }
                // 组织目标目录+文件名的新文件名
                Path result = Paths.get((targetPath + "\\" + itemName));

                // 如果该item是目录,则在解压目标目录中创建目录
                if (zipArchiveEntry.isDirectory()) {
                    Files.createDirectories(result);
                } else {
                    // 是文件的场景,先创建下父目录,如果有存量文件,删掉
                    Files.createDirectories(result.getParent());
                    Files.deleteIfExists(result);
                    // apache.commons好用的点在于可以直接基于zipFile获取到流,不需要再包装了
                    inputStream = zipFile.getInputStream(zipArchiveEntry);
                    // 这里使用nio做outputstream
                    outputStream = Files.newOutputStream(result, StandardOpenOption.CREATE_NEW);
                    IOUtils.copy(inputStream, outputStream, 4096);
                }
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }

    }
}

在这个上线级别的压缩/解压案例中,构造了一个文件+目录场景的压缩和解压方法,其中,几个需要注意的点包括:

  • 压缩方法

    • 在压缩方法zipCompress()提供了一个递归框架,用于分析目录,对于非目录的,直接添加到ZipArchiveEntry,对于是目录的,要进行遍历,对遍历结果,继续判断是否为目录,如果是目录,递归处理,如果非目录的,也添加到ZipArchiveEntry,这里需要注意的是添加时的命名

    • 在压缩方法zipCompress()addEntry()中,透传了一个basePath,它的作用是标记当前在第几层目录,在zipCompress()遍历到目录的时候,它会重置其绝对路径值,因为只有正确传递basePath才能在addEntry()方法中构造正确的文件名,从而生成和原始目录结构一致的压缩包结构

    • 使用IOUtils.copy() 方法可以省掉OutputStream循环write()的逻辑,更方便一些

  • 解压方法

    • apache.commons提供了好用的ZipFile类,可以快捷地拿到InputStream,不需要再自己层层包装

    • ZipFile#getEntries()方法拿到的是遍历完成的文件,与File#listFile()方法返回是不同的,不需要判断返回结果是目录还是文件,这一点可以省去递归遍历目录的场景,只需要一层循环即可,非常好用

0

评论区