第八章 IO流

概述

什么是IO流?
水分子的移动形成了水流。
IO流指的是:程序中数据的流动。数据可以从内存流动到硬盘,也可以从硬盘流动到内存。
Java中IO流最基本的作用是:完成文件的读和写。
IO流的分类?
根据数据流向分为:输入和输出是相对于内存而言的。
输入流:从硬盘到内存。(输入又叫做读:read)
输出流:从内存到硬盘。(输出又叫做写:write)
根据读写数据形式分为:
字节流:一次读取一个字节。适合读取非文本数据。例如图片、声音、视频等文件。(当然字节流是万能的。什么都可以读和写。)
字符流:一次读取一个字符。只适合读取普通文本。不适合读取二进制文件。因为字符流统一使用Unicode编码,可以避免出现编码混乱的问题。
注意:Java的所有IO流中凡是以Stream结尾的都是字节流。凡是以Reader和Writer结尾的都是字符流。
根据流在IO操作中的作用和实现方式来分类:
节点流:节点流负责数据源和数据目的地的连接,是IO中最基本的组成部分。
处理流:处理流对节点流进行装饰/包装,提供更多高级处理操作,方便用户进行数据处理。
Java中已经将io流实现了,在java.io包下,可以直接使用。

IO流的体系结构

右图是常用的IO流。实际上IO流远远不止这些。
InputStream:字节输入流
OutputStream:字节输出流
Reader:字符输入流
Writer:字符输出流
以上4个流都是抽象类,是所有IO流的四大头领!!!
所有的流都实现了Closeable接口,都有close()方法,流用完要关闭。
所有的输出流都实现了Flushable接口,都有flush()方法,flush方法
的作用是,将缓存清空,全部写出。养成好习惯,以防数据丢失。

四大头领:
	InputStream
	OutputStream
	Reader
	Writer

File相关的:
	FileInputStream
	FileOutputStream
	FileReader
	FileWriter

缓冲流相关的:
	BufferedInputStream
	BufferedOutputStream
	BufferedReader
	BufferedWriter

转换流相关的:
	InputStreamReader
	OutputStreamWriter

打印流相关的:
	PrintStream
	PrintWriter

对象相关的:
	ObjectInputStream
	ObjectOutputStream

数据相关的:
	DataInputStream
	DataOutputStream

字节数组相关的:
	ByteArrayInputStream
	ByteArrayOutputStream

压缩和解压缩相关的:
	GZIPInputStream
	GZIPOutputStream

线程相关的:
	PipedInputStream
	PipedOutputStream

FileInputStream

文件字节输入流,可以读取任何文件。
常用构造方法
FileInputStream(String name):创建一个文件字节输入流对象,参数是文件的路径
常用方法
int read();从文件读取一个字节(8个二进制位),返回值读取到的字节本身,如果读不到任何数据返回-1
int read(byte[] b); 一次读取多个字节,如果文件内容足够多,则一次最多读取b.length个字节。返回值是读取到字节总数。如果没有读取到任何数据,则返回 -1
int read(byte[] b, int off, int len); 读到数据后向byte数组中存放时,从off开始存放,最多读取len个字节。读取不到任何数据则返回 -1
long skip(long n); 跳过n个字节
int available(); 返回流中剩余的估计字节数量。
void close() 关闭流。
使用FileInputStream读取的文件中有中文时,有可能读取到中文某个汉字的一半,在将byte[]数组转换为String时可能出现乱码问题,因此FileInputStream不太适合读取纯文本。
/**
 * 测试:int read(byte[] b); 一次最多可以读到b.length个字节(只要文件内容足够多)。返回值是读取到的字节数量。如果这一次没有读取到任何数据,则返回 -1
 */
