[JavaSE]第十二章 IO流

12.1 File 类的使用

  • File 类声明在 java.io 包下
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
  • File 对象可以作为参数传递给流的构造器

12.1.1 常用的构造器

  • public File(String pathname)
    • 以 pathname 为路径创建 File 对象,可以是绝对路径或者相对路径
  • public File(String parent,String child)
    • 以 parent 为父路径,child 为子路径创建 File 对象
  • public File(File parent,String child)
    • 根据一个父 File 对象和子文件路径创建 File 对象
    @Test
    public void test1() {
        //构造器一
        File file1 = new File("data/hello.txt.txt");//相对路径
        File file2 = new File("D:\\shangguiguProject\\JavaProject\\data\\hello.txt.txt");//绝对路径

        System.out.println(file1);

        //构造器二
        File file3 = new File("D:\\shangguiguProject", "JavaProject");

        //构造器三
        File file4 = new File(file3, "data/hello.txt.txt");

    }

12.1.2 路径分隔符

  • 路径中的每级目录之间用一个路径分隔符隔开
  • 路径分隔符和系统有关:
    • windows 和 DOS 系统默认使用 \ 来表示
    • UNIX 和 URL 使用 / 来表示
    • 在 Windows 中使用 / 也可以

12.1.3 常用方法

  • public String getAbsolutePath():获取绝对路径
  • public String getPath():获取路径
  • public String getName():获取名称
  • public String getParent():获取上层文件目录路径。若无,返回 null
  • public long length():获取文件长度(即:字节数)。不能获取目录的长度。
  • public long lastModified():获取最后一次的修改时间,毫秒值
@Test
public void test2() {
    File file1 = new File("../data/hello.txt");
    File file2 = new File("D:/word");

    System.out.println(file1.getAbsolutePath());
    System.out.println(file1.getPath());
    System.out.println(file1.getName());
    System.out.println(file1.getParent());
    System.out.println(file1.length());
    System.out.println(file1.lastModified());

    System.out.println();

    System.out.println(file2.getAbsolutePath());
    System.out.println(file2.getPath());
    System.out.println(file2.getName());
    System.out.println(file2.getParent());
    System.out.println(file2.length());
    System.out.println(file2.lastModified());
}
  • public String[] list():获取指定目录下的所有文件或者文件目录的名称数组
  • public File[] listFiles():获取指定目录下的所有文件或者文件目录的 File 数组
@Test
public void test3() {
    File file1 = new File("D:\\shangguiguProject\\JavaProject");
    String[] list = file1.list();

    System.out.println(Arrays.toString(list));

    File[] files = file1.listFiles();
    assert files != null;
    for (File file: files) {
        System.out.println(file);
    }
}
  • public boolean renameTo(File dest):把文件重命名为指定的文件路径,要保证返回 true,需要 file1 在硬盘中是存在的,且 file2 不能在硬盘中存在
@Test
public void test4() {
    File file1 = new File("../data/hello.txt.txt");
    File file2 = new File("D:/word/hi.txt");

    boolean rename = file1.renameTo(file2);
    System.out.println(rename);
}
  • public boolean isDirectory():判断是否是文件目录
  • public boolean isFile():判断是否是文件
  • public boolean exists():判断是否存在
  • public boolean canRead():判断是否可读
  • public boolean canWrite():判断是否可写
  • public boolean isHidden():判断是否隐藏
@Test
public void test5() {
    File file1 = new File("../data/hello.txt.txt");

    System.out.println(file1.isDirectory());
    System.out.println(file1.isFile());
    System.out.println(file1.exists());
    System.out.println(file1.canRead());
    System.out.println(file1.canWrite());
    System.out.println(file1.isHidden());

    System.out.println();
    File file2 = new File("../data");

    System.out.println(file2.isDirectory());
    System.out.println(file2.isFile());
    System.out.println(file2.exists());
    System.out.println(file2.canRead());
    System.out.println(file2.canWrite());
    System.out.println(file2.isHidden());
}
  • public boolean createNewFile():创建文件。若文件存在,则不创建,返回 false
  • public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
  • public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
  • public boolean delete():删除文件或者文件夹
