场景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.in
和System.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()
方法返回是不同的,不需要判断返回结果是目录还是文件,这一点可以省去递归遍历目录的场景,只需要一层循环即可,非常好用
评论区