JAVA学习笔记21-IO流


IO流


IO流概述

什么是IO流?

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

在这里插入图片描述

IO流的分类?
有多种分类方式:

一种方式是按照流的方向进行分类:

以内存作为参照物,
往内存中去,叫做输入(Input)。或者叫做读(Read)。
从内存中出来,叫做输出(Output)。或者叫做写(Write)。

另一种方式是按照读取数据方式不同进行分类:

有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。
这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等…

假设文件file1.txt,采用字节流的话是这样读的:
a中国bc张三fe
第一次读:一个字节,正好读到’a’
第二次读:一个字节,正好读到’中’字符的一半。
第三次读:一个字节,正好读到’中’字符的另外一半。

有的流是按照字符的方式读取数据的

一次读取一个字符,这种流是为了方便读取普通文本文件而存在的
这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。

假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:'a’字符('a’字符在windows系统中占用1个字节。)
第二次读:'中’字符('中’字符在windows系统中占用2个字节。)

综上所述:流的分类

输入流、输出流
字节流、字符流

java IO流这块有四大家族:
	四大家族的首领:
		java.io.InputStream  字节输入流
		java.io.OutputStream 字节输出流

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

		四大家族的首领都是抽象类。(abstract class)

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

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


	注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
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


文件路径

java.io.FileInputStream:
    1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
    2、字节的方式,完成输入的操作,完成读的操作(硬盘---> 内存)
首先,涉及到对文件的操作,自然无法避免文件的位置。
在之前已经学过了文件的“相对路径”和“绝对路径”。
例如:D:/course/JavaProjects/02-JavaSE/temp 就是文件的绝对路径。
而:tempfile3 这种就是相对路径。
绝对路径很好理解,那么相对路径呢?
相对路径一定是从当前所在的位置作为起点开始找!
IDEA默认的当前路径是哪里?工程Project的根就是IDEA的默认当前路径。
如下图:

在这里插入图片描述

可以看到在Project目录下的文件就是他的相对路径的起点。
如果在某个包下的文件,其相对路径就是chapter23/src/com等等。
以此类推。

read()方法

在进行学习之前,首先要知道的是:在IO流中有许多方法都需要去对异常进行处理
一般都是直接try catch进行处理,为了简洁,在下面的笔记不表示出来
实际上,仅仅需要通过IDEA的一键生成try catch即可
所以说IDEA万岁!
回到FileInputStream

首先创建文件字节输入流对象
FileInputStream fis = null;
fis = new FileInputStream(这里面写文件的路径,相对绝对都行,带双引号);

在FileInputStream里面有个方法是read()
int	read()	从此输入流中读取下一个数据字节。
意思就是读取下一个字节的数据并返回。

例如:
假设文件中是:abcde
int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身。
System.out.println(readData); //97
当改文件读取完时,read()方法便返回 -1

所以加上循环,我们便可以一直读取文件到结束:
int readData = 0;
while((readData = fis.read()) != -1){
	System.out.println(readData);
}

一次读取多个字节!

至此,read()方法我们已经了解,但是一个一个字节的读取未免太慢!
一次读取一个字节byte,这样内存和硬盘交互太频繁,
基本上时间/资源都耗费在交互上面了。能不能一次读取多个字节呢?
可以!

在API中可以查阅到,read()方法还有一个:
int read(byte[] b)
一次最多读取 b.length 个字节。
减少硬盘和内存的交互,提高程序的执行效率。
往byte[]数组当中读。

开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节。
byte[] bytes = new byte[4]; // 准备一个4个长度的byte数组,一次最多读取4个字节。
int readCount = fis.read(bytes);

注意:这个方法的返回值是:读取到的字节数量。(不是字节本身)
要是想将其转为字节本身,还得借助String的构造方法。

---源自API的String构造方法
public String​(byte[] bytes,
              int offset,
              int length)
通过使用平台的默认字符集解码指定的字节子阵列来构造新的String 。 新String的长度是字符集的函数,因此可能不等于子String的长度。
未指定给定字节在默认字符集中无效时此构造函数的行为。 当需要更多地控制解码过程时,应使用CharsetDecoder类。
参数
bytes - 要解码为字符的字节
offset - 要解码的第一个字节的索引
length - 要解码的字节数
---
应该是读取了多少个字节,转换多少个,故用此构造方法。
System.out.println(new String(bytes,0, readCount));

