1. 数据输入输出流

1. 数据输入输出流的概述:
(1)以Stream结尾的,一般都是字节流
(2)数据输入流(DataInputStream):数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型
(3)数据输出流(DataOutputStream):数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中

2. 数据输入输出流的特点:可以写基本数据类型,可以读取基本数据类型

3. 构造方法及常用方法

DataInputStream(InputStream in):使用指定的底层 InputStream 创建一个 DataInputStream
		 boolean readBoolean():读取一个boolean类型的数据 
		 byte readByte():读取一个byte类型的数据 
		 char readChar():读取一个char类型的数据 
		 double readDouble():读取一个double类型的数据 
		 float readFloat():读取一个float类型的数据 
		 int readInt():读取一个int类型的数据 

DataOutputStream(OutputStream out):创建一个新的数据输出流,将数据写入指定基础输出流
		 void writeBoolean(boolean v) :写入一个boolean类型的数据
		 void writeByte(int v) :写入一个byte类型的数据
		 void writeChar(int v) :写入一个char类型的数据
		 void writeDouble(double v) :写入一个double类型的数据
		 void writeFloat(float v) :写入一个float类型的数据
		 void writeInt(int v):写入一个int类型的数据

【注意】读取顺序:怎么写的就怎么读,顺序不能乱

4. 代码实现

public class TestDemo01 {
    public static void main(String[] args) throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("a.txt"));
        DataInputStream dis = new DataInputStream(new FileInputStream("a.txt"));

        dos.writeBoolean(true);
        dos.writeInt(100);
        dos.writeChar('a');
        dos.writeDouble(3.14);

        System.out.println(dis.readBoolean());   //true
        System.out.println(dis.readInt());   //100
        System.out.println(dis.readChar());   //a
        System.out.println(dis.readDouble());   //3.14
    }
}

2. 内存操作流

1. 操作字节数组

  • ByteArrayOutputStream:ByteArrayOutputStream():创建一个新的 byte 数组输出流
  • ByteArrayInputStream:ByteArrayInputStream(byte[] buf):创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组
  • 此流关闭无效,所以无需关闭
  • 此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长
  • 可使用 toByteArray () 和 toString () 获取数据
  • 代码示例
public class TestDemo02 {
    public static void main(String[] args) throws IOException {
        //创建ByteArrayOutputStream
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        String str1 = "艾欧尼亚";
        String str2 = "诺克萨斯";
        String str3 = "暗影岛";
        String str4 = "影流";
        byte[] bytes1 = str1.getBytes();
        byte[] bytes2 = str2.getBytes();
        byte[] bytes3 = str3.getBytes();
        byte[] bytes4 = str4.getBytes();

        //把上面4个字节数组中的数据写入到ByteArrayOutputStream自己所维护的那个字节数组中
        baos.write(bytes1);
        baos.write(bytes2);
        baos.write(bytes3);
        baos.write(bytes4);

        byte[] allBytes = baos.toByteArray();

        System.out.println(new String(allBytes));  //艾欧尼亚诺克萨斯暗影岛影流
        System.out.println(baos.toString());   //艾欧尼亚诺克萨斯暗影岛影流

        //创建ByteArrayInputStream
        ByteArrayInputStream bais = new ByteArrayInputStream(allBytes);
        byte[] catchBytes = new byte[1024 * 8];
        int len = bais.read(catchBytes);
        System.out.println(new String(catchBytes,0,len));   //艾欧尼亚诺克萨斯暗影岛影流
    }
}
  • 案例:使用ByteArrayInputStream和ByteArrayOutputStream,将两首歌合并成一首歌【很麻烦,不建议这么搞,只是作为对ByteArrayInputStream和ByteArrayOutputStream的理解】
public class TestDemo03 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis1 = new FileInputStream("阿刁_赵雷_128K.mp3");
        FileInputStream fis2 = new FileInputStream("鼓楼_赵雷_128K.mp3");
        ArrayList<FileInputStream> list = new ArrayList<>();
        list.add(fis1);
        list.add(fis2);

        //遍历集合,读取两首歌的字节数据,并把两首歌的字节数据放到内存操作流所维护的字节数组中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024 * 8];
        int len = 0;
        for (FileInputStream f : list) {
            while ((len = f.read(bytes)) != -1){
                baos.write(bytes,0,len);
            }
            f.close();
        }

        //取出两首歌的字节数据
        byte[] allBytes = baos.toByteArray();

        //把两首歌的字节数据写入到同一个文件中
        ByteArrayInputStream bais = new ByteArrayInputStream(allBytes);

        FileOutputStream fos = new FileOutputStream("阿刁+鼓楼-1.mp3");

        byte[] bytes2 = new byte[1024 * 8];
        int len2 = 0;
        while ((len2 = bais.read(bytes2)) != -1){
            fos.write(bytes2,0,len2);
        }
        fos.close();

        System.out.println("合并完成");
    }
}