@Test
public void test6() throws IOException {
    //文件创建
    File file1 = new File("../data/hi.txt");
    if (! file1.exists()) {
        file1.createNewFile();
        System.out.println("创建成功");
    } else {//文件存在
        file1.delete();
        System.out.println("删除成功");
    }
}

@Test
public void test7() {
    //文件目录的创建
    File file1 = new File("../data/io2/io");
    boolean mkdir = file1.mkdir();
    if (mkdir) {
        System.out.println("创建成功");
    }

    boolean mkdirs = file1.mkdirs();
    if (mkdirs) {
        System.out.println("多层目录创建成功");
    }

    //删除目录时该目录下不能有子目录或文件
    boolean delete = file1.delete();
    if (delete) {
        System.out.println("目录删除成功");
    }
}

12.2 IO 流原理及流的分类

12.2.1 Java IO 原理

  • I/O 是 Input/Output 的缩写, I/O 技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等

  • Java 程序 中,对于数据的输入/输出操作以 流(stream) 的方式进行

  • java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据

  • 输入 input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中

  • 输出 output:将程序(内存)数据输出到磁盘、光盘等存储设备中

12.2.2 流的分类

  • 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)

  • 按数据流的流向不同分为:输入流,输出流

  • 按流的角色的不同分为:

    • 节点流:直接从数据源或目的地读写数据
    • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上
  • IO 流体系:如下表:

    分类字节输入流字节输出流字符输入流字符输出流
    抽象基类InputStreamOutputStreamReaderWriter
    访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
    访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
    访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
    访问字符串StringReaderStringWriter
    缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
    转换流InputStreamReaderOutputStreamWriter
    对象流ObjectInputStreamObjectOutputStream
    过滤流FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
    打印流PrintStreamPrinterWriter
    推回输入流PushbackInputStreamPushbackReader
    特殊流DataInputStreamDataOutputStream
  • InputStream 和 Reader 是所有 输入流 的基类

    • InputStream:

      • int read():从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1
      • int read(byte[] buffer):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数
      • int read(byte[] buffer, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1
    • Reader:

      • int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个

        字节的Unicode码),如果已到达流的末尾,则返回 -1

      • int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数

      • int read(char[] cbuf, int off, int len):将字符读入数组的某一部分。存到数组 cbuf 中,从 off 处开始存储,最多读 len 个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

  • OuputStream 和 Writer 是所有 输出流 的基类

    • OutputStream 和 Writer 也非常相似
      • void write(int b/int c);
      • void write(byte[] b/char[] cbuf);
      • void write(byte[] b/char[] buff, int off, int len);
      • void flush();:刷新该流的缓冲,则立即将它们写入预期目标
      • void close(); 需要先刷新,再关闭此流
  • 因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组,即以 String 对象作为参数

    • void write(String str);

    • void write(String str, int off, int len);

  • 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源

  • FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader

  • FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

12.3 节点流(文件流)

  • 对于非文本文件(图片、视频、.doc、.ppt)使用字节流进行处理
  • 文本文件(.txt、.java)使用字符流处理

12.3.1 字符输入流

