接着上一篇文章的内容,我们在说完了InputStream、OutputStream和Reader、Writer之后,来看一些有趣的类,最后我们来看一些具体应用的实例,去体会在实际项目中怎么组织这些类来让它们发挥作用。
(一)ZIP文档,ZipInputStream类
ZIP文档以压缩格式存储一个或多个文件,每个ZIP文档都有一个头,包含诸如每个文件名字和所使用的压缩方法等信息。在java中,我们通常使用ZipInputStream来读入ZIP文档。
在ZipInputStream对象中我们可能有一个或多个项(文件),你可能需要浏览文档中每个单独的项,所以java使用叫做ZipEntry的类来表示在ZIP中的一个个项。下面我们通过一个略长的例子来学习一下操作ZIP的所有方法和它们的用法。
package Stream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
*
* @author QuinnNorris
*
* 使用java中的ZipInputStream和ZipOutputStream类操作ZIP文件
*/
public class ZIPStream {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("根目录是:" + System.getProperty("user.dir"));
String fileName = "example.zip";
try (FileOutputStream fos = new FileOutputStream(fileName);
ZipOutputStream zos = new ZipOutputStream(fos);) {
for (int i = 0; i < 10; i++) {
zos.putNextEntry(new ZipEntry("No." + i + ".txt"));
// zos.write(("这是第" + i + "个txt文件").getBytes());
PrintWriter pw = new PrintWriter(zos);
pw.print("这是第" + i + "个txt文件");
pw.flush();
zos.closeEntry();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
System.err.println("没有在根目录:" + System.getProperty("user.dir")
+ "下找到:" + fileName);
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
System.err.println("将文件加入压缩包时发生未知错误。");
e.printStackTrace();
}
try (FileInputStream fis = new FileInputStream(fileName);
ZipInputStream zis = new ZipInputStream(fis);) {
while (zis.getNextEntry() != null) {
/*
* byte[] b = new byte[1024]; zis.read(b);
* System.out.println(new String(b));
*/
Scanner sn = new Scanner(zis);
if (sn.hasNextLine())
System.out.println(sn.nextLine());
zis.closeEntry();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 在一开始,我们通过System.getProperty(“user.dir”)先输出这个.java文件所在的位置,为我们之后的工作带来方便。
- 之后我们使用了带资源的try块(jdk1.7后可用),带资源的try块会为你自动调用close方法,省去了很多不必要的麻烦。
- 通过FileOutputStream与ZipOutputStream的套用,我们构建出了可以进行操作的输出流。
- 我们打算在这个zip文件中写入10个txt文件,for循环做标记。
- 调用putNextEntry方法,这个方法将参数中的ZipEntry对象作为一个新的条目加入ZIP文件中。
- ZipEntry对象的构造器传入String作为这个条目的名字,不要忘记后缀。
- 被注释掉的write方法是ZipOutputStream自带的写出方法,参数是byte[],如果要使用write一般都需要类型转换。
- 我们不使用原生的write方法,创建PrintWriter对象,使用print方法写入内容,调用flush方法将内容填充进去。
- 调用closeEntry方法表示这个条目已经操作结束。
- 期间会出现两个异常,这里我们选择catch来捕获,如果你想throws也没有问题。
- 下面的try块同样带资源, 根据需要,我们将文件和zip关联起来,使用FileInputStream,ZipInputStream这两个类。
- 调用getNextEntry方法,如果需要操作ZipEntry对象,为它赋个名字,这里我们不需要。
- 被注释的方法是原生read方法。
- 我们通过Scanner类来获取zis对象中的内容,将每行输出到控制台。
- 调用closeEntry方法走向下一个条目。
通过以上的步骤完成了创建zip文件和在控制台显示其中每个txt文件的内容。
(二)序列化对象流、ObjectOutputStream类
当你要存储数据时,使用固定长度的数组或者是集合类库中的列表是个不错的选择。但是如果你想要同时存储不同类型的数据呢?或许可以使用类似Object[]的数组来存储,但是这里要强势推荐一个对象序列化的通用机制来存放任何对象。我们通过这个方法将任何对象写出到流中,并在之后将其读回。而且这种序列化的方法有很多优点,最著名的优点就是通过序列化我们可以得到一个对象的clone,如果你在很多项目中都见过这个接口——Serializable。说明对象克隆使用的正是这种方法。
package Stream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
*
* @author QuinnNorris
*
* 序列化对象流会深拷贝复制对象
*/
public class ObjectStream {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String fileName = "example.txt";
try (FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis)) {
Student s1 = new Student(19, "XiaoZhang", null);
Student s2 = new Student(20, "XiaoWang", s1);
oos.writeObject(s2);
Student getS2 = (Student) ois.readObject();
getS2.getFriend().setAge(5);
// 将getS2的Student类型的friend属性的age值改为5.
if (19 == s2.getFriend().getAge())
// 如果序列化不是深拷贝,那么getS2和s2指向的应该是同一个s1,这个if判断不会正确
System.out.println("序列化是深拷贝");
// 结果确实输出了:“序列化是深拷贝”,证明getS2和s2只想的对象已经不同,经过了深拷贝
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(三)IO流典型使用方式
这一段主要来自于thinking in java,但我们采用了try-catch方法,原书中为了方便直接使用throws来回避异常的问题,主要是各种常用的IO流使用手段。
1.缓冲输入文件
如果想打开一个文件用于字符输入,可以使用String或File对象作为文件名。为了提高速度,我们想要为这个文件进行缓冲,我们将产生的引用传递给BufferedReader构造器。通过这个类的readLine()方法,这就是我们最终对象和进行读取的接口。
package Stream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
*
* @author QuinnNorris
*
* 缓冲输入文件
*/
public class Stream1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try (FileReader fir = new FileReader("example.txt");
// 直接接受一个File文件或String,和FileInputStream用法一样。
BufferedReader br = new BufferedReader(fir)
// 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
// 构造器接受一个Reader对象,类似包装器
) {
String nextLine = null;
while ((nextLine = br.readLine()) != null) {
System.out.println(nextLine);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.基本的文件输出
FileWriter对象可以向文件写入数据。首先创建一个与指定文件连接的FileWriter。实际上,我们会通过用BufferedWriter将其包装起来用以缓冲输出。为了提供格式化的方法我们还可以使用PrintWriter方法。
package Stream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
*
* @author QuinnNorris
*
* 基本文件输出
*/
public class Stream2 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try (FileWriter fw = new FileWriter(new File("example.txt"));
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter pw = new PrintWriter(bw)) {
for (int i = 0; i < 10; i++)
pw.print("这是第" + i + "行文字。"
+ System.getProperty("line.separator"));
pw.flush();
// 在当前目录下多出个txt文件,有十行内容。
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3.存储和恢复数据
如果我们使用DataOutputStream写入数据,java保证我们可以使用DataInputStream准确的读取数据——无论读和写数据的平台多么不同,这一点是非常有价值的。
package Stream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
*
* @author QuinnNorris
*
* 存储和恢复数据
*/
public class Stream3 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String fileName = "example.txt";
try (FileOutputStream fos = new FileOutputStream(fileName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(bos);) {
dos.writeDouble(3.14);
dos.writeUTF("圆周率");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try (FileInputStream fis = new FileInputStream(fileName);
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis)) {
System.out.println(dis.readDouble());
System.out.println(dis.readUTF());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}