2. 操作字符数组

  • CharArrayWrite:CharArrayWriter():创建一个新的 CharArrayWriter
  • CharArrayReader:CharArrayReader(char[] buf):根据指定的 char 数组创建一个 CharArrayReader
  • 代码示例
public class TestDemo04 {
    public static void main(String[] args) throws IOException {
        CharArrayWriter caw = new CharArrayWriter();

        String str1 = "艾欧尼亚";
        String str2 = "诺克萨斯";
        String str3 = "暗影岛";
        String str4 = "影流";
        caw.write(str1);
        caw.write(str2);
        caw.write(str3);
        caw.write(str4);

        //取出CharArrayWrite所维护的字符数组
        char[] allChars = caw.toCharArray();
        System.out.println(allChars.toString());   //[C@4f023edb
        System.out.println(new String(allChars));   //艾欧尼亚诺克萨斯暗影岛影流

        CharArrayReader car = new CharArrayReader(allChars);
        char[] catchChars = new char[1024 * 8];
        int len = car.read(catchChars);
        System.out.println(new String(catchChars,0,len));   //艾欧尼亚诺克萨斯暗影岛影流
    }
}

3. 打印流

1. 打印流的概述
(1)打印流是单个的,只能往出写数据
(2)打印流分为字符打印流、字节打印流

2. 字符打印流
(1)构造方法

PrintWriter(File file):使用指定文件创建不具有自动行刷新的新 PrintWriter。
	PrintWriter(String fileName):创建具有指定文件名称且不带自动行刷新的新 PrintWriter
	PrintWriter(OutputStream out, boolean autoFlush):通过现有的 OutputStream 创建新的 PrintWriter
    PrintWriter(Writer out, boolean autoFlush):创建新 PrintWriter
        					//参数2:true开启自动刷新
   【注意】如果启用了自动刷新,则只有在调用 println、printf 或 format的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成

(2)成员方法

void println():通过写入行分隔符字符串终止当前行

(3)代码示例

public class TestDemo05 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintWriter pw = new PrintWriter(new File("b.txt"));
        pw.println(5);
        pw.println(3.14);
        pw.println(true);
        pw.println("java");

        pw.flush();
        pw.close();

        System.out.println("--------------------------------------");

        PrintWriter pw2 = new PrintWriter(new FileOutputStream("c.txt"),true);
        pw2.println(5);
        pw2.println(3.14);
        pw2.println(true);
        pw2.println("java");

        pw2.close();
    }
}

3. 字节打印流

(1)构造方法

PrintStream(File file):创建具有指定文件且不带自动行刷新的新打印流。
		PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
		PrintStream(OutputStream out):创建新的打印流。
		PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。
		PrintStream(String fileName, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流

(2)代码示例

public class TestDemo06 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("d.txt");
        ps.println(100);
        ps.println(3.14);
        ps.println(true);
        ps.println("java");

        ps.flush();
        ps.close();
    }
}

(3)需求:打印流复制文本文件

//需求:打印流复制文本文件
public class TestDemo07 {
    public static void main(String[] args) throws IOException {
        /**
         * 这个打印流只能进行写数据,不能进行读取数据,
         * 那么我们应该找一个可以读取文本文件中的的数据的流对象进行读取操作.
         * 可以使用BufferedReader进行读取数据
         */
        BufferedReader br = new BufferedReader(new FileReader("d.txt"));
        PrintWriter pw = new PrintWriter(new FileWriter("newD.txt"));

        String line = null;
        while ((line = br.readLine()) != null){
            pw.println(line);
        }

        br.close();
        pw.close();
    }
}

4. 随机访问流

1. 随机访问流概述
(1)RandomAccessFile:最大特点:能读能写而且有一个文件指针,可以控制指针的位置
(2)RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。支持对随机访问文件的读取和写入
(3)随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针

2. 构造方法及成员方法

(1)构造方法

RandomAccessFile(String name, String mode):创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称
	参数1:文件名称
	参数2:模式
		"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。  
		"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。  
		"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。  
		"rwd"   打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。

(2)成员方法