还需要注意的是:
假设文件内容为:abcdef
但是我的byte数组长度为4,那么也就是一次读取4个字节,那么在读满一次后,下一次读取时我的byte数组中是如何的?
可以借助下图来表示:

在这里插入图片描述

是的!从图上可以看出,当读完一次后,下一次读取时是回到byte数组的第0位开始重新覆盖掉读取!
所以这就是为什么我们在转换为String时要保证:
读取了多少个字节,转换多少个。

FileInputStream的最终版

/*
最终版,需要掌握。
 */
public class FileInputStreamTest04 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("chapter23/src/tempfile3");
            // 准备一个byte数组
            byte[] bytes = new byte[4];
            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();
                }
            }
        }
    }
}

FileInputStream的其他常用方法:

FileInputStream类的其它常用方法:
    int available():返回流当中剩余的没有读到的字节数量
    long skip(long n):跳过几个字节不读。
    
int available()    
这个方法有什么用?
byte[] bytes = new byte[fis.available()];
这种方式不太适合太大的文件,因为byte[]数组不能太大。
不需要循环了。直接读一次就行了。
int readCount = fis.read(bytes);
System.out.println(new String(bytes));

long skip(long n)
skip跳过几个字节不读取,这个方法也可能以后会用!

FileOutputStream

首先创建输出对象:
FileOutputStream fos = null;
fos = new FileOutputStream("chapter23/src/tempfile3");

然后调用FileOutputStream里面的write()方法进行对文件的写入:
先来看看这个方法:
---源自API
public void write​(byte[] b) throws IOException
将b.length个字节写入此输出流。
该write的方法FilterOutputStream调用它write的三个参数方法与参数b , 0和b.length 。
注意,此方法不调用一个参数write其基础输出流的方法与单个参数b 。
---
首先创建一个byte数组。
byte[] bytes = {97, 98, 99, 100};byte数组全部写出!
fos.write(bytes); // abcd

当然也可以通过以前学过的String中的getBytes()方法去将String转换为byte数组,然后去write()。
字符串
String s = "我是一个中国人,我骄傲!!!";
将字符串转换成byte数组。
byte[] bs = s.getBytes();
写
fos.write(bs);

注意:如像上述的方法去写的话,那么每次执行write()方法时都是先将原文件清空,然后重新写入。
如果不想要先清空再写入,而是直接在后面加上时,则可以在创建输出对象时加上一个东西:
fos = new FileOutputStream("chapter23/src/tempfile3", true);
这个true表示以追加的方式去写入,而不是先清空再写入。

写完之后一定要刷新!!!
fos.flush();

文件复制1

使用FileInputStream + FileOutputStream完成文件的拷贝。
拷贝的过程应该是一边读,一边写。
使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。
public class Copy01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            // 创建一个输入流对象
            fis = new FileInputStream("D:\\course\\02-JavaSE\\video\\chapter01\\动力节点-JavaSE-杜聚宾-001-文件扩展名的显示.avi");
            // 创建一个输出流对象
            fos = new FileOutputStream("C:\\动力节点-JavaSE-杜聚宾-001-文件扩展名的显示.avi");

            // 最核心的:一边读,一边写
            byte[] bytes = new byte[1024 * 1024]; // 1MB(一次最多拷贝1MB。)
            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 (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileReader

文件字符输入流,只能读取普通文本。
读取文本内容时,比较方便,快捷。

创建文件字符输入流
reader = new FileReader("tempfile");

准备一个char数组
char[] chars = new char[4];char数组中读
reader.read(chars); 
for(char c : chars) {
	System.out.println(c);
}

上述的read()方法最终根据继承可以得到其来自于他的父类:Reader
int	read​(char[] cbuf)	将字符读入数组。
当然其中也还有方法:
abstract int	read​(char[] cbuf, int off, int len)	将字符读入数组的一部分。
或者另一子类中的
int	read​(char[] cbuf, int offset, int length)	将字符读入数组的一部分。

以及
boolean	ready()	判断此流是否可以读取。
等等,详询API

JDK11-API


FileWriter

FileWriter:
    文件字符输出流。写。
    只能输出普通文本。
    
创建文件字符输出流对象
out = new FileWriter("file");
out = new FileWriter("file", true);//这种同上,以追加的方式写入

开始写。
char[] chars = {'我','是','中','国','人'};
out.write(chars);
write​(char[] cbuf)	
写一个字符数组。

out.write(chars, 2, 3);
write​(char[] cbuf, int off, int len)	
写一个字符数组的一部分。

out.write("hello world!");
write​(String str)	
写一个字符串。

write​(String str, int off, int len)	
写一个字符串的一部分。

最后记得刷新!
out.flush();

更多方法详询API

JDK11-API


文件复制2

使用FileReader FileWriter进行拷贝的话,只能拷贝“普通文本”文件。

public class Copy02 {
    public static void main(String[] args) {
        FileReader in = null;
        FileWriter out = null;
        try {
            // 读
            in = new FileReader("chapter23/src/com/bjpowernode/java/io/Copy02.java");
            // 写
            out = new FileWriter("Copy02.java");

            // 一边读一边写:
            char[] chars = new char[1024 * 512]; // 1MB
            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();
                }
            }
        }
    }
}

