JavaSE 进阶 - 第23章 IO流

1、IO流,什么是IO?

  I : Input
  O : Output
  通过IO可以完成硬盘文件的读和写

在这里插入图片描述

  

2、IO流的分类

2.1 按照流的方向进行分类:
  以内存作为参照物,
  (1)往内存中去,叫做输入(Input)。或者叫做读(Read)。
  (2)从内存中出来,叫做输出(Output)。或者叫做写(Write)。

2.2 按照读取数据方式不同进行分类:
  (1)有的流是按照 【字节】 的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。
    这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等…
      假设文件file1.txt,采用字节流的话是这样读的:
      a中国bc张三fe
      第一次读:一个字节,正好读到’a’
      第二次读:一个字节,正好读到’中’字符的一半。
      第三次读:一个字节,正好读到’中’字符的另外一半。
      …

  (2)有的流是按照 【字符】 的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,
    这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
      假设文件file1.txt,采用字符流的话是这样读的:
      a中国bc张三fe
      第一次读:'a’字符('a’字符在windows系统中占用1个字节。)
      第二次读:'中’字符('中’字符在windows系统中占用2个字节。)
      …

综上所述,流的分类:
   输入流、输出流
   字节流、字符流
  

3、流应该怎样学习?

  Java中的IO流都已经写好了,我们程序员不需要关心,我们最主要还是掌握:
  在java中已经提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些

  java中所有的流都是在:java.io.* 下。

  java中主要还是研究:
    怎么new流对象
    调用流对象的哪个方法是读,哪个方法是写

  

4、java IO流的四大家族

4.1、四大家族的首领:
  java.io.InputStream 字节输入流
  java.io.OutputStream 字节输出流

  java.io.Reader 字符输入流
  java.io.Writer 字符输出流

注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。

4.2、四大家族的首领都是抽象类。(类名以public abstract class 修饰)

4.3、所有的【流】都实现了:
  java.io.Closeable接口,都是可关闭的,都有close()方法
  流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。
  ——————>养成好习惯,用完流一定要关闭

4.4、所有的【输出流】都实现了:
  java.io.Flushable接口,都是可刷新的,都有flush()方法
  这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)。
  刷新的作用就是清空管道。
  注意:如果没有flush()可能会导致丢失数据。
  ——————>养成好习惯,输出流在最终输出之后,一定要记得flush()刷新一下
  

5、java.io包下需要掌握的16个流

文件专属:
	java.io.FileInputStream(重点掌握)
	java.io.FileOutputStream(重点掌握)
	java.io.FileReader
	java.io.FileWriter

转换流:(将字节流转换成字符流)
	java.io.InputStreamReader
	java.io.OutputStreamWriter

缓冲流专属:
	java.io.BufferedReader
	java.io.BufferedWriter
	java.io.BufferedInputStream
	java.io.BufferedOutputStream

数据流专属:
	java.io.DataInputStream
	java.io.DataOutputStream

标准输出流:(看好欧,只是输出!)
	java.io.PrintWriter
	java.io.PrintStream(重点掌握)

对象专属流:
	java.io.ObjectInputStream(重点掌握)
	java.io.ObjectOutputStream(重点掌握)

重点掌握FileInputStream和FileOutputStream,其他的就懂了

  