@Test
public void fileReaderTest() {
    //1.实例化 File 类的对象,指明要操作的文件
    File file = new File("hello.txt");
    //2.提供具体的流
    FileReader fileReader = null;
    try {
        fileReader = new FileReader(file);
        //3.数据的读入
        /*int read = fileReader.read();
        while (read != -1) {
            System.out.print((char) read);
            read = fileReader.read();
        }*/
        int data;
        while ((data = fileReader.read()) != -1) {
            System.out.print((char)data);
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        try {
            assert fileReader != null;
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1() {
    //1.File类的实例化
    File file = new File("hello.txt");
    FileReader reader = null;
    try {
        //2.FileReader流的实例化
        reader = new FileReader(file);
        //3.读入的操作
        //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回 -1
        char[] cbuf = new char[5];
        int len;
        while ((len = reader.read(cbuf)) != -1) {
            //方式一
            //for (int i = 0; i < len; i++) {
            //	System.out.print(cbuf[i]);
            //}

            //方式二
            //错误的写法
            //String str = new String(cbuf);
            //System.out.print(str);
            //正确的写法
            String str2 = new String(cbuf, 0, len);
            System.out.print(str2);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        try {
            assert reader != null;
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

12.3.2 字符输出流

/*
从内存中写出数据到硬盘的文件中

说明:
1.输出操作,对应的File可以不存在
2.File对应的硬盘中的文件如果不存在,则会自动创建此文件
  如果存在:
     如果流使用的构造器是:FileWriter(file, false) / FileWriter(file):对原有文件进行覆盖
     如果流使用的构造器是:FileWriter(file, true): 对原有文件进行追加
     */
    @Test
    public void fileWriterTest() {
        //1.提供File类的对象,指明写出到的文件
        File file = new File("hello2.txt");
        FileWriter fileWriter = null;

        try {
            //2.提供FileWriter的对象,用于数据的写出
            fileWriter = new FileWriter(file, false);

            //3.写出的操作
            fileWriter.write("HelloWorld\n");
            fileWriter.write("Hello Java");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流
            try {
                assert fileWriter != null;
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void testFileReaderFileWriter() {
        //1.创建File类的对象,指明读入和写出的文件
        File inputFile = new File("hello.txt");
        File outputFile = new File("hello_copy.txt");
        FileReader fileReader = null;
        FileWriter fileWriter = null;

        try {
            //2.创建FileReader类和FileWriter类的对象
            fileReader = new FileReader(inputFile);
            fileWriter = new FileWriter(outputFile);

            //3.数据的读入和写出操作
            char[] cbuf = new char[5];
            int len;//记录每次读入cbuf数组中的字符的个数
            while ((len = fileReader.read(cbuf)) != -1) {
                //每次写入len个字符
                fileWriter.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流
            try {
                assert fileWriter != null;
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.3.3 字节输入、输出流

/*
实现对图片的复制
 */
@Test
public void testFileInputOutputStream() {
    //1.创建File类的对象,指明读入和写出的文件
    File inputFile = new File("../data/photo/u029.png");
    File outputFile = new File("../data/photo/u2.png");

    //2.创建FileReader类和FileWriter类的对象
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
        fis = new FileInputStream(inputFile);
        fos = new FileOutputStream(outputFile);

        //3.数据的读入和写出操作
        byte[] cbuf = new byte[5];
        int len;//记录每次读入cbuf数组中的字节的个数
        while ((len = fis.read(cbuf)) != -1) {
            //每次写入len个字符
            fos.write(cbuf, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        try {
            assert fos != null;
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

12.4 缓冲流

  • 为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个字节(8Kb)的缓冲区

image-20220110203427967

  • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为
    • BufferedInputStream 和 BufferedOutputStream
    • BufferedReader和 BufferedWriter
  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  • 当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性从文件中读取 8192 个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个 8192 个字节数组。
  • 使用方法 flush() 可以强制将缓冲区的内容全部写入输出流
  • flush() 方法的使用:手动将 buffer 中内容写入文件
  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
  • 如果是带缓冲区的流对象的 close() 方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

12.4.1 字节缓冲流

    public void bufferedInputOutputStream(String srcPath, String destPath) {
        //1.实例化File类
        File srcFile = new File(srcPath);
        File destFile = new File(destPath);

        //2.造流
        FileInputStream fis = null;
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //2.1 造节点流
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.操作数据
            byte[] bytes = new byte[1024];
            int len;

            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
                //bos.flush();//刷新缓冲区
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流:后用先关。先关闭外层的流,再关闭内层的流
            try {
                assert bos != null;
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //说明关闭外层流的同时,内存流也会自动的进行关闭。
            //关于内存流的关闭,我们可以省略
            //fos.close();
            //fis.close();
        }
    }

    @Test
    public void testBufferedInputOutputStream() {
        long startTime = System.currentTimeMillis();

        String srcPath = "C:\\Users\\14533\\Pictures\\QQplayerPic\\毕设录屏.mp4";
        String destPath = "C:\\Users\\14533\\Pictures\\QQplayerPic\\复制.mp4";

        bufferedInputOutputStream(srcPath, destPath);

        long endTime = System.currentTimeMillis();
        System.out.println("复制操作所花费的时间为:" + (endTime - startTime));//140
    }

12.4.2 字符缓冲流

	/*
    使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
     */
    @Test
    public void testBufferedReaderBufferedWriter() {
        //1.创建文件和响应的流
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp2.txt")));

            //2.读写操作
            //方式一:使用 char[] 数组
/*            char[] cbuf = new char[1024];
            int len;
            while ((len = br.read(cbuf)) != -1) {
                bw.write(cbuf, 0, len);
            }*/

            //方式二:使用 String
            String data;
            while ((data = br.readLine()) != null) {
                //readLine():不包含换行
                //方式一:换行
                bw.write(data + "\n");

                //方式二:换行
                //bw.write(data);
                //bw.newLine();//提供换行的操作
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //3.关闭流
            try {
                assert bw != null;
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.5 转换流

  • 转换流提供了在字节流和字符流之间的转换

  • Java API 提供了两个转换流:

    • InputStreamReader:将 InputStream 转换为 Reader
    • OutputStreamWriter:将 Writer 转换为 OutputStream
  • 字节流中的数据都是字符时,转成字符流操作更高效

  • 很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能

12.5.1 InputStreamReader

  • 实现将字节的输入流按指定字符集转换为字符的输入流
  • 构造器
    • public InputStreamReader(InputStream in)
    • public InputStreamReader(InputStream in,String charsetName)
@Test
public void test1() throws IOException {

    FileInputStream fis = new FileInputStream("dbcp.txt");
    //InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符编码
    //参数二指明了字符集,具体使用哪个字符集,取决于文件保存时使用的字符集
    InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);

    char[] chars = new char[20];
    int len;
    while ((len = isr.read(chars)) != -1) {

        String str = new String(chars, 0, len);
        System.out.println(str);
    }

    isr.close();
}

12.5.2 OutputStreamWriter

  • 实现将字符的输出流按指定字符集转换为字节的输出流
  • 构造器
    • public OutputStreamWriter(OutputStream out)
    • public OutputStreamWriter(OutputStream out,String charsetName)
@Test
public void test2() throws IOException{
    FileInputStream fis = new FileInputStream("dbcp.txt");
    FileOutputStream fos = new FileOutputStream("dbcp_gbk.txt");

    InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");

    char[] cbuf = new char[20];
    int len;
    while ((len = isr.read(cbuf)) != -1) {
        osw.write(cbuf, 0, len);
    }

    osw.close();
    isr.close();
}

12.6 标准输入、输出流

  • System.inSystem.out 分别代表了系统标准的输入和输出设备

  • 默认输入设备是:键盘,输出设备是:显示器

  • System.in 的类型是 InputStream

  • System.out 的类型是 PrintStream,是 OutputStream的子类、FilterOutputStream 的子类

  • 重定向:通过 System 类的 setIn,setOut 方法对默认设备进行改变

  • public static void setIn(InputStream in)

  • public static void setOut(PrintStream out)

public static void main(String[] args) {
    //方法二:使用 System.in 实现
    InputStreamReader isr = new InputStreamReader(System.in);
    BufferedReader br = new BufferedReader(isr);

    while (true) {
        String data = null;
        try {
            System.out.println("请输入字符串:");
            data = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
            break;
        }
        assert data != null;
        System.out.println(data.toUpperCase());
    }

    try {
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

12.7 打印流

  • 实现将基本数据类型的数据格式转化为字符串输出
  • 打印流:PrintStreamPrintWriter
    • 提供了一系列重载的 print()println() 方法,用于多种数据类型的输出
    • PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常
    • PrintStream 和 PrintWriter 有自动 flush 功能
    • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类
	@Test
    public void test2() {
        PrintStream ps = null;
        try {
            FileOutputStream fos = new FileOutputStream(new File("D:\\word\\text.txt"));
            // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
            ps = new PrintStream(fos, true);
            if (ps != null) {// 把标准输出流(控制台输出)改成文件
                System.setOut(ps);
            }
            for (int i = 0; i <= 255; i++) { // 输出ASCII字符
                System.out.print((char) i);
                if (i % 50 == 0) { // 每50个数据一行
                    System.out.println(); // 换行
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }
    }

12.8 数据流

  • DataInputStream 和 DataOutputStream,分别“套接”在 InputStream 和 OutputStream 子类的流上

  • 作用:用于读取或写出基本数据类型的变量或字符串

@Test
public void test3() throws Exception{
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("test.txt"));

    dos.writeUTF("Java");
    dos.flush();//刷新操作,将内存中的数据写入文件
    dos.writeInt(23);
    dos.flush();
    dos.writeBoolean(false);
    dos.flush();

    dos.close();
}

/*
将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致
 */
@Test
public void test4() throws IOException {
    DataInputStream dis = new DataInputStream(new FileInputStream("test.txt"));

    String s1 = dis.readUTF();
    System.out.println(s1);
    int i = dis.readInt();
    System.out.println(i);
    boolean s = dis.readBoolean();
    System.out.println(s);

    dis.close();
}

12.9 对象流

  • ObjectInputStream 和 OjbectOutputSteam

  • 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来

  • 序列化:ObjectOutputStream保存内存中的对象 -> 存储中的的文件、网络传输出去

  • 反序列化:ObjectInputStream读取存储中的文件、通过网络接收过来 -> 内存中的对象

  • ObjectOutputStreamObjectInputStream 不能序列化 statictransient 修饰的成员变量

12.9.1 对象的序列化

  • 对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象

  • 凡是实现 Serializable 接口的类都有一个表示序列化版本标识符的静态变量:

    • private static final long serialVersionUID
    • serialVersionUID 用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
    • 如果类没有显示定义这个静态常量,它的值是 Java 运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改serialVersionUID 可能发生变化。故建议,显式声明
  • 简单来说,Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常 (InvalidCastException)

谈谈对 java.io.Serializable 接口的理解:

Serializable 接口用于序列化,是一个空方法的接口,可以看作是一个标识,实现了 Serializable 接口的对象,可以将它们装换成一系列字节,并可以恢复原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在 Windows 机器上创建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节

12.9.2 序列化

  • 若某个类实现了 Serializable 接口,该类的对象就是可序列化的
    • 创建一个 ObjectOutputStream
    • 调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
    • 注意写出一次,操作 flush() 一次
  • 如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field 的类也不能序列化
/*
Person 需要满足如下的要求,方可序列化
1.需要实现接口:Serializable
2.当前类提供一个全局常量:serialVersionUID
3.除了当前 Person 类需要实现 Serializable 之外,还必须保证其内部所有属性也
 必须是可序列化的(默认情况下,基本数据类型可序列化)

补充:ObjectOutputStream 和 ObjectInputStream 
	不能序列化 static 和 transient 修饰的成员变量
 */
public class Person implements Serializable {

    private static final long serialVersionUID = -6987513259817358L;

    private static String name;
    private transient int age;
    private int id;
    private Account account;

    public Person() {
    }

    public Person(String name, int age, int id, Account account) {
        this.name = name;
        this.age = age;
        this.id = id;
        this.account = account;
    }

    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 int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

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

class Account implements Serializable {
    private static final long serialVersionUID = -526528799939956L;

    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public Account() {
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }
}
	/*
    序列化的过程:将内存中的 Java 对象保存到磁盘中或通过网络传输出去
    使用 ObjectOutputStream 实现
     */
    @Test
    public void testObjectOutputStream() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("data.dat"));

            oos.writeObject(new String("Java语言"));
            oos.flush();//刷新操作

            oos.writeObject(new Person("张三", 23, 1, new Account(4500)));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                assert oos != null;
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.9.3 反序列化

    /*
    反序列化:将磁盘文件中的对象还原为内存中的一个 Java 对象
    使用 ObjectInputStream 来实现
     */
    @Test
    public void testObjectInputStream() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("data.dat"));

            String str = (String) ois.readObject();
            Person p = (Person) ois.readObject();

            System.out.println(str);
            System.out.println(p);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                assert ois != null;
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.10 随机存储文件流

  • RandomAccessFile 声明在 java.io 包下,但直接继承于 java.lang.Object 类。并且它实现了 DataInput、DataOutput 这两个接口,也就意味着这个类既可以读可以写

  • RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件

    • 支持只访问文件的部分内容
    • 可以向已存在的文件后追加内容
  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置

  • RandomAccessFile 类对象可以自由移动记录指针:

    • long getFilePointer():获取文件记录指针的当前位置
    • void seek(long pos):将文件记录指针定位到 pos 位置
  • 构造器:

    • public RandomAccessFile(File file, String mode)
    • public RandomAccessFile(String name, String mode)
  • 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式

    • r:以只读方式打开
    • rw:打开以便读取和写入
    • rwd:打开以便读取和写入;同步文件内容的更新
    • rws:打开以便读取和写入;同步文件内容和元数据的更新
  • 如果模式为只读 r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为 rw 读写。如果文件不存在则会去创建文件,如果存在则不会创建

	@Test
    public void test1() {
        RandomAccessFile raf2 = null;
        try {
            RandomAccessFile raf1 = new RandomAccessFile(new File("../data/photo/u029.png"), "r");
            raf2 = new RandomAccessFile(new File("../data/photo/u029_1.png"), "rw");

            byte[] buffer = new byte[1024];
            int len;

            while ((len = raf1.read(buffer)) != -1) {
                raf2.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                assert raf2 != null;
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /*
    通过指针定位,在指定的位置追加内容且保留后面已存在的内容
    */
    @Test
    public void test2() throws IOException{

        RandomAccessFile raf1 = new RandomAccessFile(new File("hello_1.txt"), "rw");

        raf1.seek(3);//将指针调到角标为3的位置

        StringBuilder builder = new StringBuilder((int)new File("hello_1").length());
        byte[] buffer = new byte[20];
        int len;
        while ((len = raf1.read(buffer)) != -1) {
            builder.append(new String(buffer, 0, len));
        }
        //调回指针
        raf1.seek(3);
        raf1.write("zzz".getBytes());

        //将StringBuilder中的数据写入到文件中
        raf1.write(builder.toString().getBytes());
        raf1.close();
    }

12.11 NIO.2 中 Path、Paths、Files 类的使用

12.11.1 Path、Paths 类的使用

public class PathTest {

    //如何使用Paths实例化Path
    @Test
    public void test1() {
        Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)

        Path path2 = Paths.get("d:\\", "nio\\hello.txt");//new File(String parent,String filename);

        System.out.println(path1);
        System.out.println(path2);

        Path path3 = Paths.get("d:\\", "nio");
        System.out.println(path3);
    }

    //Path中的常用方法
    @Test
    public void test2() {
        Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
        Path path2 = Paths.get("hello.txt");

//		String toString() : 返回调用 Path 对象的字符串表示形式
        System.out.println(path1);

//		boolean startsWith(String path) : 判断是否以 path 路径开始
        System.out.println(path1.startsWith("d:\\nio"));
//		boolean endsWith(String path) : 判断是否以 path 路径结束
        System.out.println(path1.endsWith("hello.txt"));
//		boolean isAbsolute() : 判断是否是绝对路径
        System.out.println(path1.isAbsolute() + "~");
        System.out.println(path2.isAbsolute() + "~");
//		Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
        System.out.println(path1.getParent());
        System.out.println(path2.getParent());
//		Path getRoot() :返回调用 Path 对象的根路径
        System.out.println(path1.getRoot());
        System.out.println(path2.getRoot());
//		Path getFileName() : 返回与调用 Path 对象关联的文件名
        System.out.println(path1.getFileName() + "~");
        System.out.println(path2.getFileName() + "~");
//		int getNameCount() : 返回Path 根目录后面元素的数量
//		Path getName(int idx) : 返回指定索引位置 idx 的路径名称
        for (int i = 0; i < path1.getNameCount(); i++) {
            System.out.println(path1.getName(i) + "*****");
        }

//		Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
        System.out.println(path1.toAbsolutePath());
        System.out.println(path2.toAbsolutePath());
//		Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
        Path path3 = Paths.get("d:\\", "nio");
        Path path4 = Paths.get("nioo\\hi.txt");
        path3 = path3.resolve(path4);
        System.out.println(path3);

//		File toFile(): 将Path转化为File类的对象
        File file = path1.toFile();//Path--->File的转换

        Path newPath = file.toPath();//File--->Path的转换

    }
}

12.11.2 Files 类的使用

public class FilesTest {

   @Test
   public void test1() throws IOException{
      Path path1 = Paths.get("d:\\nio", "hello.txt");
      Path path2 = Paths.get("atguigu.txt");
      
//    Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
      //要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
//    Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
      
//    Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
      //要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
      Path path3 = Paths.get("d:\\nio\\nio1");
//    Files.createDirectory(path3);
      
//    Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
      //要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
      Path path4 = Paths.get("d:\\nio\\hi.txt");
//    Files.createFile(path4);
      
//    void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
//    Files.delete(path4);
      
//    void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
      Files.deleteIfExists(path3);
      
//    Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
      //要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
//    Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
      
//    long size(Path path) : 返回 path 指定文件的大小
      long size = Files.size(path2);
      System.out.println(size);

   }

   @Test
   public void test2() throws IOException{
      Path path1 = Paths.get("d:\\nio", "hello.txt");
      Path path2 = Paths.get("atguigu.txt");
//    boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
      System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));

//    boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
      //不要求此path对应的物理文件存在。
      System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));

//    boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

//    boolean isHidden(Path path) : 判断是否是隐藏文件
      //要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
//    System.out.println(Files.isHidden(path1));

//    boolean isReadable(Path path) : 判断文件是否可读
      System.out.println(Files.isReadable(path1));
//    boolean isWritable(Path path) : 判断文件是否可写
      System.out.println(Files.isWritable(path1));
//    boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
      System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
   }

   /**
    * StandardOpenOption.READ:表示对应的Channel是可读的。
    * StandardOpenOption.WRITE:表示对应的Channel是可写的。
    * StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
    * StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
    *
    * @author shkstart 邮箱:shkstart@126.com
    * @throws IOException
    */
   @Test
   public void test3() throws IOException{
      Path path1 = Paths.get("d:\\nio", "hello.txt");

//    InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
      InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

//    OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
      OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);


//    SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
      SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

//    DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
      Path path2 = Paths.get("e:\\teach");
      DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
      Iterator<Path> iterator = directoryStream.iterator();
      while(iterator.hasNext()){
         System.out.println(iterator.next());
      }


   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值