long getFilePointer():返回此文件中的当前偏移量 
	void seek(long pos):设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作
	int read():从此文件中读取一个数据字节 
	int read(byte[] b):将最多 b.length 个数据字节从此文件读入 byte 数组
	int read(byte[] b, int off, int len):将最多 len 个数据字节从此文件读入 byte 数组
	boolean readBoolean():从此文件读取一个 boolean
	byte readByte():从此文件读取一个有符号的八位值
	char readChar():从此文件读取一个字符
	double readDouble():从此文件读取一个 double
	float readFloat():从此文件读取一个 float
	int readInt():从此文件读取一个有符号的 32 位整数
	String readLine():从此文件读取文本的下一行
	void write(byte[] b):将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。 
	void write(byte[] b, int off, int len):将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。 
 	void write(int b):向此文件写入指定的字节。 
	void writeBoolean(boolean v):按单字节值将 boolean 写入该文件 
 	void writeInt(int v):按四个字节将 int 写入该文件,先写高字节

(3)代码示例

public class TestDemo08 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("e.txt","rw");
        raf1.writeInt(100);
        raf1.writeBoolean(true);
        raf1.writeDouble(3.14);
        raf1.writeUTF("你好");

        RandomAccessFile raf2 = new RandomAccessFile("e.txt", "rw");
        int i = raf2.readInt();
        System.out.println(i);   //100
        long filePointer = raf2.getFilePointer();
        System.out.println("文件指针的位置" + filePointer);    //文件指针的位置4
        boolean b = raf2.readBoolean();
        System.out.println(b);    //true
        filePointer = raf2.getFilePointer();
        System.out.println("文件指针的位置" + filePointer);    //文件指针的位置5
        double d = raf2.readDouble();
        System.out.println(d);    //3.14
        filePointer = raf2.getFilePointer();
        System.out.println("文件指针的位置" + filePointer);    //文件指针的位置13
        String s = raf2.readUTF();
        System.out.println(s);     //你好
        filePointer = raf2.getFilePointer();
        System.out.println("文件指针的位置" + filePointer);    //文件指针的位置21

        //可以使用seek()方法移动文件指针
        raf2.seek(0);
        int i1 = raf2.readInt();
        System.out.println(i1);    //100

        raf2.seek(13);
        String s1 = raf2.readUTF();
        System.out.println(s1);    //你好
    }
}

5. 序列化流与反序列化流

1. 序列化流与反序列化流概述:
(1)序列化流(ObjectOutputStream):就是把对象通过流的方式存储到文件中.注意:此对象 要重写Serializable 接口才能被序列化
(2)反序列化流(ObjectInputStream):就是把文件中存储的对象以流的方式还原成对象
(3)注意:一个对象可以被序列化的前提是这个对象对应的类必须实现Serializable接口

2. 构造方法和成员方法
(1)构造方法

ObjectOutputStream(OutputStream out):创建写入指定 OutputStream 的 ObjectOutputStream
	ObjectInputStream(InputStream in):创建从指定 InputStream 读取的 ObjectInputStream

(2)成员方法

void writeObject(Object obj):将指定的对象写入 ObjectOutputStream 
	 Object readObject():从 ObjectInputStream 读取对象

(3)代码示例

①序列化单个对象

public class TestDemo01 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student("伊泽瑞尔", 22, '男');

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
        oos.writeObject(student);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
        Student stu = (Student) ois.readObject();
        System.out.println(stu.getName() + "--" + stu.getAge() + "--" + stu.getSex());
        ois.close();
    }
}

class Student implements Serializable {
    //当你实现了Serializable接口后,在给他一个标记
    private static final long serialVersionUID = 6007595135962043221L;

    private String name;
    private int age;
    private char sex;

    public Student() {
    }

    public Student(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}

②序列化多个对象

public class TestDemo01 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student1 = new Student("ez", 22, '男');
        Student student2 = new Student("noc", 25, '男');
        Student student3 = new Student("tf", 27, '男');

        ArrayList<Student> list = new ArrayList<>();
        list.add(student1);
        list.add(student2);
        list.add(student3);

        //如果我们要序列化多个对象,我们一个个去存这个对象。我我们可以把这多个对象,放到一个容器中,把容器序列化到文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("newObj.txt"));
        oos.writeObject(list);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("newObj.txt"));
        ArrayList<Student> myList = (ArrayList<Student>) ois.readObject();

        Student stu1 = myList.get(0);
        System.out.println(stu1.getName() + "--" + stu1.getAge() + "--" + stu1.getSex());   //ez--22--男
        Student stu2 = myList.get(1);
        System.out.println(stu2.getName() + "--" + stu2.getAge() + "--" + stu2.getSex());   //noc--25--男
        Student stu3 = myList.get(2);
        System.out.println(stu3.getName() + "--" + stu3.getAge() + "--" + stu3.getSex());   //tf--27--男

        oos.close();
        ois.close();
    }
}