5.1 FileInputStream的使用(重点)

  1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
  2、字节的方式,完成输入的操作,完成读的操作(硬盘—> 内存)

  (1)int read() 读1个字节

	---------------------------------------------------------------
	FileInputStream fis = new FileInputStream("C:/Users/Administrator/Desktop/temp"); //绝对路径
          // 开始读
	int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身。
	System.out.println(readData); //97 (也就是temp文件里面我们放的的a)
	。。。。。。
	。。。。。。
	// 已经读到文件的末尾了,在读的时候读取不到任何数据,返回-1.
	readData = fis.read();
	System.out.println(readData);  // -1
	---------------------------------------------------------------

  (2)int read(byte[] b)

	往byte[]数组当中读,一次读多个字节。 减少硬盘和内存的交互,提高程序的执行效率。
	
	事先创建好一定长度byte数组,
	调用这个方法往byte数组中读,
        这个方法返回值是:读取到的字节数量(不是字节本身), (1个字节都没有读取到时返回-1)
	然后把byte数组转换成字符串String,读到多少个转换多少个。
	
	---------------------------------------------------------------
	FileInputStream fis = new FileInputStream("chapter23/tempfile2");//相对路径
	//相对路径的话呢?相对路径一定是从当前所在的位置作为起点开始找!
	//IDEA默认的当前路径是哪里?工程Project的根就是IDEA的默认当前路径。(E:\IdeaProjects\javase)

	byte[] bytes = new byte[4];

	int readCount = fis.read(bytes);
	System.out.println(readCount);          // 4
	
	//将字节数组转换成字符串
	//读取了多少个字节,转换多少个,输出
	System.out.println(new String(bytes,0, readCount));  // abcd
	---------------------------------------------------------------

  【示例】:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest04 {
    public static void main(String[] args) {
		 //要想在finally里面关闭fis,那么fis不能定义在try{}里面,不然是局部变量,出了try的{}就不起作用了s
        FileInputStream fis = null; 
        try {
            fis = new FileInputStream("chapter23/tempfile2");
            // 准备一个byte数组
            byte[] bytes = new byte[4];
            
			/*while(true){
                int readCount = fis.read(bytes);
                if(readCount == -1){
                    break;
                }
                // 把byte数组转换成字符串,读到多少个转换多少个。
                System.out.print(new String(bytes, 0, readCount));
            }*/

            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1) { // 1个字节都没有读取到时返回-1
                System.out.print(new String(bytes, 0, readCount));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在finally语句块当中确保流一定关闭。
            if (fis != null) { 
                // 关闭流的前提是:流不是空。流是null的时候没必要关闭。
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  (3)int available():返回流当中剩余的没有读到的字节数量

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/*
FileInputStream类的其它常用方法:
    int available():返回流当中剩余的没有读到的字节数量
 */
public class FileInputStreamTest05 {
    public static void main(String[] args) {
		 //要想在finally里面关闭fis,那么fis不能定义在try{}里面,不然是局部变量,出了try的{}就不起作用了
        FileInputStream fis = null;
        try {
            //tempfile2文件里面的内容是abcdef
            fis = new FileInputStream("chapter23/tempfile2");
            System.out.println("总字节数量:" + fis.available());
            // 读1个字节
            //int readByte = fis.read();
            // 还剩下可以读的字节数量是:5
            //System.out.println("剩下多少个字节没有读:" + fis.available());

            // 这个方法有什么用?
            byte[] bytes = new byte[fis.available()]; // 这种方式不太适合太大的文件,因为byte[]数组不能太大。
            // 不需要循环了。
            // 直接读一次就行了。
            int readCount = fis.read(bytes); // 6
            //把byte数组转换成String输出
            System.out.println(new String(bytes)); // abcdef

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

  (4)long skip(long n):跳过几个字节不读。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/*
FileInputStream类的其它常用方法:
   long skip(long n):跳过几个字节不读。
 */
public class FileInputStreamTest06 {
    public static void main(String[] args) {
		 //要想在finally里面关闭fis,那么fis不能定义在try{}里面,不然是局部变量,出了try的{}就不起作用了
        FileInputStream fis = null;
        try {
            //tempfile2文件里面的内容是abcdef
            fis = new FileInputStream("chapter23/tempfile2");

            // skip跳过几个字节不读取,这个方法也可能以后会用!
            fis.skip(3);
            System.out.println(fis.read()); //100 (也就是字母d,跳过了abc)

            // 剩下多少个字节没有读:2  (因为还有剩下的ef没有读)
            System.out.println("剩下多少个字节没有读:" + fis.available());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.2 FileOutputStream的使用(重点)

  文件字节输出流,负责写。从内存——>硬盘。
  fos.write(bytes);

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 文件字节输出流,负责写。
 * 从内存到硬盘。
 */
public class FileOutputStreamTest01 {
    public static void main(String[] args) {
        FileOutputStream fos =null;
        try {
            // myfile文件不存在的时候会自动新建!
            // 这种方式谨慎使用,这种方式会先将原文件清空,然后重新写入。
                   //fos = new FileOutputStream("chapter23/myfile");
            // 以追加的方式在文件末尾写入。不会清空原文件内容。
            //第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处
            fos = new FileOutputStream("chapter23/tempfile2", true);//这里的tempfile2文件是已经存在的,里面是abcdef

            // 开始写。
            byte[] bytes = {65, 66, 67, 68};
            // 将byte数组全部写到tempfile2文件里面去!
            fos.write(bytes); // 写进去ABCD
            // 将byte数组的一部分写!
            fos.write(bytes, 0, 2); // 再写出AB

            // 字符串
            String s = "我是一个中国人,我骄傲!!!";
            // 将字符串转换成byte数组。
            byte[] bs = s.getBytes();
            // 写
            fos.write(bs);

            // 写完之后,最后一定要刷新
            fos.flush();

            //这个程序运行完毕之后,不会有任何反应,但是去tempfile2文件里面查看,就可以看到
            //在后面添加了 : ABCDAB我是一个中国人,我骄傲!!!
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos !=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

  

5.3 文件复制1(字节流型)

  使用FileInputStream + FileOutputStream完成文件的拷贝。
  拷贝的过程应该是一边读,一边写。
  使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。
002-文件的复制原理

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
使用FileInputStream + FileOutputStream完成文件的拷贝。
 */
public class Copy01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //创建一个输入流对象
            fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\唯美图片.jpeg");
            //创建一个输出流对象
            fos = new FileOutputStream("F:\\唯美图片.jpeg");

            // 最核心的:一边读,一边写
            byte[] bytes = new byte[1024 * 1024]; //1MB (一次最多拷贝1MB。)1KB=1024字节。1MB=1024KB=1024*1024字节。
            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, readCount);
            }

            //刷新,输出流最好要刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 分开try,不要一起try。
            // 一起try的时候,其中一个出现异常,可能会影响到另一个流的关闭。
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

  

5.4 FileReader

  文件字符输入流,只能读取普通文本。读取文本内容时,比较方便,快捷。
  reader.read(chars) 读到char数组里面

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/*
FileReader:
    文件字符输入流,只能读取普通文本。
    读取文本内容时,比较方便,快捷。
 */
public class FileReaderTest {
    public static void main(String[] args) {
        FileReader reader = null ;
        try {
            // 创建文件字符输入流
            reader = new FileReader("chapter23/tempfile2");
            
            /*//准备一个char数组
            char[] chars = new char[4];
            // 往char数组中读
            reader.read(chars); // 只读一次
            for(char c : chars) {
                System.out.println(c);  //a b c d
            }*/

            // 开始读
            char[] chars = new char[4]; // 一次读取4个字符
            int readCount = 0;
            while((readCount = reader.read(chars)) != -1) {
				// 把char数组转换成字符串,读到多少个转换多少个。
                //循环读,全部输出:abcdefABCDAB我是一个中国人,我骄傲!!!
                System.out.print(new String(chars,0,readCount));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.5 FileWriter

  文件字符输出流。写。只能输出普通文本
  out.write(chars); //写一个数组的内容进去
  out.write(“hello world!”); //写一个字符串也可以

import java.io.FileWriter;
import java.io.IOException;

/*
FileWriter:
    文件字符输出流。写。
    只能输出普通文本。
 */
public class FileWriterTest {
    public static void main(String[] args) {
        FileWriter out = null;
        try {
            // 创建文件字符输出流对象
            //out = new FileWriter("chapter23/file");
            out = new FileWriter("chapter23/file", true);  //追加的形式

            // 开始写。
            char[] chars = {'我','是','中','国','人'};
            out.write(chars);                    //写进去了 我是中国人
            out.write(chars, 2, 3);    //又写进去了 中国人

            //FileWriter可以直接写字符串进去,不用像FileOutputStream要转换
            out.write("我是一名java软件工程师!");
            // 写出一个换行符。
            out.write("\n"); 
            out.write("hello world!");

            // 刷新
            out.flush();
            /*
			这个程序运行完毕之后,不会有任何反应,但是去file文件里面查看,就可以看到
            在后面添加了 : 我是中国人中国人我是一名java软件工程师!
							 hello world!
			*/
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.6 文件复制2(字符流型)

   使用FileReader+FileWriter进行拷贝的话,只能拷贝“普通文本”文件。
   注:记事本写的都是“普通文本”文件 .java文件也是普通文本文件,与文件后缀没关系,不一定非要是.txt

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/*
使用FileReader FileWriter进行拷贝的话,只能拷贝“普通文本”文件。
 */
public class Copy02 {
    public static void main(String[] args) {
        FileReader in = null;
        FileWriter out = null;
        try {
            in = new FileReader("chapter23/file");  // 读
            out = new FileWriter("file");// 写

            // 一边读一边写:
            char[] chars = new char[1024 * 512]; // 1MB
            // --1个char占2个字节(2个byte)--1MB=1024KB=1024*1024字节=1024*512*2字节=1024*512个char
            int readCount = 0;
            while( (readCount = in.read(chars)) != -1){
                out.write(chars,0,readCount);
            }

            // 刷新
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.7 带有缓冲区的字符输入流BufferedReader

   带有缓冲区的字符输入流
   使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。
   BufferedReader(Reader in) 这个构造方法只能传一个字符流(不像上面的,是传一个字符串)

   当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:【节点流】
   外部负责包装的这个流,叫做:【包装流】或者【处理流】
   注:关闭流,对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。

   br.readLine() 方法读取一个文本行

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/*
BufferedReader:
    带有缓冲区的字符输入流。
    使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。
 */
public class BufferedReaderTest01 {
    public static void main(String[] args){

        FileReader reader = null;
        BufferedReader br = null;
        try {
            reader = new FileReader("chapter23/file");
            // 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
            // 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
            // 像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
            br = new BufferedReader(reader);

            /*String firstLine = br.readLine(); // 读一行
            System.out.println(firstLine);
            String secondLine = br.readLine(); // 读二行
            System.out.println(secondLine);
            String line3 = br.readLine(); // 读三行
            System.out.println(line3);*/

            // br.readLine()方法读取一个文本行,但不带换行符。
            String s = null;
            while((s = br.readLine()) != null){
                System.out.println(s);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
                // 关闭流
                // 对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
            if (br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.8 转换流:InputStreamReader、OutputStreamWriter
import java.io.*;

/*
    转换流:InputStreamReader
 */
public class BufferedReaderTest02 {
    public static void main(String[] args){

        BufferedReader br = null;
        try {
            /*// 字节流
		    FileInputStream in = new FileInputStream("chapter23/file);

		    // 通过转换流转换(InputStreamReader将字节流in转换成字符流reader。)
		    // 同时这里,in是节点流。reader是包装流。
		    InputStreamReader reader = new InputStreamReader(in);

            // 这个构造方法只能传一个字符流。不能传字节流。
            // 这里,reader是节点流。br是包装流。
            BufferedReader br = new BufferedReader(reader);*/

            // 合并
            br = new BufferedReader(new InputStreamReader(new FileInputStream("chapter23/file")));

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

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭最外层
            if (br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.9 带有缓冲区的字符输出流 BufferedWriter

  构造方法: BufferedWriter(Writer out)

import java.io.*;

/*
BufferedWriter:带有缓冲的字符输出流。
OutputStreamWriter:转换流
 */
public class BufferedWriterTest {
    public static void main(String[] args){
        // 带有缓冲区的字符输出流
        BufferedWriter out = null;
        try {
            //直接传字符流
            //out = new BufferedWriter(new FileWriter("chapter23/file"));

            //或者传 转换成字符流的字节流
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("chapter23/file", true)));

            // 开始写。
            out.write("hello world!");
            out.write("\n");
            out.write("hello kitty!");

            // 刷新
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭最外层
            if (out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  

5.10 数据流 专属 (了解)

(1)DataOutputStream:数据字节输出流
  这个流可以将数据连同数据的类型一并写入文件。
  注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)

  构造方法:DataOutputStream(OutputStream out)

import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
java.io.DataOutputStream:数据专属的流。
这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)
 */
public class DataOutputStreamTest {
    public static void main(String[] args) {
        // 创建数据专属的字节输出流
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("chapter23/data"));
            // 写数据
            byte b = 100;
            short s = 200;
            int i = 300;
            long l = 400L;
            float f = 3.0F;
            double d = 3.14;
            boolean sex = false;
            char c = 'a';
            // 写
            dos.writeByte(b); // 把数据以及数据的类型一并写入到文件当中。
            dos.writeShort(s);
            dos.writeInt(i);
            dos.writeLong(l);
            dos.writeFloat(f);
            dos.writeDouble(d);
            dos.writeBoolean(sex);
            dos.writeChar(c);
            // 刷新
            dos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭最外层
            if (dos != null){
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

(2)DataInputStream: 数据字节输入流
  DataOutputStream写的文件,只能使用DataInputStream去读。
  并且读的时候你需要提前知道写入的顺序。读的顺序需要和写的顺序一致。才可以正常取出数据。

  构造方法:DataInputStream(InputStream in)

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/*
DataInputStream:数据字节输入流。
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。
 */
public class DataInputStreamTest01 {
    public static void main(String[] args) {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream("chapter23/data"));
            // 开始读
            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 sex = dis.readBoolean();
            char c = dis.readChar();

            System.out.println(b);
            System.out.println(s);
            System.out.println(i + 1000);
            System.out.println(l);
            System.out.println(f);
            System.out.println(d);
            System.out.println(sex);
            System.out.println(c);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭最外层
            if (dis != null){
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
/*
100
200
1300
400
3.0
3.14
false
a
 */

  

5.11 标准输出流(重点)

  1、java.io.PrintWriter
  2、java.io.PrintStream(掌握这个就可以了)

import java.io.FileOutputStream;
import java.io.PrintStream;

/*
java.io.PrintStream:标准的字节输出流。默认输出到控制台。
 */
public class PrintStreamTest {
    public static void main(String[] args) throws Exception{
        //因为这已经是main方法了,异常不应该抛给main方法,应该try catch,这里只是为了便于写下面代码,偷懒了。。。

        // 联合起来写
        System.out.println("hello world!");

        // 分开写
        PrintStream ps = System.out;
        ps.println("hello zhangsan");
        ps.println("hello lisi");
        ps.println("hello wangwu");

        // 标准输出流不需要手动close()关闭。
        // 可以改变标准输出流的输出方向吗? 可以
        /*
        // 这些是之前System类使用过的方法和属性。
        System.gc();
        System.currentTimeMillis();
        PrintStream ps2 = System.out;
        System.exit(0);
        System.arraycopy(....);
         */

        // 标准输出流不再指向控制台,指向“log”文件。
        PrintStream printStream = new PrintStream(new FileOutputStream("chapter23/log"));
        // 修改输出方向,将输出方向修改到"log"文件。
        System.setOut(printStream);
        // 再输出
        System.out.println("hello world");
        System.out.println("hello kitty");
        System.out.println("hello zhangsan");
        //这三个输出,输出在chapter23/log文件里面了

    }
}

  【拓展】:记录日志的方法。【Logger.java】+【LogTest.java】

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/*
日志工具
 */
public class Logger {
    /*
    记录日志的方法。
     */
    public static void log(String msg) {
        try {
            // 指向一个日志文件
            PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
            // 改变输出方向
            System.setOut(out);
            // 日期当前时间
            Date nowTime = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime = sdf.format(nowTime);

            System.out.println(strTime + ": " + msg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
public class LogTest {
    public static void main(String[] args) {
        //测试工具类是否好用
        Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
        Logger.log("调用了UserService的doSome()方法");
        Logger.log("用户尝试进行登录,验证失败");
        Logger.log("我非常喜欢这个记录日志的工具哦!");
    }
}
/*运行后,输出在log.txt文件里面的内容:
2020-07-30 22:56:46 356: 调用了System类的gc()方法,建议启动垃圾回收
2020-07-30 22:56:46 423: 调用了UserService的doSome()方法
2020-07-30 22:56:46 426: 用户尝试进行登录,验证失败
2020-07-30 22:56:46 427: 我非常喜欢这个记录日志的工具哦!
 */

  

5.12 对象专属流(重点)

   ObjectInputStream
   ObjectOutputStream

   重点:
      参与序列化的类型的类必须实现java.io.Serializable接口
      并且建议在该类里面将序列化版本号手动的写出来。
      private static final long serialVersionUID = 1L;

5.12.1 序列化和反序列化的理解
在这里插入图片描述

5.12.2 序列化和反序列化的实现:

		(1)参与序列化和反序列化的对象类,必须实现Serializable接口。
		     否者会出现异常java.io.NotSerializableException: 该对象不是可序列化的

		(2)通过源代码发现,Serializable接口只是一个标志接口:
		    public interface Serializable {
		    }
		    这个接口当中什么代码都没有。
		    那么它起到一个什么作用呢?
			起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
			Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成
			一个序列化版本号。

		【Student.java】+【ObjectOutputStreamTest01.java】+【ObjectInputStreamTest01.java】
import java.io.Serializable;

public class Student implements Serializable {

    // IDEA工具自动生成序列化版本号。
    //private static final long serialVersionUID = -7998917368642754840L;

    // Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号。
    // 这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。
    // 建议将序列化版本号手动的写出来。不建议自动生成
    private static final long serialVersionUID = 1L; // java虚拟机识别一个类的时候先通过类名,如果类名一致,再通过序列化版本号。

    private int no;
    //private String name;

    // 过了很久,Student这个类源代码改动了。
    // 源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。
    // 并且class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变。
    private int age;
    private String email;
    private String address;

    public Student() {
    }

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

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    /*public String getName() {
        return name;
    }*/

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

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

    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

/*
序列化
 */
public class ObjectOutputStreamTest01 {
    public static void main(String[] args) throws Exception{  //异常先放到这里,没有try catch,便于以下代码的书写
        // 创建java对象
        Student s = new Student(1111, "zhangsan");
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chapter23/students"));

        // 序列化对象
        oos.writeObject(s);

        // 刷新
        oos.flush();
        // 关闭
        oos.close();
    }
}
import java.io.FileInputStream;
import java.io.ObjectInputStream;

/*
反序列化
 */
public class ObjectInputStreamTest01 {
    public static void main(String[] args) throws Exception{//异常先放到这里,没有try catch,便于以下代码的书写
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("chapter23/students"));
        // 开始反序列化,读
        Object obj = ois.readObject();
        // 反序列化回来是一个学生对象,所以会调用学生对象的toString方法。
        System.out.println(obj);
        ois.close();
    }
}
/*
Student{no=1111, age=0, email='null', address='null'}
 */

5.12.3 序列化版本号有什么用呢?

		之前说到:Java虚拟机在Student类看到Serializable接口之后,会自动生成一个序列化版本号。

		【需求变化】:过了很久,Student这个类源代码改动了。
			这个时候再去反序列化(运行【ObjectInputStreamTest01.java】)的时候,结果报错了:
				报错:java.io.InvalidClassException:     ------无效的类异常
					com.bjpowernode.java.bean.Student;
					local class incompatible:
					    stream classdesc serialVersionUID = -684255398724514298(十年后),
					    local class serialVersionUID = -3463447116624555755(十年前)
				
				说明序列化版本号在Student类修改后和修改前不一样了,序列化版本号不一致,就没有办法再反序列化了
				
			源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。class文件再次运行的时候,
			java虚拟机生成的序列化版本号也会发生相应的改变。

			这个时候再去反序列化(运行【ObjectInputStreamTest01.java】)的时候,结果就会报错
			除非重新序列化之后(运行【ObjectOutputStreamTest01.java】),
			再去反序列化(运行【ObjectInputStreamTest01.java】),才不会报错
			要么就自己手动提供一个固定不变的序列化版本号,不管怎么修改,再去反序列化都不会报这个错误了。


			java语言中是采用什么机制来区分类的?
				第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
				第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

			小鹏编写了一个类:com.yuming.java.bean.Student implements Serializable
			胡浪编写了一个类:com.yuming.java.bean.Student implements Serializable
			不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。
			对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,
			都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)

			这种自动生成序列化版本号有什么缺陷?
				这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,
				因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机
				会认为这是一个全新的类。(这样就不好了!)

  最终结论:
    凡是一个类实现了Serializable接口,建议在该类中手动提供一个固定不变的序列化版本号。
    这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

    private static final long serialVersionUID = 1L;

    注:IDEA工具可以自动生成序列化版本号。
    (需手动设置后,再类名那里alt+回车,点击“Add 'serialVersionUID’field”)

  
5.12.4 序列化多个对象:

		【User.java】+【ObjectOutputStreamTest02.java】+【ObjectInputStreamTest02.java】
		
		(1)一次序列化多个对象呢?
			可以,可以将对象放到集合当中,序列化集合。
		
		(2)参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
		
		(3)在User类中,有两个属性:no、name,如果希望序列化的时候,name不参与序列化操作怎么办?
			答:使用transient关键字

			private int no;
			// transient关键字表示游离的,不参与序列化。
			private transient String name; // name不参与序列化操作!
import java.io.Serializable;

public class User implements Serializable {
    private int no;
    // transient关键字表示游离的,不参与序列化。
    private transient String name; // name不参与序列化操作!

    public User() {
    }

    public User(int no, String name) {
        this.no = no;
        this.name = name;
    }

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

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/*
一次序列化多个对象呢?
    可以,可以将对象放到集合当中,序列化集合。
提示:
    参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
 */
public class ObjectOutputStreamTest02 {
    public static void main(String[] args) throws Exception{//异常先放到这里,没有try catch,便于以下代码的书写
        List<User> userList = new ArrayList<>();
        userList.add(new User(1,"zhangsan"));
        userList.add(new User(2, "lisi"));
        userList.add(new User(3, "wangwu"));
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chapter23/users"));

        // 序列化一个集合,这个集合对象中放了很多其他对象。
        oos.writeObject(userList);

        oos.flush();
        oos.close();
    }
}	
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
/*
反序列化集合
 */
public class ObjectInputStreamTest02 {
    public static void main(String[] args) throws Exception{//异常先放到这里,没有try catch,便于以下代码的书写
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("chapter23/users"));
        //Object obj = ois.readObject();
        //System.out.println(obj instanceof List);
        List<User> userList = (List<User>)ois.readObject();
        for(User user : userList){
            System.out.println(user);
        }
        ois.close();
    }
}
/*
User{no=1, name='null'}
User{no=2, name='null'}
User{no=3, name='null'}
*/

  

6、java.io.File类的常用方法

1、File类和四大家族没有关系,所以File类不能完成文件的读和写。
2、File对象代表什么?
	文件和目录路径名的抽象表示形式。
	C:\Drivers				这是一个File对象
	C:\Drivers\Lan\Realtek\Readme.txt	也是File对象。
	一个File对象有可能对应的是目录,也可能是文件。
	File只是一个路径名的抽象表示形式。

3、需要掌握File类中常用的方法

【FileTest01.java】

	new File(String pathname) 构造方法 ,创建一个File对象
	
	boolean exists()  判断是否存在该文件或目录

	boolean createNewFile() 如果pathname不存在,以文件的形式创建出来
	
	boolean mkdir()  如果pathname不存在,以目录的形式创建出来
	
	boolean mkdirs()  以多重目录的形式新建,包括所有必需但不存在的父目录。 

	String getParent()	获取文件的父路径

	File getParentFile() 返回此抽象路径名父目录的抽象路径名 

	String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串

【FileTest02.java】
	String getName()  获取此抽象路径名表示的文件或目录的名称 

	boolean isDirectory()  判断是否是一个目录

	boolean isFile() 判断是否是一个文件

	long lastModified() 获取文件最后一次修改时间 

	long length()  获取文件的长度(大小),单位:字节

【FileTest03.java】

	File[] listFiles() 获取当前目录下所有的子文件。
import java.io.File;
/*
File类的常用方法
 */
public class FileTest01 {
    public static void main(String[] args) throws Exception { //异常先放到这里,没有try catch,便于以下代码的书写
        // 创建一个File对象
        File f1 = new File("F:\\file");

        // 判断是否存在!
        System.out.println(f1.exists());  //false

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

        // 如果F:\file不存在,则以目录的形式创建出来
        /*if(!f1.exists()) {
            // 以目录的形式新建。
            f1.mkdir();
        }*/

        // 可以创建多重目录吗?
        File f2 = new File("F:/a/b/c/d/e/f");
        if(!f2.exists()) {
            // 多重目录的形式新建。
            f2.mkdirs();
        }

        File f3 = new File("F:\\aaa\\bbb\\学习方法.txt");
        // 获取文件的父路径
        String parentPath = f3.getParent();
        System.out.println(parentPath); //F:\aaa\bbb

        File parentFile = f3.getParentFile();
        System.out.println("获取绝对路径:" + parentFile.getAbsolutePath()); //获取绝对路径:F:\aaa\bbb

        File f4 = new File("copy");
        System.out.println("绝对路径:" + f4.getAbsolutePath()); // 绝对路径:E:\IdeaProjects\javase\copy

    }
}
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

/*
File类的常用方法
 */
public class FileTest02 {
    public static void main(String[] args) {

        File f1 = new File("F:\\java自学\\面试题\\程序员面试宝典.pdf");
        // 获取文件名或者目录名
        System.out.println("文件名:" + f1.getName());  // 文件名:程序员面试宝典.pdf

        // 判断是否是一个目录
        System.out.println(f1.isDirectory()); // false

        // 判断是否是一个文件
        System.out.println(f1.isFile()); // true

        // 获取文件最后一次修改时间
        long haoMiao = f1.lastModified(); // 这个毫秒是从1970年到现在的总毫秒数。
        // 将总毫秒数转换成日期?????
        Date time = new Date(haoMiao);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(time);
        System.out.println("该文件最后一次被修改的时间为"+strTime); //该文件最后一次被修改的时间为2020-05-11 13:45:34 515

        // 获取文件大小
        System.out.println(f1.length() + "字节"); //701601字节。
        System.out.println(f1.length()/1024 + "KB");   // 1024字节=1KB
    }
}
import java.io.File;

/*
File中的listFiles方法。
 */
public class FileTest03 {
    public static void main(String[] args) {
        // File[] listFiles()
        // 获取当前目录下所有的子文件。
        File f = new File("F:\\java自学\\面试题");
        File[] files = f.listFiles();
        // foreach
        for(File file : files){
            //System.out.println(file.getAbsolutePath());
            System.out.println(file.getName());
        }
    }
}

  

7、拷贝目录【高难度】

拷贝某一目录下面的所有文件(包括子目录和文件)
需要使用到:
	FileInputStream
	FileOutputStream
	File
需要使用到递归。尝试实现一下!
import java.io.*;

public class CopyAll {
    public static void main(String[] args) {
        // 拷贝源
        File srcFile = new File("F:\\00测试文件夹\\测试\\测试素材");//测试素材内含多个子目录和文件
        // 拷贝目标
        File destFile = new File("D:\\a\\b\\c");
        // 调用方法拷贝
        copyDir(srcFile,destFile);
    }
    /**
     * 拷贝目录方法
     * @param srcFile 拷贝源
     * @param destFile 拷贝目录
     */
    private static void copyDir(File srcFile, File destFile) {
        if(srcFile.isFile()) {
            // srcFile如果是一个文件的话,递归结束。
            // 是文件的时候需要拷贝。
            // ....一边读一边写。
            FileInputStream in = null;
            FileOutputStream out = null;
            try {
                // 读这个文件
                in = new FileInputStream(srcFile);
                // 写到这个文件中
                //我们所希望的是,拷贝后目标目录变成这样:D:\a\b\c\00测试文件夹\测试\测试素材
                // 三目运算符? :  如果目标文件的绝对路径是以\结尾,那么就获取绝对路径,否则在获取的目标文件绝对路径后面添加\-------------substring(3)去掉源文件绝对路径的前三个字符
                String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\")  + srcFile.getAbsolutePath().substring(3);
                out = new FileOutputStream(path);
                // 一边读一边写
                byte[] bytes = new byte[1024 * 1024]; // 一次复制1MB
                int readCount = 0;
                while((readCount = in.read(bytes)) != -1){
                    out.write(bytes, 0, readCount);
                }
                out.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return;
        }

        // 获取源下面的子文件(包括目录和文件)
        File[] files = srcFile.listFiles();
        for(File file : files){
            // 获取所有文件的(包括目录和文件)绝对路径
            //System.out.println(file.getAbsolutePath());
            if(file.isDirectory()){
                // 新建对应的目录
                //System.out.println(file.getAbsolutePath());
                String srcDir = file.getAbsolutePath();
                //我们所希望的是,拷贝后目标目录变成这样:D:\a\b\c\00测试文件夹\测试\测试素材
                // 三目运算符? :  如果目标文件的绝对路径是以\结尾,那么就获取绝对路径,否则在获取的目标文件绝对路径后面添加\--------srcDir.substring(3)去掉srcDir的前三个字符
                String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\")  + srcDir.substring(3);
                File newFile = new File(destDir);
                if(!newFile.exists()){
                    newFile.mkdirs();
                }
            }
            // 递归调用
            copyDir(file, destFile);
        }
    }
}

  

8、IO + Properties联合使用(!!!)

IO流:文件的读和写。
Properties: 属性类,是一个Map集合,key和value都是String类型。

非常好的一个设计理念:
    以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。
    将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,
    服务器也不需要重启。就可以拿到动态的信息。

    类似于以上机制的这种文件被称为【配置文件】。
    并且当配置文件中的内容格式是:
	key1=value
	key2=value
    的时候,我们把这种配置文件叫做【属性配置文件】。

    java规范中有要求:
        属性配置文件建议以.properties结尾,但这不是必须的。
        这种以.properties结尾的文件在java中被称为:属性配置文件。
        其中Properties是专门存放属性配置文件内容的一个类。

在属性配置文件中的注意点:
	1、建议key和value之间使用=的方式,=左边是key,=右边是value
	2、在属性配置文件中井号#是注释
	3、属性配置文件的key重复的话,value会自动覆盖!
	4、最好不要有空格
	5、一个格式标准的属性配置文件:
		username=admin
		password=456456
		data=abc

【chapter23/userinfo.properties】+【IoPropertiesTest01.java】
#建议key和value之间使用=的方式。
#=左边是key,=右边是value
username=admin
########################在属性配置文件中井号是注释#############################
#属性配置文件的key重复的话,value会自动覆盖!
#password=admin123
password=456456
#最好不要有空格
data                   =     abc

#不建议使用:
#usernamex:admin
import java.io.FileReader;
import java.util.Properties;

/*
IO + Properties联合使用
 */
public class IoPropertiesTest01 {
    public static void main(String[] args) throws Exception{
        /*
        Properties是一个Map集合,key和value都是String类型。
        想将userinfo文件中的数据加载到Properties对象当中。
         */
        // 新建一个输入流对象
        FileReader reader = new FileReader("chapter23/userinfo.properties");

        // 新建一个Map集合
        Properties pro = new Properties();

        // 调用Properties对象的load方法将文件中的数据加载到Map集合中。
        pro.load(reader); // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value

        // 通过key来获取value呢?
        String username = pro.getProperty("username");
        System.out.println(username);    //admin

        String password = pro.getProperty("password");
        System.out.println(password);  //456456

        String data = pro.getProperty("data");
        System.out.println(data);      //abc

        String usernamex = pro.getProperty("usernamex");
        System.out.println(usernamex);   // null
    }
}
/*注:这里的userinfo.properties是在chapter23/userinfo.properties
       属性文件userinfo.properties中写了:
username=admin
password=456456
data=abc

 */	

传送门

上一章:JavaSE 进阶 - 第22章 集合(三)
下一章:JavaSE 进阶 - 第24章 多线程(一)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值