[TOC]

IO流程进阶学习

1.序列流

描述:序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时将从被整合的第一个流开始读, 读完一个之后继续读第二个以此类推。
原因:由于多个输入流写入到一个输出流,代码重复性高则采用序列流进行输出;
基础语法:

1
2
3
4
5
#可以将两个输入流整合成一个输入流
SequenceInputStream(InputStream s1, InputStream s2)

#序列流整合多个输入流合成一个输入流
SequenceInputStream(Enumeration<? extends InputStream> e)

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.weiyigeek.ioadvance;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class Demo1_SequenceInputStream {

public static void main(String[] args) throws IOException {
//示例1.将两个输入流整合成一个输入流并输出
demo1();

//示例2.将多个输入流整合成为一个输入流并输出采用Vector进行实现
FileInputStream fis1 = new FileInputStream("d:\\chinese.txt");//创建输入流对象
FileInputStream fis2 = new FileInputStream("d:\\demo.txt"); //创建输入流对象
FileInputStream fis3 = new FileInputStream("d:\\times.txt"); //创建输入流对象

Vector<FileInputStream> v = new Vector<FileInputStream>(); //创建集合对象(关键点)
v.add(fis1); //将流对象存储进来
v.add(fis2);
v.add(fis3);
Enumeration<FileInputStream> en = v.elements(); //枚举存在流对象元素
SequenceInputStream sis = new SequenceInputStream(en); //将枚举中输入流整合成一个
FileOutputStream fos = new FileOutputStream("d:\\Enumeration.txt");
int b;
while((b = sis.read()) != -1)
fos.write(b);
sis.close();
fos.close();

}

public static void demo1() throws IOException {
FileInputStream fis1 = new FileInputStream("d:\\chinese.txt");//创建输入流对象
FileInputStream fis2 = new FileInputStream("d:\\demo.txt"); //创建输入流对象
SequenceInputStream sis = new SequenceInputStream(fis1,fis2); //整合fis1/fis2两个输入流整合成一个流
FileOutputStream fos = new FileOutputStream("d:\\times.txt"); //创建输出流对象
byte[] arr = new byte[sis.available()];
int ch;
while((ch = sis.read(arr)) != -1){
fos.write(arr,0,ch);
fos.write("\r\n".getBytes());
}
sis.close();
fos.close();
}
}


2.内存输出流

什么是内存输出流?
答:该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据

使用方式

1
2
3
* 创建对象: new ByteArrayOutputStream()  //可以防止中文乱码,还有一种方法就是采用字符流
* 写出数据: write(int), write(byte[])
* 获取数据: toByteArray() //数据可以用 toByteArray()和 toString()检索。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.weiyigeek.ioadvance;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Demo2_ByteArrayOutputStream {
public static void main(String[] args) throws IOException {
// 示例1.内存输出流案例ByteArrayOutputStream并采用字节数组来循环读取字节数组
FileInputStream fis = new FileInputStream("d:\\times.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //在内存中创建了可以增长得内存数组
byte[] arr = new byte[1024]; //创建字节数组
int ch;
while((ch = fis.read(arr)) != -1)
baos.write(arr,0,ch); //将读取到的数据写入到内存之中

//方式1:将缓冲区数据全部获取除了并且赋值给arr数组
byte[] arr1 = baos.toByteArray();
System.out.println(new String(arr1));

//方式2:将缓存区的内容直接转换伟字符串进行输出(推荐)
System.out.println(baos.toString()); //注意即使没有调用底层也会默认的帮我们调用toString方法
fis.close(); //注意事项:内存输出流是不用关闭的当使用完毕后自动释放
}
}

//执行结果
// 我爱您,祖国!
// I'm WeiyiGeek!
// I studying JAVA!
// 我爱您,祖国!
// I'm WeiyiGeek!
// I studying JAVA!

// 我爱您,祖国!
// I'm WeiyiGeek!
// I studying JAVA!
// 我爱您,祖国!
// I'm WeiyiGeek!
// I studying JAVA!


3.随机访问流

描述:RandomAccessFile概述支持对随机访问文件的读取和写入还可以指定位置让文件指针执行,通过响应的方法从文件指针位置进行读写,输入操作读取字节开始在文件指针,并推进文件指针过去的字节读,并且采用随机访问流的好处可以多线程下载;

