30.3. 文件拷贝操作
需求:把copy_before.txt文件中的数据拷贝到copy_after.txt文件中
private static void copy() throws Exception { //1):创建源或者目标对象 File src = new File("file/copy_before.txt"); File dest = new File("file/copy_after.txt"); //2):创建IO流对象 FileReader in = new FileReader(src); FileWriter out = new FileWriter(dest); //3):具体的IO操作 int len = -1;//记录以及读取了多个字符 char[] buff = new char[1024];//每次可以读取1024个字符 len = in.read(buff);//先读取一次 while(len > 0) { //边读边写 out.write(buff, 0, len); len = in.read(buff);//再继续读取 } //4):关闭资源(勿忘) out.close(); in.close(); }
如何,正确处理异常:
private static void copy2() { //1):创建源或者目标对象 File src = new File("file/copy_before.txt"); File dest = new File("file/copy_after.txt"); //把需要关闭的资源,声明在try之外 FileReader in = null; FileWriter out = null; try { //可能出现异常的代码 //2):创建IO流对象 in = new FileReader(src); out = new FileWriter(dest); //3):具体的IO操作 int len = -1;//记录以及读取了多个字符 char[] buff = new char[1024];//每次可以读取1024个字符 len = in.read(buff);//先读取一次 while (len > 0) { out.write(buff, 0, len); len = in.read(buff);//再继续读取 } } catch (Exception e) { e.printStackTrace(); } finally { //4):关闭资源(勿忘) try { if (out != null) { out.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (in != null) { in.close(); } } catch (Exception e) { e.printStackTrace(); } } }
此时关闭资源的代码,又臭又长,在后续的学习中为了方便就直接使用throws抛出IO异常了,在实际开发中需要处理。
30.4. 缓冲流
节点流的功能都比较单一,性能较低。处理流,也称之为包装流,相对于节点流更高级,这里存在一个设计模式——装饰设计模式,此时撇开不谈。
包装流如何区分?写代码的时候,发现创建流对象时,需要传递另一个流对象,类似:
new 流类A( new 流类B(..) ) ;
那么流A就属于包装流,当然B可能属于节点流也可能属于包装流。
有了包装流之后,我们只关系包装流的操作即可,比如只需要关闭包装流即可,无需在关闭节点流。
非常重要的包装流——缓冲流,根据四大基流都有各自的包装流:
BufferedInputStream / BufferedOutputStream / BufferedReader / BufferedWriter
缓冲流内置了一个默认大小为8192个字节或字符的缓存区,缓冲区的作用用来减少磁盘的IO操作,拿字节缓冲流举例,比如一次性读取8192个字节到内存中,或者存满8192个字节再输出到磁盘中。
操作数据量比较大的流,都建议使用上对应的缓存流。
需求:把郭德纲-报菜名.mp3文件中的数据拷贝到郭德纲-报菜名2.mp3文件中
private static void copy3() throws Exception { //1):创建源或者目标对象 File src = new File("file/郭德纲-报菜名.mp3"); File dest = new File("file/郭德纲-报菜名2.mp3"); //2):创建IO流对象 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src), 8192); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest), 8192); //3):具体的IO操作 int len = -1;//记录以及读取了多个字符 byte[] buff = new byte[1024];//每次可以读取1024个字符 len = bis.read(buff);//先读取一次 while (len > 0) { //边读边写 bos.write(buff, 0, len); len = bis.read(buff);//再继续读取 } //4):关闭资源(勿忘) bos.close(); bis.close(); }
30.5. 对象序列化
序列化:指把Java堆内存中的对象数据,通过某种方式把对象数据存储到磁盘文件中或者传递给给网络上传输。序列化在分布式系统在应用非常广泛。
反序列化:把磁盘文件中的对象的数据或者把网络节点上的对象数据恢复成Java对象的过程。
需要做序列化的类必须实现序列化接口:java.io.Serializable(这是标志接口[没有抽象方法])
可以通过IO中的对象流来做序列化和反序列化操作。
-
ObjectOutputStream:通过writeObject方法做序列化操作的
-
ObjectInputStream:通过readObject方法做反序列化操作的
如果字段使用transient 修饰则不会被序列化。
class User implements Serializable { private String name; private transient String password; private int age; public User(String name, String password, int age) { this.name = name; this.password = password; this.age = age; } public String getName() { return name; } public String getPassword() { return password; } public int getAge() { return age; } public String toString() { return "User [name=" + name + ", password=" + password + ", age=" + age + "]"; } }
测试代码
public class ObjectStreamDemo { public static void main(String[] args) throws Exception { String file = "file/obj.txt"; ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); User u = new User("Will", "1111", 17); out.writeObject(u); out.close(); //-------------------------------------- ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Object obj = in.readObject(); in.close(); System.out.println(obj); } }
obj.txt文件
序列化的版本问题
当类实现Serializable接口后,在编译的时候就会根据字段生成一个缺省的serialVersionUID值,并在序列化操作时,写到序列化数据文件中。
但随着项目的升级系统的class文件也会升级(增加一个字段/删除一个字段),此时再重新编译,对象的serialVersionUID值又会改变。那么在反序列化时,JVM会把对象数据数据中的serialVersionUID与本地字节码中的serialVersionUID进行比较,如果值不相同(意味着类的版本不同),那么报异常InvalidClassException,即:类版本不对应,不能进行反序列化。如果版本号相同,则可以进行反序列化。
为了避免代码版本升级而造成反序列化因版本不兼容而失败的问题,在开发中我们可以故意在类中提供一个固定的serialVersionUID值。
class User implements Serializable { private static final long serialVersionUID = 1L; //TODO }
30.6. 查漏补缺
30.6.1. 打印流
打印流是一种特殊是输出流,可以输出任意类型的数据,比一般的输出流更好用。可以作为处理流包装一个平台的节点流使用,平时我们使用的System.out.println其实就是使用的打印流。
-
PrintStream :字节打印流
-
PrintWriter :字符打印流
打印流中的方法:
-
提供了print方法:打印不换行
-
提供了println方法:先打印,再换行
private static void test5() throws Exception { //1):创建源或者目标对象 File dest = new File("file/result3.txt"); //2):创建IO流对象 PrintStream ps = new PrintStream(new FileOutputStream(dest)); //3):具体的IO操作 ps.println("Will"); ps.println(17); ps.println("众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。"); //4):关闭资源(勿忘),打印流可以不用关闭 }
30.6.2. 标准IO
标准的输入:通过键盘录入数据给程序
标准的输出:在屏幕上显示程序数据
在System类中有两个常量int和out分别就表示了标准流:
InputStream in = System.in; PrintStream out = System.out;
需求:做一个ECHO(回声)的小案例
public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); //接受用户输入数据后敲回车 while (sc.hasNextLine()) { //判断用户是否输入一行数据 String line = sc.nextLine(); //获取用户输入的数据 System.out.println("ECHO:" + line);//显示用户输入的数据 } }
30.7.IO流小结
在开发中使用比较多的还是字节和字符流的读写操作,务必要非常熟练,再体会一下六字箴言(读进来,写出去),到底有何深意。
综合练习题:做一个统计代码行数的程序,扫描一个目录能统计出该目录中包括所有子目录中所有Java文件的行数,不统计空行。
private static void doWork(File dir) { //TODO }