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
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
文件复制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是专门存放属性配置文件内容的一个类。