带有缓冲区的字符流


BufferedReader

BufferedReader:
    带有缓冲区的字符输入流。
    使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。

从API我们可以查阅到他的构造方法:
BufferedReader​(Reader in)	
创建使用默认大小的输入缓冲区的缓冲字符输入流。

所以要传一个Reader进去,但是Reader是抽象类,所以我们可以传他的子类:
FileReader

创建BufferedReader
FileReader reader = new FileReader("Copy02.java");
BufferedReader br = new BufferedReader(reader);

在API中可以查阅到,BufferedReader有方法:
readLine()
这个方法可以读一整行文本(不包括换行符!)
String s = null;
while((s = br.readLine()) != null){
	System.out.print(s);
}
从这里便可以看出缓冲的作用,我们在读的时候不需要新建数组(byte数组或者char数组!)

最后则需要关闭流:
对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
br.close();

节点流和包装流

当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。

像上面的程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。

转换流

InputStreamReader将字节流转换成字符流。
OutputStreamWriter:转换流

字节流
FileInputStream in = new FileInputStream("Copy02.java");
通过转换流转换(InputStreamReader将字节流转换成字符流。)
InputStreamReader reader = new InputStreamReader(in);
现在reader就是Reader类的了
这样可以把一个原本是FileInputStream的转为Reader了,从而放进BufferedReader

BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("Copy02.java")));

BufferedWriter

BufferedWriter:带有缓冲的字符输出流。

BufferedWriter out = new BufferedWriter(new FileWriter("copy"));
out.write("hello world!");

同样也是不需要新建数组就可以直接操作。

数据流

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

DataOutputStreamTest


public class DataOutputStreamTest {
    public static void main(String[] args) throws Exception{
        // 创建数据专属的字节输出流
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("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();
        // 关闭最外层
        dos.close();
    }
}


DataInputStream

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

public class DataInputStreamTest01 {
    public static void main(String[] args) throws Exception{
        DataInputStream dis = new DataInputStream(new FileInputStream("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);
        System.out.println(l);
        System.out.println(f);
        System.out.println(d);
        System.out.println(sex);
        System.out.println(c);

        dis.close();
    }
}

标准输出流

java.io.PrintStream:标准的字节输出流。默认输出到控制台。

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

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

标准输出流不需要手动close()关闭。

可以改变标准输出流的输出方向
标准输出流不再指向控制台,指向“log”文件。
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
修改输出方向,将输出方向修改到"log"文件。
System.setOut(printStream);

File

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

创建一个File对象
File f1 = new File("D:\\file");

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

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

	b、新建(以文件形式)
	if(!f1.exists()) {
     	以文件形式新建
		f1.createNewFile();
	}

	c、新建(以目录形式)
	if(!f1.exists()) {
		以目录的形式新建。
		f1.mkdir();
	}

	d、新建(多重目录)
	File f2 = new File("D:/a/b/c/d/e/f");
    if(!f2.exists()) {
		多重目录的形式新建。
		f2.mkdirs();
	}
	
	e、获取文件绝对路径
	getAbsolutePath()
	System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());
	
