u06-流类进阶

154 阅读6分钟

1. 数据流

概念: 数据流支持以类型为单位读取和写入数据。

1.1 DataOutputStream

概念: 数据输出字节流DataOutputStream是OutputStream的子类,可以以类型为单位向文件或内存写入数据。

  • 构造器:DataOutputStream(OutputStream out)
  • 常用API方法:
    • writeDouble():写一个double类型的数据到输出流中。
    • writeBoolean():写一个boolean类型的数据到输出流中。
    • writeInt()::写一个int类型的数据到输出流中。

源码: /javase-advanced/

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

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

    /**
     * 将一个int,一个boolean和一个double写入文件 `data.txt`
     */
    @Test
    public void dataOutputStream() {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(filePath))) {
            dos.writeInt(250);
            dos.writeBoolean(true);
            dos.writeDouble(3.14);
            dos.flush();
            System.out.println("write over...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件中的数据需要使用 DataInputStream 读取,直接打开是乱码。

1.2 DataInputStream

概念: 数据输入字节流DataInputStream是InputStream的子类,可以以类型为单位从文件或内存中读取数据。

  • 构造器:DataInputStream(InputStream in)
  • 常用API方法:
    • readDouble():从输入流中读取一个double类型的数据。
    • readBoolean():从输入流中读取一个boolean类型的数据。
    • readInt()::从输入流中读取一个int类型的数据。

源码: /javase-advanced/

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

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

     /**
     * 将 `data.txt` 读出来
     */
    @Test
    public void dataInputStream() {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
            System.out.println(dis.readInt());
            System.out.println(dis.readBoolean());
            System.out.println(dis.readDouble());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. 对象流

概念: 对象流可以传输一个类对象,但要求该类必须是一个可序列化的类。

  • transient 修饰符修饰的属性不参与序列化过程,真实的值不可见,只可见其默认值。

2.1 ObjectOutputStream

概念: 对象输出字节流ObjectOutputStream是OutputStream的子类,可以将类对象数据写入文件。

  • 构造器:ObjectOutputStream(OutputStream out)
  • 常用API方法:writeObject():写一个类对象数据到输出流中。

源码: /javase-advanced/

  • src: c.y.io.ObjectSeriesTest.objectOutputStream()
/**
 * @author yap
 */
public class ObjectSeriesTest {
    private String filePath = "D:" + File.separator + "java-io" + File.separator + "object.txt";

    private static class Student implements Serializable {
        private String name;
        private transient Integer age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }

    /**
     * 将 `Student.java` 写入 `object.txt` 文件中
     */
	@Test
	public void objectOutputStream() {
		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
			Student student = new Student();
			student.setName("赵四");
			student.setAge(18);
			oos.writeObject(student);
			oos.flush();
			System.out.println("write over...");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

文件中的数据需要使用 ObjectInputStream 读取,直接打开是乱码。

2.2 ObjectInputStream

概念: 对象输入字节流ObjectInputStream是InputStream的子类,可以从文件中读取类对象数据。

  • 构造器:ObjectInputStream(InputStream out)
  • 常用API方法:readObject():将一个类对象数据从输入流中读出来。

源码: /javase-advanced/

  • src: c.y.io.ObjectSeriesTest.objectInputStream()
/**
 * @author yap
 */
public class ObjectSeriesTest {
    private String filePath = "D:" + File.separator + "java-io" + File.separator + "object.txt";

    private static class Student implements Serializable {
        private String name;
        private transient Integer age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }

    /**
     * 将 `Student.java` 从 `object.txt` 文件中读取出来
     */
    @Test
    public void objectInputStream() {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
            Student student = (Student) ois.readObject();
            System.out.println("name:" + student.getName());
            System.out.println("age:" + student.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

3. 原型模式

概念: 原型模式就是java中的克隆技术,以某个实例为原型,克隆出一个新的实例,新实例具有原实例的属性值,这样的做法效率很高,因为不需要执行构造器(new),也不需要为属性赋值等。

3.1 浅克隆

概念: 通过浅克隆的方式克隆出来的实例的所有属性都含有与原来的实例相同的值,但对于引用类型来说,克隆的是内存地址,试想100个克隆实例的Date属性,全都指向0x9527,那么当0x9527位置上的Date值发生改变,100个克隆实例的值就全都跟着改变了,就像你和你的影子,你变胖了,你的影子也变胖了。

  • 浅克隆其实是直接或者间接调用了 Object 中的clone(),所以要求原实例实现 Cloneable 接口(标记接口,标识某个类为"可克隆的")以开启克隆操作的支持。

源码: /javase-advanced/

  • src: c.y.io.prototype.ShallowCloneTest.java
/**
 * @author yap
 */
public class ShallowCloneTest {
    private static class Sheep implements Cloneable{
        String name;
        Date birth;

        Sheep shallowClone(Sheep sheep) {
            Sheep result = null;
            try {
                result = (Sheep) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return result;
        }
    }

    /**
     * 通过DEBUG断点观测两只羊的属性,以及birth属性值的更改是否会同时影响两只羊的birth值。
     */
    @Test
    public void shallowClone() {
        Date birth = new Date();
        Sheep sheepA = new Sheep();
        sheepA.name = "dorset";
        sheepA.birth = birth;
        Sheep sheepB = sheepA.shallowClone(sheepA);
        sheepB.name = "dolly";
        birth.setTime(999999999999L);
    }
}

3.2 多次浅克隆

概念:

  • 多次浅克隆的效果等同于一次深克隆,即在你的浅克隆的时候,将Date属性再浅克隆一次。
  • 这种方式需要把所有引用属性都重新浅克隆一遍,当引用属性比较多,或者引用层度比较深的时候不适用。

源码: /javase-advanced/

  • src: c.y.io.prototype.MultipleShallowCloneTest.java
/**
 * @author yap
 */
public class MultipleShallowCloneTest {
    private static class Sheep implements Cloneable{
        String name;
        Date birth;

        Sheep multipleShallowClone(Sheep sheep) {
            Sheep result = null;
            try {
                result = (Sheep) super.clone();
                result.birth = (Date) result.birth.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return result;
        }
    }

    /**
     * 通过DEBUG断点观测两只羊的属性,以及birth属性值的更改是否会同时影响两只羊的birth值。
     */
    @Test
    public void multipleShallowClone() {
        Date birth = new Date();
        Sheep sheepA = new Sheep();
        sheepA.name = "dorset";
        sheepA.birth = birth;
        Sheep sheepB = sheepA.multipleShallowClone(sheepA);
        sheepB.name = "dolly";
        birth.setTime(999999999999L);
    }
}

3.3 深克隆

概念: 通过深克隆的方式克隆出来的实例的所有属性都含有与原来的实例相同的值,对与引用类型来说,它们所指向的对象,也被克隆了一份新的,即100个克隆实例的Date属性指向了100个不同的内存地址,此时Date值的改变,不会影响任何克隆实例,就像你和你的克隆人,你变胖了,但你的克隆人不变。

  • 深克隆的是实现实际上是对实例的序列化和反序列化,所以需要让你的被克隆类 Sheep.java 实现序列化接口。
  • 深克隆不需要使用 clone(),所以不必实现 Cloneable() 接口。

源码: /javase-advanced/

  • src: c.y.io.prototype.DeepCloneTest.java
/**
 * @author yap
 */
public class DeepCloneTest {
    private static class Sheep implements Serializable{
        String name;
        Date birth;

        Sheep deepClone(Sheep sheep) {
            Sheep result = null;
            ObjectOutputStream oos = null;
            ObjectInputStream ois = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(baos);
                oos.writeObject(sheep);
                oos.flush();
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ois = new ObjectInputStream(bais);
                result = (Sheep) ois.readObject();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (oos != null) { oos.close(); }
                    if (ois != null) { ois.close(); }
                } catch (IOException e) { e.printStackTrace(); }
            }
            return result;
        }
    }

    /**
     * 通过DEBUG断点观测两只羊的属性,以及birth属性值的更改是否会同时影响两只羊的birth值。
     */
    @Test
    public void deepClone() {
        Date birth = new Date();
        Sheep sheepA = new Sheep();
        sheepA.name = "dorset";
        sheepA.birth = birth;
        Sheep sheepB = sheepA.deepClone(sheepA);
        sheepB.name = "dolly";
        birth.setTime(999999999999L);
    }
}

4. 属性文件

概念: 配置文件的意义是拆分硬编码和组织调度业务层模块,属性文件是配置文件的一种,java中使用 java.util.Properties 类来表示属性文件。

  • void load(Reader reader):通过Reader流将数据全部读进Properties实例中。
  • String getProperty(String key):通过key获取属性文件中的value。

需求: 通过切换 config.txt 中的内容来控制访问 Student 类中的 studentInfo(),或是访问 Teacher 类中的 teacherInfo()

源码: /javase-advanced/

  • src: c.y.io.PropertiesTest
/**
 * @author yap
 */
public class PropertiesTest {

    private static class Student {
        public void studentInfo() {
            System.out.println("Im a Student...");
        }
    }

    private static class Teacher {
        public void teacherInfo() {
            System.out.println("Im a Teacher...");
        }
    }

    @Test
    public void properties() throws Exception {
        Properties properties = new Properties();
        String fileName = "u02-oop" + File.separator + "src" + File.separator + "config.txt";
        FileReader fr = new FileReader(fileName);
        properties.load(fr);
        fr.close();
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        Class<?> klass = Class.forName(className);
        Object instance = klass.getDeclaredConstructor().newInstance();
        klass.getDeclaredMethod(methodName).invoke(instance);
    }
}
  • res: config.txt
className=com.yap.reflect.Teacher
methodName=teacherInfo

config.txt的路径使用右键 Copy Relative Path 来获取。