class Student implements Serializable {
    //当你实现了Serializable接口后,在给他一个标记
    private static final long serialVersionUID = 6007595135962043221L;

    private String name;
    private int age;
    private char sex;

    public Student() {
    }

    public Student(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}

6. Properties类(集合)

1. Properties类的概述:

  • Properties类继承自Hashtable,是一个双列集合,表示了一个持久的属性集
  • Properties可以保存在流中或者在流中加载,经常使用该集合来读取配置文件
  • Properties规定了键和值都是String类型

2. Properties集合特有的功能

public Object setProperties(String key,String value):向该集合中添加数据。返回值:属性列表中指定键的旧值,如果没有值,则为 null
	public String getProperties(String key):取出该集合的数据。返回值:value的值
	public Set<String> stringPropertyName():获取一个key的set集合
public class TestDemo01 {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.setProperty("刀妹", "艾欧尼亚");
        properties.setProperty("诺手","诺克萨斯");
        properties.setProperty("武器大师","战争学院");

        String s1 = properties.getProperty("刀妹");
        String s2 = properties.getProperty("诺手");
        String s3 = properties.getProperty("武器大师");

        System.out.println(s1);   //艾欧尼亚
        System.out.println(s2);   //诺克萨斯
        System.out.println(s3);   //战争学院

        System.out.println("--------------------------------");

        //如果键没有找到值,则返回null
        String s4 = properties.getProperty("亚索");
        System.out.println(s4);   //null

        //如果键没有找到值,第二个参数设定为一个默认值,则返回这个默认值
        String s5 = properties.getProperty("劫", "影流");
        System.out.println(s5);

        System.out.println("--------------------------------");

        Set<String> set = properties.stringPropertyNames();
        System.out.println(set);   //[诺手, 武器大师, 刀妹]
    }
}

3. Properties的load()和store()功能(和IO流配合使用)

public void load(Reader reader):读取键值对数据把数据读取到Properties中
	public void store(Writer writer, String comments):把Properties集合中的键值对数据写入到文件中, comments注释
/**
 * 需求:把双列集合的数据,存储到文本文件中
 * 示例:
 * username = zhangsan
 * password = 123456
 * email = haha@163.com
 */
public class TestDemo02 {
    public static void main(String[] args) throws IOException {
        //方式1:
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("username", "zhangsan");
        hashMap.put("password", "123456");
        hashMap.put("email", "haha@163.com");

        PrintWriter pw = new PrintWriter(new FileWriter("user1.properties"), true);

        hashMap.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String key, String value) {
                pw.println(key + "=" + value);
            }
        });

        pw.flush();
        pw.close();

        System.out.println("-------------------------------------");

        //方式2:
        Properties properties = new Properties();
        properties.setProperty("username", "zhangsan");
        properties.setProperty("password", "123456");
        properties.setProperty("email", "haha@163.com");

        properties.store(new FileWriter("user2.properties"), null);
    }
}

===============================================================================================

/**
 * 需求:
 *      我有一个配置文件,配置文件是简直对的数据 键=值
 *      我要把配置文件中的数据读取到双列集合中
 */
public class TestDemo03 {
    public static void main(String[] args) throws IOException {
        //方式1:
        HashMap<String, String> hashMap = new HashMap<>();
        BufferedReader br = new BufferedReader(new FileReader("user1.properties"));

        String s1 = br.readLine();
        System.out.println(s1);   //password=123456
        String[] strings1 = s1.split("=");
        hashMap.put(strings1[0],strings1[1]);

        String s2 = br.readLine();
        System.out.println(s2);   //email=haha@163.com
        String[] strings2 = s2.split("=");
        hashMap.put(strings2[0],strings2[1]);

        String s3 = br.readLine();
        System.out.println(s3);    //username=zhangsan
        String[] strings3 = s3.split("=");
        hashMap.put(strings3[0],strings3[1]);

        System.out.println(hashMap);  //{password=123456, email=haha@163.com, username=zhangsan}

        System.out.println("-------------------------------------");


        /**
         * 方式2:
         *      使用Properties集合中的load()方法,直接读取到集合中
         *      Properties 属性集合来取读取配置文件,要求配置文件的中的数据 键和值是以=来拼接的
         *      一般属性配置文件的后缀名会以 .properties作为扩展名
         */
        Properties properties = new Properties();
        properties.load(new FileReader("user1.properties"));
        System.out.println(properties);   //{password=123456, email=haha@163.com, username=zhangsan}
        System.out.println(properties.getProperty("email"));    //haha@163.com

    }
}