RandomAccessFile类不属于流它是Object类的子类。但它融合了InputStream和OutputStream的功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java.lang.Object 
java.io.RandomAccessFile

//类结构
public class RandomAccessFile extends Object implements DataOutput, DataInput, Closeable

//#构造方法
RandomAccessFile(File file, String mode) //#创建一个随机访问文件流读,随意写来,由 File参数指定的文件。
RandomAccessFile(String name, String mode) //#创建一个随机访问文件流,并可选择地写入到具有指定名称的文件中。

//#常用方法
read() //从这个文件读取一个字节的数据。
write() //将指定的字节写入该文件。
seek() //设置文件指针偏移量,从这个文件的开始,在下一个读或写发生。
length() //返回此文件的长度。

补充说明:

  • mode的四种模式:
    • “r”只供阅读。调用结果对象的任何写方法都会引发IOException。
    • “rw”开放阅读和写作。如果文件不存在那么将尝试创建它。
    • 与”rw”一样,”rws”对读写也是开放的,并且要求对文件内容或元数据的每次更新都要同步写入底层存储设备。
    • 与”rw”一样,”rwd”用于读写,并且要求对文件内容的每次更新都要同步写入底层存储设备。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.weiyigeek.ioadvance;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Demo3_RandomAccessFile {
public static void main(String[] args) throws IOException {
// 基础示例
RandomAccessFile raf = new RandomAccessFile("d:\\times.txt", "rw"); //注意模式
System.out.println("文件字节数: "+raf.length());
System.out.println("文件描述符对象: "+raf.getFD());
System.out.println("读取前offset量: "+raf.getFilePointer());
System.out.println("#### 读取内容 #####");
byte[] arr = new byte[(int)raf.length()]; //防止中文不乱码字节数组
int ch;
while((ch = raf.read(arr)) != -1)
System.out.print(new String(arr,0,ch));
System.out.println("读取后offset量: "+raf.getFilePointer());

raf.seek(0); //初始化读取从头读取
raf.write(97); //从头写入 (替换了我)
raf.write("\r\n".getBytes()); //从头写入
raf.seek(0);
int x = raf.read();
System.out.println("读取到的字符: " + (char)x); //从头读取我们输入97 = a
raf.close();
}
}

//执行结果
// 文件字节数: 106
// 文件描述符对象: [email protected]
// 读取前offset量: 0
// #### 读取内容 #####
// a
// 爱您,祖国! //关键点
// I'm WeiyiGeek!
// I studying JAVA!
// 我爱您,祖国!
// I'm WeiyiGeek!
// I studying JAVA!
// 读取后offset量: 106
// 读取到的字符: a


4对象操作流

描述:什么是对象操作流?
答:该流可以将一个对象写出或者读取一个对象到程序中.,也就是执行了序列化和反序列化的操作

基础语法:

1
2
写出(序列化): new ObjecOutputStream(OutputStream), writeObject()
读取(反序列化): new ObjectInputStream(InputStream), readObject()

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.weiyigeek.ioadvance;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;

public class Demo4_ObjectIO {

public static void main(String[] args) throws IOException, ClassNotFoundException {
// 示例1.实现对象的序列化和反序列化
demo1();
// 示例2:优化对象的序列化和反序列化
demo2();
}

public static void demo1() throws IOException, FileNotFoundException, ClassNotFoundException {
System.out.println("#示例1.实现对象的序列化和反序列化");
// (1)实例化对象
Personer p1 = new Personer("Java",23);
Personer p2 = new Personer("C",40);

// (2)创建对象输出流将对象序列化写出到对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\object.txt"));

// (3)将实例化对象经过序列化写入到object.txt中
oos.writeObject(p1);
oos.writeObject(p2);

// (4)关闭操作对象输出流
oos.close();

// (5)创建操作对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\object.txt"));

// (6)饭序列号读取对象(注意这里没执行一次readObject便是读取一个对象,但是如果读到末尾还在读会出现EOFException异常);
// 记住需要向下转型(需要添加类不存在的异常)
Personer fp1 = (Personer) ois.readObject();
Personer fp2 = (Personer) ois.readObject();

System.out.println("fp1 = " + fp1);
System.out.println("fp2 = " + fp2);

// (7)关闭操作对象输入流
ois.close();
}

public static void demo2() throws IOException, FileNotFoundException, ClassNotFoundException {
System.out.println("示例2:优化对象的序列化和反序列化");
// 采用集合方式存储对象然后经过对象输出流的writeObject一次将对象写成
Personer p1 = new Personer("张三",15);
Personer p2 = new Personer("李四",16);
Personer p3 = new Personer("王五",17);
Personer p4 = new Personer("雅虎",65);

ArrayList<Personer> list = new ArrayList<Personer>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);
//创建操作输出流并关联object.txt文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\object.txt"));
oos.writeObject(list); //将集合对象一次性写入
oos.close();

//反序列化读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\object.txt"));
//建立一个arraylist集合接收存储的对象
//泛型在运行期会被擦除,索引运行期相当于没有泛型
@SuppressWarnings("unchecked")
ArrayList<Personer> res = (ArrayList<Personer>) ois.readObject(); //将集合对象一次读取
//遍历输出反序列对象
for (Personer person : res) {
System.out.println(person);
}
ois.close();
}
}