	f、获取文件父路径
	File f3 = new File("D:\\course\\01-开课\\学习方法.txt");
    获取文件的父路径
    String parentPath = f3.getParent();
    System.out.println(parentPath); //D:\course\01-开课

	g、获取文件名
    File f1 = new File("D:\\course\\01-开课\\开学典礼.ppt");       
    System.out.println("文件名:" + f1.getName());

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

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

    j、获取文件最后一次修改时间
    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);

    k、获取文件大小
    System.out.println(f1.length()); //216064字节。

	l、获取当前目录下所有的子文件
	File f = new File("D:\\course\\01-开课");
	File[] files = f.listFiles();
	for(File file : files){
	System.out.println(file.getName());
	}	

序列化与反序列化


什么是序列化和反序列化

首先来了解什么是序列化和反序列化:
当需要将JAVA内存中的JAVA对象传输保存带硬盘中时
需要将JAVA对象“切”为许多小份,按顺序编号放入,这个过程就叫“序列化”
同理,反过来则是“反序列化”
如下图

在这里插入图片描述

这里面涉及到了:
ObjectOutputStream和ObjectInputStream

ObjectOutputStream

参与序列化和反序列化的对象,必须实现Serializable接口。
否则会出现异常:java.io.NotSerializableException

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

那么序列化该如何操作?
首先,为所需要的序列化的类实现Serializable接口:
例如为Student类实现:
public class Student implements Serializable{}

接下来实现序列化:
创建java对象
Student s = new Student(1111, "zhangsan");

序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));

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

刷新
oos.flush();

关闭
oos.close();

这是一次序列化一个对象,如何一次序列化多个对象呢?对集合进行序列化:
注意:参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。(可以去源代码或者API查看是否实现了Serializable接口)

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("users"));

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

oos.flush();
oos.close();

ObjectInputStream

反序列化

public class ObjectInputStreamTest01 {
    public static void main(String[] args) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
        // 开始反序列化,读
        Object obj = ois.readObject();
        // 反序列化回来是一个学生对象,所以会调用学生对象的toString方法。
        System.out.println(obj);
        ois.close();
    }
}

---

反序列化集合

public class ObjectInputStreamTest02 {
    public static void main(String[] args) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("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();
}

关键字transient

在一个类当中,有些属性如果我们不想让他序列化,怎么办?
使用transient关键字来完成该操作!
transient 游离的
这样可以让该元素不被序列化。

序列化版本号

每个JAVA对象序列化之后,会自动赋予其一个序列化版本号,长相如下:
private static final long serialVersionUID = -7998917368642754840L;

这个序列化版本号是干什么的呢?
用来区别类的。

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

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

但是这样就造成了一个问题:如果对于同一个类,我在10年前写的已经无法满足10年后我的需求,我得去修改/增添代码在类的内部!那可以直接写吗?

不可以!
改动过后的类必然要重新编译,此时由于他实现了Serializable接口,JAVA势必会给他重新分配一个“序列化版本号”
此时再去反序列化,是无法成功的!因为此时该类的序列化版本号已经与以前(初次序列化时)的不一样,自然无法反序列化!

会触发异常:
java.io.InvalidClassException:
com.bjpowernode.java.bean.Student;
local class incompatible:
stream classdesc serialVersionUID = -684255398724514298(十年后),
local class serialVersionUID = -3463447116624555755(十年前)

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

最终结论:
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。
这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
直接在类中加入:
private static final long serialVersionUID = 随便写L;

IO & Properties

首先我们来新建一个文件(无后缀)
userinfo
类容如下:
username=admin
password=456456

这就类似于一个Map了,有key和value切一一对应
=左边是key,=右边是value

Properties是一个Map集合,key和value都是String类型。
然后我们想将userinfo文件中的数据加载到Properties对象当中。

新建一个输入流对象
FileReader reader = new FileReader("chapter23/userinfo");

新建一个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);

这样就能够完成一个操作:仅仅在文件中修改数据,从而不用去改动java代码,也不用重新编译和重启服务器等等,非常好!

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值