public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:\\test\\abc.txt");

            // 开始读
            // 提前准备一个byte[]数组(一次最多读取4个字节。)
            byte[] bytes = new byte[4];
            // 第一次读
            /*int readCount = fis.read(bytes);
            System.out.println("第一次读取到的字节数量:" + readCount); // 4*/

            // 将byte数组转换成字符串
            /*String s1 = new String(bytes);
            System.out.println(s1);*/

            /*String s1 = new String(bytes, 0, readCount);
            System.out.println(s1);*/

            // 第二次读
            /*readCount = fis.read(bytes);
            System.out.println("第二次读取到的字节数量:" + readCount); // 2*/

            /*String s2 = new String(bytes);
            System.out.println(s2);*/

            /*String s2 = new String(bytes, 0, readCount);
            System.out.println(s2);*/

            // 第三次读
            /*readCount = fis.read(bytes);
            System.out.println("第三次读取到的字节数量:" + readCount); // -1

            readCount = fis.read(bytes);
            System.out.println("第四次读取到的字节数量:" + readCount); // -1*/

            // 循环
            /*while(true){
                int readCount = fis.read(bytes);
                if(readCount == -1) break;
                String s = new String(bytes, 0, readCount);
                System.out.print(s);
            }*/

            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1){
                System.out.print(new String(bytes, 0, readCount));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
/**
 * 测试:
 *      int read(byte[] b, int off, int len); 一次读取len个字节。将读到的数据从byte数组的off位置开始放。
 *      long skip(long n); 跳过n个字节。
 *      int available(); 获取流中剩余的估计字节数。
 */
public class FileInputStreamTest03 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("E:/powernode/02-JavaSE/code/file1.txt");

            /*byte[] bytes = new byte[10];
            int readCount = fis.read(bytes, 2, 5);
            System.out.println("读取到了多少个字节:" + readCount);

            for(byte b : bytes){
                System.out.println(b);
            }*/

            /*int readByte = fis.read();
            System.out.println(readByte); // 97

            // 跳过两个
            fis.skip(2);

            readByte = fis.read();
            System.out.println(readByte); // 100

            System.out.println("还剩几个字节没有读取?" + fis.available());*/

            byte[] bytes = new byte[fis.available()];
            int readCount = fis.read(bytes);
            System.out.println(new String(bytes, 0, readCount));

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

FileOutputStream

文件字节输出流。
常用构造方法:
FileOutputStream(String name) 创建输出流,先将文件清空,再不断写入。
FileOutputStream(String name, boolean append) 创建输出流,在原文件最后面以追加形式不断写入。
常用方法:
write(int b)	写一个字节
void write(byte[] b);  将字节数组中所有数据全部写出
void write(byte[] b, int off, int len); 将字节数组的一部分写出
void close() 关闭流
void flush() 刷新
使用FileInputStream和FileOutputStream完成文件的复制。
/**
 * java.io.FileOutputStream
 *      1. 文件字节输出流,负责写。
 *
 *      2. 常用构造方法:
 *
 *          FileOutputStream(String name)
 *              创建一个文件字节输出流对象,这个流在使用的时候,最开始会将原文件内容全部清空,然后写入。
 *
 *          FileOutputStream(String name, boolean append)
 *              创建一个文件字节输出流对象,当append是true的时候,不会清空原文件的内容,在原文件的末尾追加写入。
 *              创建一个文件字节输出流对象,当append是false的时候,会清空原文件的内容,然后写入。
 *
 *              注意:
 *                  append==true表示:不会清空原文件内容,在原文件内容后面追加。
 *                  append==false表示:清空原文件内容,在文件中写入。
 *
 *      3. 常用方法:
 *          void close();
 *          void flush();
 *          void write(int b); 写一个字节
 *          void write(byte[] b); 将整个byte字节数组写出
 *          void write(byte[] b, int off, int len) 将byte字节数组的一部分写出。
 */
public class FileOutputStreamTest01 {
    public static void main(String[] args) {
        // 创建文件字节输出流对象
        FileOutputStream out = null;
        try {
            //out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt", true);

            // 以下两行代码一样。
            /*out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt");
            out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt", false);*/

            out = new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file2.txt");

            // 开始写
            /*out.write(97);
            out.write(98);
            out.write(99);
            out.write(100);*/

            byte[] bytes = {97,98,99,100};
            out.write(bytes);

            out.write(bytes, 0, 2);

            byte[] bs = "动力节点,一家只教授Java的培训机构".getBytes();
            out.write(bs);

            // 记着刷新
            out.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭资源
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

文件拷贝

/**
 * 文件拷贝,实现原理:
 * 使用FileInputStream读文件,使用FileOutputStream写文件。
 * 一边读一边写。
 */
public class FileInputOutputStreamCopy {
    public static void main(String[] args) {

        long begin = System.currentTimeMillis();
        // 输入流
        FileInputStream in = null;
        // 输出流
        FileOutputStream out = null;
        try {
            in = new FileInputStream("E:\\powernode\\02-JavaSE\\video\\上\\012-第一章Java的加载与执行原理.avi");
            out = new FileOutputStream("E:\\012-第一章Java的加载与执行原理.avi");

            // 一次至少拷贝1KB
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while ((readCount = in.read(bytes)) != -1) {
                out.write(bytes, 0, readCount);
            }

            // 刷新
            out.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭(分别try..catch..)
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        long end = System.currentTimeMillis();
        System.out.println("使用不带缓冲区的方式耗时"+(end - begin)+"毫秒"); // 1477
    }
}

try-with-risource语法

FileRead

文件字符输入流
常用的构造方法:
FileReader(String fileName)
常用的方法:
int read()
int read(char[] cbuf);
int read(char[] cbuf, int off, int len);
long skip(long n);
void close()
/**
 * 文件字符输入流。读。以字符的形式读文件。一次至少读取一个完整的字符。
 */
public class FileReaderTest01 {
    public static void main(String[] args) {
        try(FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\code\\file1.txt")){
            // 开始读
            char[] chars = new char[3];
            int readCount = 0;
            while((readCount = reader.read(chars)) != -1){
                System.out.print(new String(chars, 0, readCount));
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

FileWirter

文件字符输出流
常用的构造方法:
FileWriter(String fileName)
FileWriter(String fileName, boolean append)
常用的方法:
void write(char[] cbuf)
void write(char[] cbuf, int off, int len);
void write(String str);
void write(String str, int off, int len);
void flush();
void close();
Writer append(CharSequence csq, int start, int end)
使用FileReader和FileWriter拷贝普通文本文件
/**
 * 文件字符输出流。写。(写普通文本用的。)
 */
public class FileWriterTest01 {
    public static void main(String[] args) {
        try(FileWriter writer = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file3.txt")){
            // 开始写
            writer.write("hello world!");
            writer.append("\n");
            writer.write("张三李四王五赵六", 2, 2);
            writer.append("\n");
            writer.write("张三李四王五".toCharArray());
            writer.append("\n");
            writer.write("张三李四王五".toCharArray(), 2, 2);
            writer.append("\n");

            writer.append("a");
            writer.append("b");
            writer.append("c");
            writer.append("d");

            // 建议手动刷新
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

文件拷贝

/**
 * 使用文件字符输入输出流进行文件复制,但是这种方式只能复制普通文本文件。
 */
public class FileReaderFileWriterCopy {
    public static void main(String[] args) {
        try(FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\document\\02-第一章 Java开发环境搭建.pptx");
            FileWriter writer = new FileWriter("E:\\x.ppt")){
            // 一边读一边写
            char[] chars = new char[3];
            int readCount = 0;
            while((readCount = reader.read(chars)) != -1){
                writer.write(chars, 0, readCount);
            }
            // 刷新
            writer.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
// 新的内容
// 以下讲解内容有一些代码是和线程有关系的。大致理解一下。
// 或者说这个代码死记硬背也是可以的。
// 从类路径当中加载资源。
// Thread.currentThread() 获取当前线程
// Thread.currentThread().getContextClassLoader() 获取当前线程的类加载器
// getResource()方法就表示从类的根路径下开始加载资源
// 注意:这种方式只能从类路径当中加载资源。如果这个资源是放在类路径之外的,这种方式不合适。
// 如果你代码是以下这种写法的,表示当前路径就是类的根路径。自动从类的根路径下开始加载资源。
// 这种方式的优点:通用,在进行系统移植的时候,这种方式仍然是通用的。适应性强。
// 这种方式的缺点:资源必须放在类路径当中。没有在类路径下,是无法加载到的。
String path = Thread.currentThread().getContextClassLoader().getResource("test/file").getPath();
System.out.println(path);

FileInputStream in = new FileInputStream(path);

缓冲流

BufferedInputStream、BufferedOutputStream(适合读写非普通文本文件)
BufferedReader、BufferedWriter(适合读写普通文本文件。)
缓冲流的读写速度快,原理是:在内存中准备了一个缓存。读的时候从缓存中读。写的时候将缓存中的数据一次写出。都是在减少和磁盘的交互次数。如何理解缓冲区?家里盖房子,有一堆砖头要搬在工地100米外,单字节的读取就好比你一个人每次搬一块砖头,从堆砖头的地方搬到工地,这样肯定效率低下。然而聪明的人类会用小推车,每次先搬砖头搬到小车上,再利用小推车运到工地上去,这样你再从小推车上取砖头是不是方便多了呀!这样效率就会大大提高,缓冲流就好比我们的小推车,给数据暂时提供一个可存放的空间。
缓冲流都是处理流/包装流。FileInputStream/FileOutputStream是节点流。
关闭流只需要关闭最外层的处理流即可,通过源码就可以看到,当关闭处理流时,底层节点流也会关闭。
输出效率是如何提高的?在缓冲区中先将字符数据存储起来,当缓冲区达到一定大小或者需要刷新缓冲区时,再将数据一次性输出到目标设备。
输入效率是如何提高的? read()方法从缓冲区中读取数据。当缓冲区中的数据不足时,它会自动从底层输入流中读取一定大小的数据,并将数据存储到缓冲区中。大部分情况下,我们调用read()方法时,都是从缓冲区中读取,而不需要和硬盘交互。
可以编写拷贝的程序测试一下缓冲流的效率是否提高了!
缓冲流的特有方法(输入流):以下两个方法的作用是允许我们在读取数据流时回退到原来的位置(重复读取数据时用)
void mark(int readAheadLimit); 标记位置(在Java21版本中,参数无意义。低版本JDK中参数表示在标记处最多可以读取的字符数量,如果你读取的字符数超出的上限值,则调用reset()方法时出现IOException。)
void reset(); 重新回到上一次标记的位置
这两个方法有先后顺序:先mark再reset,另外这两个方法不是在所有流中都能用。有些流中有这个方法,但是不能用。

字节缓冲流

/**
 * 1. java.io.BufferedInputStream的用法和FileInputStream用法相同。
 *
 * 2. 他们的不同点是:
 *      FileInputStream是节点流。
 *      BufferedInputStream是缓冲流(包装流/处理流)。这个流的效率高。自带缓冲区。并且自己维护这个缓冲区。读大文件的时候建议采用这个缓冲流来读取。
 *
 * 3. BufferedInputStream对 FileInputStream 进行了功能增强。增加了一个缓冲区的功能。
 *
 * 4. 怎么创建一个BufferedInputStream对象呢?构造方法:
 *      BufferedInputStream(InputStream in)
 *
 */
public class BufferedInputStreamTest01 {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        try {
            // 创建节点流
            //FileInputStream in = new FileInputStream("file.txt");
            // 创建包装流
            //bis = new BufferedInputStream(in);

            // 组合起来写
            bis = new BufferedInputStream(new FileInputStream("file.txt"));

            // 读,和FileInputStream用法完全相同
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while((readCount = bis.read(bytes)) != -1){
                System.out.print(new String(bytes, 0, readCount));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 包装流以及节点流,你只需要关闭最外层的那个包装流即可。节点流不需要手动关闭。
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
/**
 * 1. java.io.BufferedOutputStream也是一个缓冲流。属于输出流。
 * 2. 怎么创建BufferedOutputStream对象?
 *      BufferedOutputStream(OutputStream out)
 * 3. FileOutputStream是节点流。 BufferedOutputStream是包装流。
 */
public class BufferedOutputStreamTest01 {
    public static void main(String[] args) {
        BufferedOutputStream bos = null;
        try {
            bos = new BufferedOutputStream(new FileOutputStream("file.txt"));

            bos.write("动力节点".getBytes());

            // 缓冲流需要手动刷新。
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    // 只需要关闭最外层的包装流即可。
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

文件拷贝

/**
 * 使用BufferedInputStream BufferedOutputStream完成文件的复制。
 */
public class BufferedInputOutputStreamCopy {
    public static void main(String[] args) {

        long begin = System.currentTimeMillis();

        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\powernode\\02-JavaSE\\video\\上\\012-第一章Java的加载与执行原理.avi"));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:/012-第一章Java的加载与执行原理.avi"))){

            // 一边读一边写
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while((readCount = bis.read(bytes)) != -1){
                bos.write(bytes, 0, readCount);
            }

            // 手动刷新
            bos.flush();

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        long end = System.currentTimeMillis();
        System.out.println("带有缓冲区的拷贝耗时"+(end - begin)+"毫秒"); // 671
    }
}

字符缓冲流

public class BufferedReaderTest01 {
    public static void main(String[] args) throws Exception{

        // BufferedWriter bw = new BufferedWriter(new FileWriter(""));

        // 创建带有缓冲区的字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("file.txt"));

        // 开始读(br.readLine()方法每次读取一行,如果读取不到任何数据,则返回null。)
        String s = null;
        while((s = br.readLine()) != null) {
            System.out.println(s);
        }

        br.close();
    }
}

mark 和 reset

/**
 * BufferedReader/BufferedInputStream的两个方法:
 *      1. mark方法:在当前的位置上打标记
 *      2. reset方法:回到上一次打标记的位置
 *
 *      这两个方法的调用顺序是:
 *          先调用mark,再调用reset。
 *      这两个方法组合起来完成的任务是:某段内容重复读取。
 */
public class BufferedReaderMarkTest {
    public static void main(String[] args) throws Exception{

        // 创建文件字符输入流(这个看起来像节点流,其实不是,为什么?一会再说!!!!)
        //FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\code\\file1.txt");
        // 创建带有缓冲区的字符输入流(一般把BufferedReader叫做处理流/包装流)
        //BufferedReader br = new BufferedReader(reader);

        BufferedReader br = new BufferedReader(new FileReader("E:\\powernode\\02-JavaSE\\code\\file1.txt"));

        // 开始读
        System.out.println(br.read());
        System.out.println(br.read());
        System.out.println(br.read());

        // 打标记
        br.mark(3);

        System.out.println(br.read());
        System.out.println(br.read());

        // 回到上一次打标记的位置上
        br.reset();

        // 重新读刚才的内容
        System.out.println(br.read());
        System.out.println(br.read());

        br.reset();

        System.out.println(br.read());
        System.out.println(br.read());

        // 关闭流
        br.close();
    }
}

转换流

InputStreamReader(主要解决读的乱码问题)

InputStreamReader为转换流,属于字符流。
作用是将文件中的字节转换为程序中的字符。转换过程是一个解码的过程。
常用的构造方法:
InputStreamReader(InputStream in, String charsetName) // 指定字符集
InputStreamReader(InputStream in) // 采用平台默认字符集
乱码是如何产生的?文件的字符集和构造方法上指定的字符集不一致。
FileReader是InputStreamReader的子类。本质上以下代码是一样的:
Reader reader = new InputStreamReader(new FileInputStream(“file.txt”)); //采用平台默认字符集
Reader reader = new FileReader(“file.txt”); //采用平台默认字符集
因此FileReader的出现简化了代码的编写。
以下代码本质上也是一样的:
Reader reader = new InputStreamReader(new FileInputStream(“file.txt”), “GBK”);
Reader reader = new FileReader("e:/file1.txt", Charset.forName("GBK"));
/**
 * 使用InputStreamReader时,可以指定解码的字符集。用来解决读过程中的乱码问题。
 * InputStreamReader是一个字符流。是一个转换流。
 * InputStreamReader是一个输入的过程。
 * InputStreamReader是一个解码的过程。
 *
 * InputStreamReader常用的构造方法:
 *      InputStreamReader(InputStream in) 采用平台默认的字符集进行解码。
 *      InputStreamReader(InputStream in, String charsetName) 采用指定的字符集进行解码。
 *
 * FileReader实际上是InputStreamReader的子类。
 * FileReader也是一个包装流,不是节点流。
 */
public class InputStreamReaderDecodingTest {

    public static void main(String[] args) throws Exception{
        // 创建一个转换流对象(输入流)
        // 节点流
        //FileInputStream in = new FileInputStream("");
        // 包装流
        //InputStreamReader isr = new InputStreamReader(in);

        //InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\powernode\\02-JavaSE\\code\\file3.txt"), "GBK");

        // 以上代码太长了。在IO流的继承体系结构中,IO流又给InputStreamReader提供了一个子类:FileReader
        // 代码可以这样写了:
        //FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.defaultCharset());
        FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.forName("GBK"));

        // 开始读
        int readCount = 0;
        char[] chars = new char[1024];
        while((readCount = isr.read(chars)) != -1){
            System.out.print(new String(chars, 0, readCount));
        }
        // 关闭流
        isr.close();
    }
}

OutputStreamWriter(主要解决写的乱码问题)

OutputStreamWriter是转换流,属于字符流。
作用是将程序中的字符转换为文件中的字节。这个过程是一个编码的过程。
常用构造方法:
OutputStreamWriter(OutputStream out, String charsetName) // 使用指定的字符集
OutputStreamWriter(OutputStream out) //采用平台默认字符集
乱码是如何产生的?文件的字符集与程序中构造方法上的字符集不一致。
FileWriter是OutputStreamWriter的子类。以下代码本质上是一样的:
Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”)); // 采用平台默认字符集
Writer writer = new FileWriter(“file1.txt”); // 采用平台默认字符集
因此FileWriter的出现,简化了代码。
以下代码本质上也是一样的:
Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”), “GBK”);
Writer writer = new FileWriter(“file1.txt”, Charset.forName(“GBK”));
/**
 * OutputStreamWriter也是一个字符流。也是一个转换流。
 * OutputStreamWriter是一个编码的过程。
 * 如果OutputStreamWriter在编码的过程中使用的字符集和文件的字符集不一致时会出现乱码。
 *
 * FileWriter是OutputStreamWriter的子类。
 * FileWriter的出现简化了java代码。
 * FileWriter是一个包装流,不是节点流。
 */
public class OutputStreamWriterEncodingTest {
    public static void main(String[] args) throws Exception{

        // 创建转换流对象OutputStreamWriter
        // 以下代码采用的是UTF-8的字符集进行编码。(采用平台默认的字符集)
        // 注意:以下代码中输出流以覆盖的方式输出/写。
        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"));

        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"), "GBK");

        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");

        /*OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true));*/

        /*OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");*/

        FileWriter osw = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file4.txt", Charset.forName("UTF-8"), true);

        // 开始写
        osw.write("来动力节点学Java");

        osw.flush();;
        osw.close();
    }
}

数据流

DataOutputStream/DataInputStream

这两个流都是包装流,读写数据专用的流。
DataOutputStream直接将程序中的数据写入文件,不需要转码,效率高。程序中是什么样子,原封不动的写出去。写完后,文件是打不开的。即使打开也是乱码,文件中直接存储的是二进制。
使用DataOutputStream写的文件,只能使用DataInputStream去读取。并且读取的顺序需要和写入的顺序一致,这样才能保证数据恢复原样。
构造方法:
DataInputStream(InputStream in)
DataOutputStream(OutputStream out)
写的方法:
writeByte()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble()、writeBoolean()、writeChar()、writeUTF(String)
读的方法:
readByte()、readShort()、readInt()、readLong()、readFloat()、readDouble()、readBoolean()、readChar()、readUTF()
/**
 * java.io.DataOutputStream:数据流(数据字节输出流)
 * 作用:将java程序中的数据直接写入到文件,写到文件中就是二进制。
 * DataOutputStream写的效率很高,原因是:写的过程不需要转码。
 * DataOutputStream写到文件中的数据,只能由DataInputStream来读取。
 */
public class DataOutputStreamTest {
    public static void main(String[] args) throws Exception{
        // 节点流
        //OutputStream os = new FileOutputStream("data");
        // 包装流
        //DataOutputStream dos = new DataOutputStream(os);

        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));

        // 准备数据
        byte b = -127;
        short s = 32767;
        int i = 2147483647;
        long l = 1111111111L;
        float f = 3.0F;
        double d = 3.14;
        boolean flag = false;
        char c = '国';
        String str = "动力节点";

        // 开始写
        dos.writeByte(b);
        dos.writeShort(s);
        dos.writeInt(i);
        dos.writeLong(l);
        dos.writeFloat(f);
        dos.writeDouble(d);
        dos.writeBoolean(flag);
        dos.writeChar(c);
        dos.writeUTF(str);

        dos.flush();
        dos.close();
    }
}
/**
 * java.io.DataInputStream:数据流(数据字节输入流)
 * 作用:专门用来读取使用DataOutputStream流写入的文件。
 * 注意:读取的顺序要和写入的顺序一致。(要不然无法恢复原样。)
 */
public class DataInputStreamTest {
    public static void main(String[] args) throws Exception{
        // 创建数据字节输入流对象
        DataInputStream dis = new DataInputStream(new FileInputStream("data"));

        //System.out.println(dis.readBoolean());

        // 开始读
        byte b = dis.readByte();
        short s = dis.readShort();
        int i = dis.readInt();
        long l = dis.readLong();
        float f = dis.readFloat();
        double d = dis.readDouble();
        boolean flag = dis.readBoolean();
        char c = dis.readChar();
        String str = dis.readUTF();

        System.out.println(b);
        System.out.println(s);
        System.out.println(i);
        System.out.println(l);
        System.out.println(f);
        System.out.println(d);
        System.out.println(flag);
        System.out.println(c);
        System.out.println(str);

        // 关闭流
        dis.close();

        /*FileInputStream fis = new FileInputStream("data");

        System.out.println(fis.read());
        System.out.println(fis.read());
        System.out.println(fis.read());
        System.out.println(fis.read());

        fis.close();*/


    }
}

对象流

ObjectOutputStream/ObjectInputStream

通过这两个流,可以完成对象的序列化和反序列化。
序列化(Serial):将Java对象转换为字节序列。(为了方便在网络中传输),使用ObjectOutputStream序列化。
反序列化(DeSerial):将字节序列转换为Java对象。使用ObjectInputStream进行反序列化。
参与序列化和反序列化的java对象必须实现一个标志性接口:java.io.Serializable
实现了Serializable接口的类,编译器会自动给该类添加序列化版本号的属性:serialVersionUID
在java中,是通过“类名 + 序列化版本号”来进行类的区分的。
serialVersionUID实际上是一种安全机制。在反序列化的时候,JVM会去检查存储Java对象的文件中的class的序列化版本号是否和当前Java程序中的class的序列化版本号是否一致。如果一致则可以反序列化。如果不一致则报错。
如果一个类实现了Serializable接口,还是建议将序列化版本号固定死,建议显示的定义出来,原因是:类有可能在开发中升级(改动),升级后会重新编译,如果没有固定死,编译器会重新分配一个新的序列化版本号,导致之前序列化的对象无法反序列化。显示定义序列化版本号的语法:private static final long serialVersionUID = XXL;
为了保证显示定义的序列化版本号不会写错,建议使用 @java.io.Serial 注解进行标注。并且使用它还可以帮助我们随机生成序列化版本号。
不参与序列化的属性需要使用瞬时关键字修饰:transient
/**
 * java.io.ObjectOutputStream:对象流(对象字节输出流)
 * 1. 它的作用是完成对象的序列化过程。
 * 2. 它可以将JVM当中的Java对象序列化到文件中/网络中。
 * 3. 序列化:将Java对象转换为字节序列的过程。(字节序列可以在网络中传输。)
 * 4. 序列化:serial
 */
public class ObjectOutputStreamTest {
    public static void main(String[] args) throws Exception{
        // 创建“对象字节输出流”对象
        // 包装流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object"));

        // 准备一个Java对象
        Date nowTime = new Date();

        // 序列化 serial
        oos.writeObject(nowTime);

        // 刷新
        oos.flush();

        // 关闭
        oos.close();
    }
}
/**
 * java.io.ObjectInputStream:对象流(对象字节输入流)
 * 1. 专门完成反序列化的。(将字节序列转换成JVM当中的java对象。)
 */
public class ObjectInputStreamTest {
    public static void main(String[] args) throws Exception{
        // 包装流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object"));

        // 读
        Object o = ois.readObject();

        System.out.println(o);

        // 关闭
        ois.close();
    }
}

序列化和反序列化自定义类型

/**
 * 1. 重点:凡是参与序列化和反序列化的对象必须实现 java.io.Serializable 可序列化的接口。
 * 2. 这个接口是一个标志接口,没有任何方法。只是起到一个标记的作用。
 * 3. 它到底是标记什么呢??????
 * 4. 当java程序中类实现了Serializable接口,编译器会自动给该类添加一个“序列化版本号”。
 *      序列化版本号:serialVersionUID
 * 5. 序列化版本号有什么用?
 *      在Java语言中是如何区分class版本的?
 *      首先通过类的名字,然后再通过序列化版本号进行区分的。
 *      在java语言中,不能仅仅通过一个类的名字来进行类的区分,这样太危险了。
 * 6. 为了保证序列化的安全,只有同一个class才能进行序列化和反序列化。在java中是如何保证同一个class的?
 *      类名 + 序列化版本号:serialVersionUID
 *
 * java.io.InvalidClassException: com.powernode.javase.io.Student;
 * local class incompatible:
 *      stream classdesc serialVersionUID = -4936871645261081394,  (三年前的学生对象,是三年前的Student.class创建的学生对象。)
 *      local class serialVersionUID = 5009257763737485728  (三年后,Student.class升级了。导致了版本发生了变化。)
 */
public class Student implements Serializable {

    // 建议:不是必须的。
    // 如果你确定这个类确实还是以前的那个类。类本身是合法的。没有问题。
    // 建议你将序列化版本号写死!
    @Serial
    private static final long serialVersionUID = -7005027670916214239L;

    private String name;
    private transient int age; // transient关键字修饰的属性不会参与序列化。

    private String addr;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

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

    public Student() {
    }

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

    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 class ObjectOutputStreamTest03 {
    public static void main(String[] args) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student"));

        Student stu = new Student("zhangsan", 20);

        oos.writeObject(stu);

        oos.flush();

        oos.close();
    }
}
/**
 * 反序列化过程:将文件中的Student字节序列恢复到内存中,变成Student对象。
 */
public class ObjectInputStreamTest03 {

    public static void main(String[] args) throws Exception{

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student"));

        System.out.println(ois.readObject());

        ois.close();
    }
}

注意

 其实ObjectOutputStream中也有这些方法,和DataOutputStream中的方法一样。
oos.writeInt(100);
oos.writeBoolean(false);
oos.writeUTF("张三");

打印流

PrintStream

打印流(字节形式)
主要用在打印方面,提供便捷的打印方法和格式化输出。主要打印内容到文件或控制台。
常用方法:
print(Type x)
println(Type x)
便捷在哪里?
直接输出各种数据类型
自动刷新和自动换行(println方法)
支持字符串转义
自动编码(自动根据环境选择合适的编码方式)
格式化输出?调用printf方法。
%s 表示字符串
%d 表示整数
%f 表示小数(%.2f 这个格式就代表保留两位小数的数字。)
%c 表示字符
/**
 * 1. java.io.PrintStream:打印流(专业的负责打印的流,字节形式。)
 * 2. PrintStream不需要手动刷新,自动刷新。
 */
public class PrintStreamTest {
    public static void main(String[] args) throws Exception{
        // 创建一个打印流对象
        // 构造方法:PrintStream(OutputStream out)
        // 构造方法:PrintStream(String fileName)
        PrintStream ps = new PrintStream("log1");

        // 没有这样的构造方法。
        //PrintStream ps2 = new PrintStream(new FileWriter(""));

        //PrintStream ps2 = new PrintStream(new FileOutputStream("log1"));

        // 打印流可以打印各种数据类型数据。
        ps.print(100);
        ps.println(false);
        ps.println("abc");
        ps.println('T');
        ps.println(3.14);
        ps.println("hell world");
        ps.println(200);

        ps.println("\"hello world!\"");

        String name = "张三";
        double score = 95.5;

        ps.printf("姓名:%s,考试成绩:%.2f", name, score);

        // 关闭
        ps.close();
    }
}

PrintWriter

打印流(字符形式)注意PrintWriter使用时需要手动调用flush()方法进行刷新。
比PrintStream多一个构造方法,PrintStream参数只能是OutputStream类型,但PrintWriter参数可以是OutputStream,也可以是Writer。
常用方法:
print(Type x)
println(Type x)
同样,也可以支持格式化输出,调用printf方法。
/**
 * java.io.PrintWriter:专门负责打印的流。(字符形式)
 * 需要手动刷新flush。
 * PrintWriter比PrintStream多一个构造方法:
 *      PrintStream构造方法:
 *          PrintStream(OutputStream)
 *      PrintWriter构造方法:
 *          PrintWriter(OutputStream)
 *          PrintWriter(Writer)
 */
public class PrintWriterTest {
    public static void main(String[] args) throws Exception{
        // 创建字符打印流
        //PrintWriter pw = new PrintWriter(new FileOutputStream("log2"));

        PrintWriter pw = new PrintWriter(new FileWriter("log2"), true);

        // 打印
        pw.println("world hello!!!");
        pw.println("zhangsan hello!!!");

        // 刷新
        //pw.flush();

        // 关闭
        pw.close();
    }
}

标准输入流&标准输出流

标准输入流

System.in获取到的InputStream就是一个标准输入流。
标准输入流是用来接收用户在控制台上的输入的。(普通的输入流,是获得文件或网络中的数据)
标准输入流不需要关闭。(它是一个系统级的全局的流,JVM负责最后的关闭。)
也可以使用BufferedReader对标准输入流进行包装。这样可以方便的接收用户在控制台上的输入。(这种方式太麻烦了,因此JDK中提供了更好用的Scanner。)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
当然,你也可以修改输入流的方向(System.setIn())。让其指向文件。
/**
 * 标准输入流:System.in
 *      1. 标准输入流怎么获取?
 *          System.in
 *      2. 标准输入流是从哪个数据源读取数据的?
 *          控制台。
 *      3. 普通输入流是从哪个数据源读取数据的?
 *          文件或者网络或者其他.....
 *      4. 标准输入流是一个全局的输入流,不需要手动关闭。JVM退出的时候,JVM会负责关闭这个流。
 */
public class SystemInTest {
    public static void main(String[] args) throws Exception{

        // 获取标准输入流对象。(直接通过系统类System中的in属性来获取标准输入流对象。)
        InputStream in = System.in;

        // 开始读
        byte[] bytes = new byte[1024];
        int readCount = in.read(bytes);

        for (int i = 0; i < readCount; i++) {
            System.out.println(bytes[i]);
        }
    }
}
/**
 * 对于标准输入流来说,也是可以改变数据源的。不让其从控制台读数据。也可以让其从文件中或网络中读取数据。
 */
public class SystemInTest02 {
    public static void main(String[] args) throws Exception{
        // 修改标准输入流的数据源。
        System.setIn(new FileInputStream("log2"));

        // 获取标准输入流
        InputStream in = System.in;

        byte[] bytes = new byte[1024];
        int readCount = 0;
        while((readCount = in.read(bytes)) != -1){
            System.out.print(new String(bytes, 0, readCount));
        }

    }
}
/**
 * 使用BufferedReader去包装一下这个标准输入流,来完成从键盘接收用户的输入。
 */
public class SystemInTest03 {
    public static void main(String[] args) throws Exception{

        // 创建BufferedReader对象
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        /*InputStream in = System.in;
        Reader reader = new InputStreamReader(in);
        BufferedReader br = new BufferedReader(reader);*/

        String s = null;
        while((s = br.readLine()) != null){
            if("exit".equals(s)){
                break;
            }
            System.out.println("您输入了:" + s);
        }

        /*Scanner scanner = new Scanner(System.in);

        String name = scanner.next();
        System.out.println("您的姓名是:" + name);*/

    }
}

标准输出流

System.out获取到的PrintStream就是一个标准输出流。
标准输出流是用来向控制台上输出的。(普通的输出流,是向文件和网络等输出的。)
标准输出流不需要关闭(它是一个系统级的全局的流,JVM负责最后的关闭。)也不需要手动刷新。
当然,你也可以修改输出流的方向(System.setOut())。让其指向文件。
/**
 * 标准输出流:System.out
 *      1. 标准输出流怎么获取?
 *          System.out
 *      2. 标准输出流是向哪里输出呢?
 *          控制台。
 *      3. 普通输出流是向哪里输出呢?
 *          文件或者网络或者其他.....
 *      4. 标准输出流是一个全局的输出流,不需要手动关闭。JVM退出的时候,JVM会负责关闭这个流。
 */
public class SystemOutTest {
    public static void main(String[] args) throws Exception {

        // 获取标准输出流,标准输出流默认会向控制台输出。
        PrintStream out = System.out;

        // 输出
        out.println("hello world");
        out.println("hello world");
        out.println("hello world");
        out.println("hello world");
        out.println("hello world");

        // 标准输出流也是可以改变输出方向的。
        System.setOut(new PrintStream("log"));

        System.out.println("zhangsan");
        System.out.println("lisi");
        System.out.println("wangwu");
        System.out.println("zhaoliu");

        // 获取系统当前时间
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String str = sdf.format(now);
        System.out.println(str + ": SystemOutTest's main method invoked!");
    }
}

File类

File类不是IO流,和IO的四个头领没有关系。因此通过File是无法读写文件。
File类是路径的抽象表示形式,这个路径可能是目录,也可能是文件。因此File代表了某个文件或某个目录。
File类常用的构造方法:File(String pathname);
File类的常用方法:
boolean createNewFile();			boolean delete();
boolean exists();				String getAbsolutePath();
String getName();				String getParent();
boolean isAbsolute();				boolean isDirectory();
boolean isFile();				boolean isHidden();
long lastModified();				long length();
File[] listFiles();				File[] listFiles(FilenameFilter filter);
boolean mkdir();				boolean mkdirs();
boolean renameTo(File dest);		boolean setReadOnly();
boolean setWritable(boolean writable);
编写程序要求可以完成目录的拷贝。
/**
 * java.io.File
 *      1.File是路径的抽象表示形式。
 *      2.File和IO流没有继承关系,父类是Object,通过File不能完成文件的读和写。
 *      3.File可能是一个文件,也可能是一个目录。
 */
public class FileTest01 {
    public static void main(String[] args) throws Exception{
        // 构造一个File对象
        File file = new File("e:/file");

        // 调用File对象的相关方法
        System.out.println(file.exists() ? "存在" : "不存在");

        // 如果不存在则以新文件的形式创建
        /*if(!file.exists()){
            // 以新的文件的形式创建出来
            file.createNewFile();
        }*/

        // 如果不存在则以目录的形式新建
        if(!file.exists()){
            file.mkdir();
        }

        // 构造一个File对象
        File file2 = new File("e:/a/b/c/d");
        // 如果不存在则以目录的形式新建
        if(!file2.exists()){
            file2.mkdirs();
        }
    }
}
public class FileTest03 {
    public static void main(String[] args) {
        // 构建一个File对象
        File file1 = new File("E:/file");

        // 判断该文件是否是隐藏文件
        System.out.println(file1.isHidden() ? "隐藏文件" : "非隐藏文件");

        // 获取文件的最后修改时间点
        long l = file1.lastModified();
        Date time = new Date(l);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String str = sdf.format(time);
        System.out.println("文件最后修改时间点:" + str);

        // 获取文件的大小:总字节数
        System.out.println(file1.getName() + "文件的总字节数:" + file1.length() + "字节");

        // 重命名
        File file2 = new File("file2");
        file1.renameTo(file2);

    }
}
/**
 * File类的常用方法:File[] listFiles();
 */
public class FileTest04 {
    public static void main(String[] args) {

        File file = new File("E:\\powernode\\02-JavaSE\\document");

        // 获取所有的子文件,包括子目录。
        File[] files = file.listFiles();

        // 遍历数组
        for(File f : files){
            System.out.println(f.getName());
        }

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

        File file1 = new File("E:\\powernode\\02-JavaSE\\document");

        File[] files1 = file1.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                //过滤作用
                /*if (name.endsWith(".mdj")) {
                    return true;
                }
                return false;*/
                return name.endsWith(".mdj");
            }
        });

        for(File f : files1){
            System.out.println(f.getName());
        }
    }
}

目录的递归拷贝

/**
 * 目录拷贝。
 */
public class CopyDir {
    public static void main(String[] args) {

        // 拷贝源
        File src = new File("E:\\powernode\\02-JavaSE\\code"); // E:\powernode\02-JavaSE\code\chapter01\A.java

        // 拷贝目标
        File dest = new File("E:\\a\\b\\c"); // E:\a\b\c\powernode\02-JavaSE\code\chapter01\A.java

        // 开始拷贝
        copy(src, dest);
    }

    /**
     * 拷贝目录的方法
     * @param src 拷贝源
     * @param dest 拷贝目标
     */
    private static void copy(File src, File dest) {
        if(src.isFile()){
            // 是文件的时候要拷贝。
            try(FileInputStream in = new FileInputStream(src);
                FileOutputStream out = new FileOutputStream(dest.getAbsoluteFile() + src.getAbsolutePath().substring(2))){
                // 开始拷贝
                byte[] bytes = new byte[1024 * 1024];
                int readCount = 0;
                while((readCount = in.read(bytes)) != -1){
                    out.write(bytes, 0, readCount);
                }
                out.flush();
            }catch(IOException e){
                e.printStackTrace();
            }
            return;
        }
        // 假设src是一个目录
        // 程序能够执行到此处一定是一个目录
        // 创建目录
        File newDir = new File(dest.getAbsolutePath() + src.getAbsolutePath().substring(2));
        if(!newDir.exists()){
            newDir.mkdirs();
        }
        File[] files = src.listFiles();
        for (File file : files){
            //System.out.println(file.getAbsolutePath());
            copy(file, dest);
        }
    }
}

读取属性配置文件

Properties + IO

xxx.properties文件称为属性配置文件。
属性配置文件可以配置一些简单的信息,例如连接数据库的信息通常配置到属性文件中。这样可以做到在不修改java代码的前提下,切换数据库。
属性配置文件的格式:
key1=value1
key2=value2
key3=value3
注意:使用 # 进行注释。key不能重复,key重复则value覆盖。key和value之间用等号分割。等号两边不要有空格。
Java中如何读取属性配置文件?
当然,也可以使用Java中的工具类快速获取配置信息:ResourceBundle
这种方式要求文件必须是xxx.properties
属性配置文件必须放在类路径当中
/**
 * 使用Properties集合类 + IO流来读取属性配置文件。
 * 将属性配置文件中的配置信息加载到内存中。
 */
public class LoadProperties {
    public static void main(String[] args) throws Exception {
        // 创建输入流对象
        //FileReader reader = new FileReader("chapter08/src/db.properties");
        String path = Thread.currentThread().getContextClassLoader().getResource("jdbc.properties").getPath();
        //该路径不要有中文
        FileReader reader = new FileReader(path);

        // 创建一个Map集合(属性类对象)
        Properties pro = new Properties();

        // 加载:将jdbc.properties文件中的配置信息加载到Properties对象中。
        pro.load(reader);

        // 获取所有key
        /*Enumeration<?> names = pro.propertyNames();
        while (names.hasMoreElements()) {
            String name = (String)names.nextElement();
            String value = pro.getProperty(name);
            System.out.println(name + "=" + value);
        }*/

        // 通过key来获取value
        String driver = pro.getProperty("driver");
        String url = pro.getProperty("url");
        String user = pro.getProperty("user");
        String password = pro.getProperty("password");

        System.out.println(driver);
        System.out.println(url);
        System.out.println(user);
        System.out.println(password);

        // 关闭输入流
        reader.close();
    }
}
/**
 * 使用JDK中提供的资源绑定器来绑定属性配置文件。
 */
public class BundleProperties {
    public static void main(String[] args) {
        // 获取资源绑定器对象
        // 使用这个工具要求文件也必须是一个属性配置文件。xxx.properties
        ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.jdbc");
        //ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/jdbc");

        // 这个获取的是类的根路径下的jdbc.properties文件。
        //ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        // 这个代码的意思是从类的根路径下找db.properties文件。
        //ResourceBundle bundle = ResourceBundle.getBundle("db");

        // 以下两行都是错误的:资源找不到。
        //ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.db.properties");
        //ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/db.properties");

        // 通过key获取value
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");

        System.out.println(driver);
        System.out.println(url);
        System.out.println(user);
        System.out.println(password);
    }
}

装饰者模式

* 0. 装饰器设计模式的主要目标:在松耦合的前提下,能够完成功能的扩展。
* 1. 在装饰器设计模式中有两个非常重要的角色:装饰者,被装饰者。
* 2. 装饰器设计模式当中要求:装饰者 与 被装饰者 应实现同一个接口/同一些接口,继承同一个抽象类....
* 3. 为什么装饰者 与 被装饰者 要实现同一个接口呢?
*      因为实现了同一个接口之后,对于客户端程序来说,使用装饰者的时候就向在使用被装饰者一样。
* 4. 装饰者含有被装饰者的引用。(A has a B。尽量使用has a【耦合度低一些】。不要使用is a。)

压缩和解压缩流

GZIPOutputStream(压缩)

使用GZIPOutputStream可以将文件制作为压缩文件,压缩文件的格式为 .gz 格式。
核心代码:
FileInputStream fis = new FileInputStream("d:/test.txt"); // 被压缩的文件:test.txt
GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream("d:/test.txt.gz")) // 压缩后的文件
接下来就是边读边写:
int length;
while ((length = fis.read(buffer)) > 0) {
gzos.write(buffer, 0, length);
}
gzos.finish(); // 在压缩完所有数据之后调用finish()方法,以确保所有未压缩的数据都被刷新到输出流中,并生成必要的 Gzip 结束标记,标志着压缩数据的结束。
注意(补充):实际上所有的输出流中,只有带有缓冲区的流才需要手动刷新,节点流是不需要手动刷新的,节点流在关闭的时候会自动刷新。
public static void main(String[] args) throws Exception{

        // 创建文件字节输入流(读某个文件,这个文件将来就是被压缩的。)
        FileInputStream in = new FileInputStream("e:/test.txt");

        // 创建一个GZIP压缩流对象
        GZIPOutputStream gzip = new GZIPOutputStream(new FileOutputStream("e:/test.txt.gz"));

        // 开始压缩(一边读一边读)
        byte[] bytes = new byte[1024];
        int readCount = 0;
        while((readCount = in.read(bytes)) != -1){
            gzip.write(bytes, 0, readCount);
        }

        // 非常重要的代码需要调用
        // 刷新并且最终生成压缩文件。
        gzip.finish();

        // 关闭流
        in.close();
        gzip.close();
    }

GZIPInputStream(解压缩)

使用GZIPInputStream可以将 .gz 格式的压缩文件解压。
核心代码:
GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("d:/test.txt.gz"));
FileOutputStream out = new FileOutputStream("d:/test.txt");
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = gzip.read(bytes)) != -1){
out.write(bytes, 0, readCount);
}
public static void main(String[] args) throws Exception {
    // 创建GZIP解压缩流对象
    GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("e:/test.txt.gz"));

    // 创建文件字节输出流
    FileOutputStream out = new FileOutputStream("e:/test.txt");

    // 一边读一边写
    byte[] bytes = new byte[1024];
    int readCount = 0;
    while((readCount = gzip.read(bytes)) != -1){
        out.write(bytes, 0, readCount);
    }

    // 关闭流
    gzip.close();
    // 节点流关闭的时候会自动刷新,包装流是需要手动刷新的。(补充的知识点。)
    out.close();
}

字节数组流(内存流)

ByteArrayInputStream和ByteArrayOutputStream都是内存操作流,不需要打开和关闭文件等操作。这些流是非常常用的,可以将它们看作开发中的常用工具,能够方便地读写字节数组、图像数据等内存中的数据。
ByteArrayInputStream和ByteArrayOutputStream都是节点流。
ByteArrayOutputStream,将数据写入到内存中的字节数组当中。
ByteArrayInputStream,读取内存中某个字节数组中的数据。

public static void main(String[] args) {

    // ByteArrayOutputStream的基本用法。
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); //节点流

    // 开始写
    baos.write(1);
    baos.write(2);
    baos.write(3);

    // 怎么获取内存中的哪个byte[]数组呢?
    byte[] byteArray = baos.toByteArray();
    for (byte b : byteArray){
        System.out.println(b);
    }
}
public static void main(String[] args) throws Exception{

    // 节点流
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 包装流
    ObjectOutputStream oos = new ObjectOutputStream(baos);

    // 开始写
    oos.writeInt(100);
    oos.writeBoolean(false);
    oos.writeDouble(3.14);
    oos.writeUTF("动力节点");
    oos.writeObject(new Date());

    // 使用了包装流就需要手动刷新一下。
    oos.flush();

    // 获取内存中的大byte数组
    byte[] byteArray = baos.toByteArray();
    /*for(byte b : byteArray){
        System.out.println(b);
    }*/

    // 使用ByteArrayInputStream将上面这个byte数组恢复。
    // 读的过程,读内存中的大byte数组。
    // 节点流
    ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
    // 包装流
    ObjectInputStream ois = new ObjectInputStream(bais);

    // 开始读
    System.out.println(ois.readInt());
    System.out.println(ois.readBoolean());
    System.out.println(ois.readDouble());
    System.out.println(ois.readUTF());
    System.out.println(ois.readObject());
}

对象克隆

除了我们之前所讲的深克隆方式(之前的深克隆是重写clone()方法)。使用字节数组流也可以完成对象的深克隆。
原理是:将要克隆的Java对象写到内存中的字节数组中,再从内存中的字节数组中读取对象,读取到的对象就是一个深克隆。
目前为止,对象拷贝方式:
调用Object的clone方法,默认是浅克隆,需要深克隆的话,就需要重写clone方法。
可以通过序列化和反序列化完成对象的克隆。
也可以通过ByteArrayInputStream和ByteArrayOutputStream完成深克隆。

public static void main(String[] args) throws Exception{
        // 准备对象
        Address addr = new Address("北京", "朝阳");
        User user = new User("zhangsan", 20, addr);

        // 将Java对象写到一个byte数组中。
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.writeObject(user);

        oos.flush();

        // 从byte数组中读取数据恢复java对象
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);

        // 这就是哪个经过深拷贝之后的新对象
        User user2 = (User) ois.readObject();

        user2.getAddr().setCity("南京");

        System.out.println(user);
        System.out.println(user2);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java老狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值