//序列化对象声明定义
class Personer implements Serializable {
private String name;
private int age;
public Personer() {
super();
// TODO Auto-generated constructor stub
}
public Personer(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Personer [name=" + name + ", age=" + age + "]";
}
}

//执行结果
#示例1.实现对象的序列化和反序列化
fp1 = Personer [name=Java, age=23]
fp2 = Personer [name=C, age=40]

#示例2:优化对象的序列化和反序列化
Personer [name=张三, age=15]
Personer [name=李四, age=16]
Personer [name=王五, age=17]
Personer [name=雅虎, age=65]

注意事项:

  • 如果要实现序列化必须在对象上实现implements Serializeable 接口;
  • 在类实现过程中不用必须加id号 private static final long serialVersionUID = 2L

5.数据输入输出流

描述:什么是数据输入输出流?
答:DataInputStream和DataOutputStream可以按照基本数据类型大小读写数据,例如按Long大小写出一个数字, 写出时该数据占8字节.读取的时候也可以按照Long类型读取, 一次读取8个字节.

基础语法:

1
2
3
4
5
6
7
# java.lang.Object 
# java.io.InputStream
# java.io.FilterInputStream
# java.io.DataInputStream
# 输入流是不一定安全的多线程访问。线程安全是可选的是在这个类中的方法的用户的责任。
DataInputStream(OutIntStream), ReadInt(), ReadLong() #数据输输入流可以指定读取的类型
DataOutputStream(OutputStream), writeInt(), writeLong() #数据输出流可以指定写入的类型

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.weiyigeek.ioadvance;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo5_DataIO {

public static void main(String[] args) throws IOException {
// 示例1.数据输入输出流
// (1)常规写入FileOutputStream写出流对比操作
FileOutputStream fos = new FileOutputStream("d:\\demo1.txt");
/***
* 00000000 00000000 00000011 11100101 #int类型997
* 11100101 => 229 //存入的时候截取一字节的数据(当然这是我们不想看到的)
* **/
fos.write(997);
fos.write(998); //11100110 -> 230
fos.close();
FileInputStream fis = new FileInputStream("d:\\demo1.txt");
int x = fis.read();
int y = fis.read();
System.out.println(x + " - " + y);

// (2)采用数据输入输出流便不会存在这样的情况
DataOutputStream dos = new DataOutputStream(new FileOutputStream("d:\\demo1.txt"));
dos.writeInt(997); //存入一个int类型的数据
dos.writeInt(998);
dos.close();

DataInputStream dis = new DataInputStream(new FileInputStream("d:\\demo1.txt"));
System.out.println("占用字节数: "+ dis.available()+"B"); // 2个Int类型 = 8B 内存空间
x = dis.readInt();
y = dis.readInt();
System.out.println(x + " - " + y);
dis.close();
}
}

执行结果:
1
2
3
229 - 230
占用字节数: 8B
997 - 998


6.标准输入输出流

Q:什么是标准输入输出流?

  • System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据(一次读取一个字节)
  • System.out是PrintStream, 标准输出流, 默认可以向Console中输出字符和字节数据
1
2
3
4
5
6
7
#修改标准输入输出流(可以直接写入/读取指定的文件之中)
修改输入流: System.setIn(InputStream)
修改输出流: System.setOut(PrintStream) #!!!!注意参数是打印的是字符流!!!

#从键盘接收输入的两种方式
A:BufferedReader的readLine方法。
B:Scanner

基础案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.weiyigeek.ioadvance;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

public class Demo7_StandardIO {

public static void main(String[] args) throws IOException {
//示例1.标准输入输出流基础使用
System.setIn(new FileInputStream("d:\\PrintWrite.txt")); //修改标准输入流
System.setOut(new PrintStream("b.txt")); //修改标准输出流(需要采用打印流)
InputStream in = System.in; //获取标准输入流默认指向键盘输入改变之后指向a.txt
PrintStream ps = System.out; //获取标准输出流默认指向的是控制台改变后指向a.txt
int b;
while((b = in.read()) != -1) { //从a.txt上读取数据
ps.write(b); //将数据写到b.txt上
}
//System.out.println(); //也是一个输出流,不用关因为没有和硬盘上的文件产生关联的管道
in.close(); //恢复原始的标准输入输出流指向
ps.close();


//示例2.修改标准输入输出流拷贝图片
System.setIn(new FileInputStream("IO图片.png")); //改变标准输入流
System.setOut(new PrintStream("copy.png")); //改变标准输出流

InputStream is = System.in; //获取标准输入流
PrintStream ps1 = System.out; //获取标准输出流

int len;
byte[] arr = new byte[1024 * 8]; //字节数组(存储读取的字节流)
while((len = is.read(arr)) != -1)
ps1.write(arr, 0, len);

is.close();
ps1.close();

//示例3.两种方法实现键盘录入(与上面示例1/2冲突)
System.setIn(System.in); //恢复原始的标准输入输出流指向
System.setOut(System.out);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //转换流字节流转成一个字符流然后通过BufferedReader进行进一步封装
String line = br.readLine();
System.out.println("方式1键盘录入结果: " + line);
br.close();
//方式2就是采用Scanner类来接收输入
}
}

执行结果:
1
2
1024
方式1键盘录入结果: 1024


7.打印流

描述:什么是打印流?
答:该流可以很方便的将对象的toString()结果输出并且自动加上换行, 而且可以使用自动刷出的模式。

使用方式:

1
2
3
打印方法: print(), println()
PrintStream(OutputStream out, boolean autoFlush, String encoding) #创建一个新的打印流。 [打印字节流]
PrintWriter(OutputStream out, boolean autoFlush, String encoding) #创建一个新的PrintWriter,第二个参数字符流自动刷出到文件方法(自对于println()方法有用) [打印字符流]

基础案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.weiyigeek.ioadvance;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;

public class Demo6_PrintIO {
public static void main(String[] args) throws IOException {
// 示例1:打印流只操作数据目的
// (1)打印字节流数据
PrintStream ps = System.out; //就是一个PrintStream 其默认向控制台输出信息;
ps.write(97); //输出的是字符a
ps.println();
ps.println(97); //其实底层用的是Integer.toString(x),将x转换为数字字符串打印
ps.println("aaa");
ps.println(new Personer("张三", 23)); //直接打印对象(实际调用了对象的toString方法)
Personer p = null;
ps.println(p); //如果是null就返回null,如果不是null就调用对象的toString()

// (2)打印字符流数据
PrintWriter pw = new PrintWriter(new FileOutputStream("d:\\PrintWrite.txt"), true);
pw.write(97);
pw.print("大家好");
pw.println("你好"); //自动刷出,只针对的是println方法(但是缓冲区已经存在的数据会一起刷出)
pw.close();
}
}

//执行结果
//a
//97
//aaa
//Personer [name=张三, age=23]
//null
//d:\\PrintWrite.txt内容
//a大家好你好


Properties集合

描述:Properties的概述和作为Map集合(双列集合))的使用概述:

  • Properties 类是Hashtable的子类,表示了一个持久的属性集(他是双列集合),可保存在流中或从流中加载。
  • 属性列表中每个键及其对应值都是一个字符串。

基础语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java.lang.Object 
java.util.Dictionary<K,V>
java.util.Hashtable<Object,Object>
java.util.Properties

//#Properties类代表一个持久的特性的 Properties可以保存到流或流中加载。属性列表中的每个键和它的相应值是一个字符串。
public class Properties
extends Hashtable<Object,Object>

//Properties的特殊功能方法
* public Object setProperty(String key,String value) //#调用方法 put Hashtable
* public String getProperty(String key) //#在这个属性列表中搜索指定的键的属性
* public Enumeration<String> stringPropertyNames() //存Properties元素
//与IO流相关
* void load(InputStream inStream) //从输入字节流中读取属性列表(键和元素对)。
* void store(OutputStream out, String comments) //写这个属性列表(关键元素对)在这 Properties表格式来合适的输出流加载到一个 Properties表使用 load(InputStream)方法

//常用方式补充
Properties prop = new Properties();
InputStream ins = CurrentClassDemo.class.getClassLoader().getResourceAsStream("config.properties");
prop.load(ins);
String RedisUrl = prop.getProperty("URL");

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.weiyigeek.Collection;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;

public class Demo8_Properties {
public static void main(String[] args) throws IOException {
// 示例1.Properties集合使用虽然是双列集合但是没有泛型
demo1();

// 示例2.Properties的load()和store()功能
/***
* config.properties
* name=weiyigeek
* age=23
* qq=123456
*/
Properties prop = new Properties();
System.out.println("\n load方法读取文件 ; ");
System.out.println("读取之前:" + prop);
prop.load(new FileInputStream("d:\\config.properties"));
System.out.println("读取之后:" + prop);

prop.setProperty("add", "JavaPram");
prop.setProperty("qq", "88888888"); //修改文件中的qq键的值
System.out.println("\n store方法存储文件 ; ");
prop.store(new FileOutputStream("d:\\config.properties"), "[2019年10月11日 17点57分] 插入和修改值"); //第二个参数是注释Unicode编码

System.out.println("修改之后:" + prop);
}

public static void demo1() {
Properties pr = new Properties();
pr.put("Key", 1024);
pr.put("Name", "WeiyiGeek");
pr.setProperty("Age", "13"); //设置属性值(两个值必须是String);
System.out.println(pr); //打印键和值

// 打印所有的键值(采用枚举)
Enumeration<String> key = (Enumeration<String>) pr.propertyNames(); //获取所有键的名称
while(key.hasMoreElements())
{
String name = key.nextElement();
System.out.println(name + " = " + pr.getProperty(name));
}
}
}

//执行结果
//{Name=WeiyiGeek, Key=1024, Age=13}
//Key = null
//Name = WeiyiGeek
//Age = 13
//
// load方法读取文件 ;
//读取之前:{}
//读取之后:{age=23, qq=123456789, name=weiyigeek}
//
// store方法存储文件 ;
//修改之后:{age=23, qq=88888888, name=weiyigeek, add=JavaPram}


ResourceBundle类

描述:ResourceBundle类是用来读取properties的资源文件的,很多国际化操作都使用该类。

静态读取与动态读取

  • 1) 我们可以在每个对象中用ResourceBundle来读配置文件设置自己的值,也可以用一个固定的对象去读取然后保存下来以便以后使用。
    在每个class中都去读配置文件会导致代码散乱,所以只用一个class来进行读取是比较合理的做法。

  • 2) 另外由于I/O的速度比较慢,如果负责读取配置文件的class是在每次用到这些配置项的时候去读文件,就容易成为性能上的瓶颈。为避免这样的情况,可以在初始化的时候就把配置项一次全部读入,并保存在静态成员变量中。

  • 3) 不排除会有对配置项进行动态读取的需求(因为某些应用是不能停掉的,比如应用服务器。在这些应用运行期间更新了配置文件,就需要在不不关闭应用的情况下重新读入配置项)。

以下的例子只考虑了静态读取的情况,如果是动态读取则可以把读取配置文件的代码放到某个方法中,通过对某个事件的响应来调用该方法更新配置项。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.jun.it;
import java.util.ResourceBundle;
public class ResourceBundleDemo {
/**
* 测试ResourceBundle.getBundle
* @param args
*/
public static void main(String[] args) {
//基本的使用
ResourceBundle resourceBundle=ResourceBundle.getBundle("project_en"); //该工程下的project_en.properties
System.out.println(resourceBundle.getString("hello"));
//国际化的使用
ResourceBundle resourceBundle2=ResourceBundle.getBundle("project_cn");
System.out.println(resourceBundle2.getString("hello"));
}
}

执行结果:
1
2
Hello
你好