pull/1/head
wanglei 2020-08-12 15:33:49 +08:00
parent 0f1d690937
commit 5768a06296
6 changed files with 762 additions and 0 deletions

View File

@ -0,0 +1,99 @@
今天在排除代码中的Bug的时候在浮点数运算过程中遇到了NAN与INFINITY的问题。特此记录一下。
首先明确一点的是java浮点数中有两个特殊情况NAN,INFINITY
## 1.NAN
NAN是一个特殊的值。在JDK中NAN是这么定义的
```
/**
* A constant holding a Not-a-Number (NaN) value of type
* {@code double}. It is equivalent to the value returned by
* {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
*/
public static final double NaN = 0.0d / 0.0;
```
特意将注释也copy下来。相信加上注释同学们就都明白是什么意思了。Not-a-Number准确道出了NAN的含义。
```
@Test
public void testNan() {
double NaN1 = Double.NaN;
double NaN2 = 0.0 / 0.0;
System.out.println(Double.isNaN(NaN1)); //true
System.out.println(Double.isNaN(NaN2)); //true
System.out.println(NaN1 == NaN1); //false
}
```
NAN表示非数字它与任何值都不相等甚至不等于它自己所以要判断一个数是否为NAN要用isNAN方法。
## 2.INFINITY
INFINITY主要是为了解决除数为0的情况。稍微有点数学基础的同学都应该明白无限这个概念。
```
/**
* A constant holding the positive infinity of type
* {@code double}. It is equal to the value returned by
* {@code Double.longBitsToDouble(0x7ff0000000000000L)}.
*/
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
/**
* A constant holding the negative infinity of type
* {@code double}. It is equal to the value returned by
* {@code Double.longBitsToDouble(0xfff0000000000000L)}.
*/
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
```
```
/**
* A constant holding the positive infinity of type
* {@code float}. It is equal to the value returned by
* {@code Float.intBitsToFloat(0x7f800000)}.
*/
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
/**
* A constant holding the negative infinity of type
* {@code float}. It is equal to the value returned by
* {@code Float.intBitsToFloat(0xff800000)}.
*/
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
```
这是JDK中的相关定义。很容易看出来double与float中都有INFINITY的相关定义。
```
@Test
public void testInfinity() {
double Inf1 = Double.POSITIVE_INFINITY;
double Inf2 = Double.NEGATIVE_INFINITY;
float Inf3 = Float.POSITIVE_INFINITY;
float Inf4 = Float.NEGATIVE_INFINITY;
System.out.println(Double.isInfinite(Inf1)); //true
System.out.println(Float.isInfinite(Inf3)); //true
System.out.println(Inf1 == Inf3); //true
System.out.println(Inf2 == Inf4); //true
System.out.println(Inf1 * 0); //NaN
System.out.println(Inf1 + 1); //Infinity
System.out.println(Inf1 * 0.4); //Infinity
System.out.println(Inf1 / 0); //Infinity
}
```
从测试代码中,可以得出如下结论:
1.double或者float判断是不是INFINITY都使用isInfinite方法。
2.double中的INFINITY与float中的INFINITY是相等的。
3.INFINITY乘以0得到NAN。
4.INFINITY做除了乘以0意外的任何四则运算得到的结果仍然是INFINITY。
第三点跟第四点结果INFINITY的数学性质很容易理解。

View File

@ -0,0 +1,287 @@
## 1.枚举类 (enum)
1.在某些情况下一个类的对象时有限且固定的如季节类它只有春夏秋冬4个对象这种实例有限且固定的类在 Java 中被称为枚举类;
2.在 Java 中使用 enum 关键字来定义枚举类,其地位与 class、interface 相同;
3.枚举类是一种特殊的类,它和普通的类一样,有自己的成员变量、成员方法、构造器 (只能使用 private 访问修饰符,所以无法从外部调用构造器,构造器只在构造枚举值时被调用)
4.一个 Java 源文件中最多只能有一个 public 类型的枚举类,且该 Java 源文件的名字也必须和该枚举类的类名相同,这点和类是相同的;
5.使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口;
6.所有的枚举值都是 public static final 的,且非抽象的枚举类不能再派生子类;
7.枚举类的所有实例(枚举值)必须在枚举类的第一行显式地列出,否则这个枚举类将永远不能产生实例。列出这些实例(枚举值)时,系统会自动添加 public static final 修饰,无需程序员显式添加。
## 2.枚举类的使用
### 定义枚举类
```
// 定义一个星期的枚举类
public enum WeekEnum {
// 在第一行显式地列出7个枚举实例(枚举值),系统会自动添加 public static final 修饰
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
```
### 枚举类的成员变量、成员方法、构造器
```
package enumtest;
public enum WeekEnum {
// 因为已经定义了带参数的构造器,所以在列出枚举值时必须传入对应的参数
SUNDAY("星期日"), MONDAY("星期一"), TUESDAY("星期二"), WEDNESDAY("星期三"),
THURSDAY("星期四"), FRIDAY("星期五"), SATURDAY("星期六");
// 定义一个 private 修饰的实例变量
private String date;
// 定义一个带参数的构造器,枚举类的构造器只能使用 private 修饰
private WeekEnum(String date) {
this.date = date;
}
// 定义 get set 方法
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
```
### 枚举类中的常用方法
1.int compareTo(E o) 该方法用于与制定枚举对象比较顺序,同一个枚举实例只能与相同类型的枚举实例比较。如果该枚举对象位于指定枚举对象之后,则返回正整数;反之返回负整数;否则返回零;
```
public class Test01 {
public static void main(String[] args) {
System.out.println(WeekEnum.FRIDAY.compareTo(WeekEnum.MONDAY));
System.out.println(WeekEnum.FRIDAY.compareTo(WeekEnum.SUNDAY));
System.out.println(WeekEnum.FRIDAY.compareTo(WeekEnum.SATURDAY));
}
}
```
运行结果:
4
5
-1
2.String name() 返回此枚举实例的名称,即枚举值
3.static values() 返回一个包含全部枚举值的数组,可以用来遍历所有枚举值;
```
// 没有重写 toString 方法
for (WeekEnum we : WeekEnum.values()) {
System.out.println(we);
}
```
运行结果:
SUNDAY
MONDAY
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
4.String toString() 返回枚举值的名称,与 name 方法类似,更常用;
```
// 定义一个星期的枚举类
public enum WeekEnum {
// 因为已经定义了带参数的构造器,所以在列出枚举值时必须传入对应的参数
SUNDAY("星期日"), MONDAY("星期一"), TUESDAY("星期二"), WEDNESDAY("星期三"),
THURSDAY("星期四"), FRIDAY("星期五"), SATURDAY("星期六");
// 定义一个 private 修饰的实例变量
private String date;
// 定义一个带参数的构造器,枚举类的构造器只能使用 private 修饰
private WeekEnum(String date) {
this.date = date;
}
// 定义 get set 方法
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
// 重写 toString() 方法
@Override
public String toString(){
return date;
}
}
```
```
// 重写了 toString 方法
for (WeekEnum we : WeekEnum.values()) {
System.out.println(we);
}
```
运行结果:
星期日
星期一
星期二
星期三
星期四
星期五
星期六
结合上面3.4点,可以看到,重写 toString 方法前后所返回的枚举值不同!
5.int ordinal() 返回枚举值在枚举类中的索引值(从0开始),即枚举值在枚举声明中的顺序,这个顺序根据枚举值声明的顺序而定;
```
System.out.println(WeekEnum.SUNDAY.ordinal());
System.out.println(WeekEnum.FRIDAY.ordinal());
```
运行结果:
0
5
6.static valueOf() 返回带指定名称的指定枚举类型的枚举常量,名称必须与在此类型中声明枚举常量所用的标识符完全匹配(不允许使用额外的空白字符)。这个方法与toString相对应因此重写 toString() 方法,一定要重写 valueOf() 方法(我们可以重写 toString() 方法,但不能自己重写 valueOf() 方法,当我们重写 toString() 方法时valueOf() 方法会自动重写,不用我们理会。)
```
public class Test01 {
public static void main(String[] args) {
System.out.println(WeekEnum.valueOf(WeekEnum.class, "MONDAY"));
System.out.println(WeekEnum.valueOf(WeekEnum.class, "FRIDAY"));
System.out.println(WeekEnum.valueOf(WeekEnum.class, "SUNDAY"));
}
}
```
运行结果:
MONDAY
FRIDAY
SUNDAY
7.boolean equals()方法: 比较两个枚举类对象的引用。
使用枚举类实现接口
与普通类一样,枚举类也可以实现一个或多个接口。枚举类实现接口时,同样要实现
该接口的所有方法。
```
public interface GenderDescription {
public void info();
}
```
上面定义了一个接口,该接口有一个 info() 方法,凡是实现该接口的类都需要实现该方法。
```
public enum Gender implements GenderDescription {
MALE,FEMALE;
@Override
public void info() {
System.out.println("这是一个用于定义性别的枚举类");
}
}
```
```
public class Test02 {
public static void main(String[] args) {
Gender.MALE.info();
Gender.FEMALE.info();
}
}
```
运行结果:
这是一个用于定义性别的枚举类
这是一个用于定义性别的枚举类
## 3.包含抽象方法的枚举类
定义一个 Operation 枚举类有4个枚举值PLUS、MINUS、TIMES、DIVIDE分别代表加、减、乘、除该枚举类有一个 calculate() 方法,用于完成计算。
```
public enum Operation {
// 用于执行加法运算
PLUS { // 花括号部分其实是一个匿名内部子类
@Override
public double calculate(double x, double y) {
return x + y;
}
},
// 用于执行减法运算
MINUS { // 花括号部分其实是一个匿名内部子类
@Override
public double calculate(double x, double y) {
// TODO Auto-generated method stub
return x - y;
}
},
// 用于执行乘法运算
TIMES { // 花括号部分其实是一个匿名内部子类
@Override
public double calculate(double x, double y) {
return x * y;
}
},
// 用于执行除法运算
DIVIDE { // 花括号部分其实是一个匿名内部子类
@Override
public double calculate(double x, double y) {
return x / y;
}
};
//为该枚举类定义一个抽象方法,枚举类中所有的枚举值都必须实现这个方法
public abstract double calculate(double x, double y);
}
```
```
public class Test03 {
public static void main(String[] args) {
System.out.println("6 + 2 = " + Operation.PLUS.calculate(6, 3));
System.out.println("6 - 2 = " + Operation.MINUS.calculate(6, 2));
System.out.println("6 * 2 = " + Operation.TIMES.calculate(6, 2));
System.out.println("6 / 2 = " + Operation.DIVIDE.calculate(6, 2));
}
}
```
运行结果:
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/enum/1.png)
原文链接地址:
http://www.jianshu.com/p/46dbd930f6a2

View File

@ -0,0 +1,196 @@
Java8发布已经有一段时间了这次发布的改动比较大很多人将这次改动与Java5的升级相提并论。Java8其中一个很重要的新特性就是lambda表达式允许我们将行为传到函数中。
想想看在Java8之前我们想要将行为传入函数仅有的选择就是匿名内部类。Java8发布以后lambda表达式将大量替代匿名内部类的使用简化代码的同时更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。尤其是对于做数据的同学来说当习惯使用类似scala之类的函数式编程语言以后体会将更加深刻。现在我们就来看看Java8中lambda表达式的一些常见写法。
## 1.替代匿名内部类
毫无疑问lambda表达式用得最多的场合就是替代匿名内部类而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大用()->就可以代替整个匿名内部类!请看代码:
如果使用匿名内部类:
```
@Test
public void oldRunable() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The old runable now is using!");
}
}).start();
}
```
而如果使用lambda表达式
```
@Test
public void runable() {
new Thread(() -> System.out.println("It's a lambda function!")).start();
}
```
最后的输出:
```
The old runable now is using!
It's a lambda function!
```
是不是强大到可怕是不是简单到可怕是不是清晰明了重点突出到可怕这就是lambda表达式的可怕之处用极少的代码完成了之前一个类做的事情
## 2.使用lambda表达式对集合进行迭代
Java的集合类是日常开发中经常用到的甚至说没有哪个java代码中没有使用到集合类。。。而对集合类最常见的操作就是进行迭代遍历了。请看对比
```
@Test
public void iterTest() {
List<String> languages = Arrays.asList("java","scala","python");
//before java8
for(String each:languages) {
System.out.println(each);
}
//after java8
languages.forEach(x -> System.out.println(x));
languages.forEach(System.out::println);
}
```
如果熟悉scala的同学肯定对forEach不陌生。它可以迭代集合中所有的对象并且将lambda表达式带入其中。
```
languages.forEach(System.out::println);
```
这一行看起来有点像c++里面作用域解析的写法,在这里也是可以的。
## 3.用lambda表达式实现map
一提到函数式编程一提到lambda表达式怎么能不提map。。。没错java8肯定也是支持的。请看示例代码
```
@Test
public void mapTest() {
List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
cost.stream().map(x -> x + x*0.05).forEach(x -> System.out.println(x));
}
```
最后的输出结果:
```
10.5
21.0
31.5
```
map函数可以说是函数式编程里最重要的一个方法了。map的作用是将一个对象变换为另外一个。在我们的例子中就是通过map方法将cost增加了0,05倍的大小然后输出。
## 4.用lambda表达式实现map与reduce
既然提到了map又怎能不提到reduce。reduce与map一样也是函数式编程里最重要的几个方法之一。。。map的作用是将一个对象变为另外一个而reduce实现的则是将所有值合并为一个请看
```
@Test
public void mapReduceTest() {
List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
double allCost = cost.stream().map(x -> x+x*0.05).reduce((sum,x) -> sum + x).get();
System.out.println(allCost);
}
```
最终的结果为:
```
63.0
```
如果我们用for循环来做这件事情
```
@Test
public void sumTest() {
List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
double sum = 0;
for(double each:cost) {
each += each * 0.05;
sum += each;
}
System.out.println(sum);
}
```
相信用map+reduce+lambda表达式的写法高出不止一个level。
## 5.filter操作
filter也是我们经常使用的一个操作。在操作集合的时候经常需要从原始的集合中过滤掉一部分元素。
```
@Test
public void filterTest() {
List<Double> cost = Arrays.asList(10.0, 20.0,30.0,40.0);
List<Double> filteredCost = cost.stream().filter(x -> x > 25.0).collect(Collectors.toList());
filteredCost.forEach(x -> System.out.println(x));
}
```
最后的结果:
```
30.0
40.0
```
将java写出了python或者scala的感觉有没有是不是帅到爆
## 6.与函数式接口Predicate配合
除了在语言层面支持函数式编程风格Java 8也添加了一个包叫做 java.util.function。它包含了很多类用来支持Java的函数式编程。其中一个便是Predicate使用 java.util.function.Predicate 函数式接口以及lambda表达式可以向API方法添加逻辑用更少的代码支持更多的动态行为。Predicate接口非常适用于做过滤。
```
public static void filterTest(List<String> languages, Predicate<String> condition) {
languages.stream().filter(x -> condition.test(x)).forEach(x -> System.out.println(x + " "));
}
public static void main(String[] args) {
List<String> languages = Arrays.asList("Java","Python","scala","Shell","R");
System.out.println("Language starts with J: ");
filterTest(languages,x -> x.startsWith("J"));
System.out.println("\nLanguage ends with a: ");
filterTest(languages,x -> x.endsWith("a"));
System.out.println("\nAll languages: ");
filterTest(languages,x -> true);
System.out.println("\nNo languages: ");
filterTest(languages,x -> false);
System.out.println("\nLanguage length bigger three: ");
filterTest(languages,x -> x.length() > 4);
}
```
最后的输出结果:
```
Language starts with J:
Java
Language ends with a:
Java
scala
All languages:
Java
Python
scala
Shell
R
No languages:
Language length bigger three:
Python
scala
Shell
```
可以看到Stream API的过滤方法也接受一个Predicate这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码这也是lambda表达式的魔力
参考文档:
1.http://www.importnew.com/16436.html

View File

@ -0,0 +1,56 @@
## 1.先名词解释吧:
DAO = Data Access Object = 数据存取对象
Service = 服务
Controller = 控制器
Util = 工具
Model = 模型
首先一个代码是不是有完善的结构和是不是有上面这些东西没有什么关系只是通常来说我们做一个大项目会把项目分解成很多不不同的模块Module然后根据用途和角色我们对这些模块有一个通用的命名规则这也就是上面这些英文单词的来历。所以请一定记住项目中是否包含这些模块或者单词和你的项目结构是否完善一毛钱关系没有。但是当你的项目结构相对完善的时候你会发现有这样一些角色的存在。
接下来一个个的来详细讨论一下这个东西是如何出现的:
## 2.DAO
DAO数据存取对象。通常我们会遇到很多要和数据库打交道的场景如果为每一个场景都去写一些SQL语句会让我们代码变得很奇怪我们希望我们的代码比较干净整洁那么一个很容易想到的方案就是把数据库封装一下让我们和数据库的交道看起来比较像和一个对象打交道。这个对象通常就是DAO当我们操作这个对象的时候这个对象会自动的产生SQL语句来和数据库打交道而我们只需要和DAO打交道就可以了。
当然从本质上来说DAO并不需要和数据库有什么必然的联系DAO只是数据存取对象的缩写所以只要是把数据持久化包装成一个对象的访问读写这种对象都可以被称之为DAO譬如用JSON格式存到硬盘上。
## 3.Service
Service我们有时候会需要一些相对独立与业务系统没啥关系的功能。但不是所有的功能都可以做成一个服务服务是一个相对独立的功能模块完成一些指定的工作这些工作高度抽象和通用。一个典型的服务像是数据库服务、缓存服务、文件存储服务、身份验证服务、消息队列服务等。
关系型数据库服务可以视为是一个接收SQL语句并给出一个查询结果的服务我们不必关心服务内部具体是如何处理问题的我们只需要关注服务给出的接口。
并不是所有的模块都适合做成服务一个服务首先最重要的是独立性这个服务必须可以独立的完成指定的工作。复杂的服务可能依赖于一个或者多个更基础的服务但是服务通常不应当依赖于任何具体的业务代码服务必须具有高度的抽象性。关系型数据库服务就具有高度的抽象性事实上只要我们撰写标准的SQL不论后面是MySQL、SQL Server还是Oracle他们都会呈现出几乎完全相同的行为。
一个更为简单的服务像是缓存服务,我们把一坨数据放进去,在一段时间内可以快速的获取这坨数据,在一段时间后数据就会消失。
当你的代码需要一个高度抽象高度标准化的功能,而这个功能又不能简单的实现,或者这个功能需要很多资源的配合,例如缓存服务需要内存资源,而数据库服务通常需要磁盘资源,身份验证服务通常需要数据库服务支持。这个时候就可以考虑将这个功能模块做成一个服务。
服务作为基础的部件,我们通常会要求它能够应付各种各样的情况,一个优质的服务通常会有非常高的可用性,因为我们的系统可能会依赖于各种各样的服务,而整个系统的可用性将不可能比其中任何一个服务的可用性更高。
所以服务的特征:抽象、独立、稳定。
评论中提到Java项目中的Service通常是指Business Service这里也简单说说。
很多时候我们发现服务的特征对于我们开发一个大型项目的时候很有帮助。就拿独立性来说关系型数据库服务如SQL Server可以独立发售独立安装和部署。它可以自行测试自己的接口如果都达到了预期的效果并且能够应付各种情况这个服务就可以作为一个产品独立的出售给我们安装。这意味着关系型数据库服务并不需要配合我们的业务系统一起进行测试和调试或者作出什么变更。
在完成一个大型的业务系统时,我们发现一些子模块或者子系统也可以像服务一样独立的部署和测试。例如会员系统、支付系统、订单系统等等,他们的业务逻辑可能非常复杂,但是逻辑相对独立,并且高度内聚。如果我们将这些系统分别独立的测试和部署,就可以大大的降低我们的测试、部署和运维的成本。
这些可以独自完成某一方面业务功能高度内聚可以独立部署测试的模块我们可以称之为Business Service业务服务。它同样具有服务的特征抽象、独立和稳定。一个会员系统内部的逻辑可能非常复杂积分规则分级规则风险控制行为数据但是在其外部会员的概念可以非常简单
## 4.Util
UtilUtil通常来说是我们找不到合适的名字的时候的选择Util就是工具在做项目的时候我们总会遇到一些奇奇怪怪的小功能或者重复的代码需要提取。像是URL编码或者解码当然这个类库通常会提供不过就以 .NET Framework 为例提供这个方法的类型名称叫做HttpUtility或是自创的加密签名算法等等。
## 5.Model
Model模型通常来讲我们会把模型和另一个东西放在一起来说View视图。
模型通常认为是视图的内核,何谓之视图?我们正在与之交互的知乎网站的界面就是视图,而模型是指他的内核:数据。
知乎的数据是问题和答案问题分为标题和描述答案有内容和作者以及各种状态。知乎有很多个UI例如移动页面普通PC页面手机APP以及改版前的旧界面这些被称作不同的视图。而所有这些形态迥异的视图其内核都是一样的这个内核我们就称之为模型Model
将Model和View的概念拆分开来有助于我们关注不同的方面也可以更有效的分工。有些工程师更关注于内核也就是模型通常来说他们被称之为后端工程师。有些工程师更关注于用户界面的交互和展示通常来说他们被称之为前端工程师。
原文链接:
https://www.zhihu.com/question/58410621/answer/157049250

View File

@ -0,0 +1,44 @@
系统中的密码等用户信息肯定不能用明文来存储。如果有发生信息泄露等问题用明文存储的密码就太危险了。所以一般我们都用md5等方式来对密码进行加密处理。
以下代码就可以用来生成字符串的md5加密。
```
public class Md5UtilDemo {
public static String md5(String plainText) {
String encryptText = null;
try {
//拿到一个md5转换器
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(plainText.getBytes("UTF8"));
byte s[] = md.digest();
String result = "";
for (int i = 0; i < s.length; i++) {
result += Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6);
}
return result;
} catch (Exception e) {
e.printStackTrace();
}
return encryptText;
}
public static void main(String[] args) {
String inputStr = "abc";
String result = md5(inputStr);
System.out.println(result);
System.out.println(result.length());
}
}
```
代码run起来的结果
```
900150983cd24fb0d6963f7d28e17f72
32
```
其中,`Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6)`的作用 是显示一个byte型的单字节十六进制(两位十六进制表示)的编码。
byteVar & 0x000000FF的作用是如果byteVar 是负数则会清除前面24个零正的byte整型不受影响。(...) | 0xFFFFFF00的作用是如果byteVar 是正数则置前24位为一这样toHexString输出一个小于等于15的byte整型的十六进制时倒数第二位为零且不会被丢弃这样可以通过substring方法进行截取最后两位即可。

View File

@ -0,0 +1,80 @@
在日常开发中通常我们会存储配置参数信息到属性文件,这些属性文件最常见的就是键值对文件。对于配置文件来说,最常见的操作无非就这两种:读与写。以下针对这两种场景我们来做一个详细的示例。
## 1.读取properties文件中的键值对
假设我们在与src平级的路径中有一个conf文件夹里面有个province.properties的属性文件文件里的内容格式如下
```
北京市={"citycode":"010","adcode":"110100","name":"北京市","center":"116.405285,39.904989","level":"city","districts":[{"citycode":"010","adcode":"110101","name":"东城区","center":"116.418757,39.917544","level":"district","districts":[]},{"citycode":"010","adcode":"110102","name":"西城区","center":"116.366794,39.915309","level":"district","districts":[]},{"citycode":"010","adcode":"110105","name":"朝阳区","center":"116.486409,39.921489","level":"district","districts":[]},{"citycode":"010","adcode":"110106","name":"丰台区","center":"116.286968,39.863642","level":"district","districts":[]},{"citycode":"010","adcode":"110107","name":"石景山区","center":"116.195445,39.914601","level":"district","districts":[]},{"citycode":"010","adcode":"110108","name":"海淀区","center":"116.310316,39.956074","level":"district","districts":[]},{"citycode":"010","adcode":"110109","name":"门头沟区","center":"116.105381,39.937183","level":"district","districts":[]},{"citycode":"010","adcode":"110111","name":"房山区","center":"116.139157,39.735535","level":"district","districts":[]},{"citycode":"010","adcode":"110112","name":"通州区","center":"116.658603,39.902486","level":"district","districts":[]},{"citycode":"010","adcode":"110113","name":"顺义区","center":"116.653525,40.128936","level":"district","districts":[]},{"citycode":"010","adcode":"110114","name":"昌平区","center":"116.235906,40.218085","level":"district","districts":[]},{"citycode":"010","adcode":"110115","name":"大兴区","center":"116.338033,39.728908","level":"district","districts":[]},{"citycode":"010","adcode":"110116","name":"怀柔区","center":"116.637122,40.324272","level":"district","districts":[]},{"citycode":"010","adcode":"110117","name":"平谷区","center":"117.112335,40.144783","level":"district","districts":[]},{"citycode":"010","adcode":"110118","name":"密云区","center":"116.843352,40.377362","level":"district","districts":[]},{"citycode":"010","adcode":"110119","name":"延庆区","center":"115.985006,40.465325","level":"district","districts":[]}]}
```
现在我们想把里层的name解析出来代码如下
```
@Test
public void test() throws IOException {
Properties prop = new Properties();
String str = Demo1.class.getClassLoader().getResource("").getPath();
String input = str + "/conf/province.properties";
InputStream is = new BufferedInputStream(new FileInputStream(
prop.load(new InputStreamReader(is,"UTF-8"));
// 如下写法也可以:
// InputStream is = XXXClass.class.getClassLoader().getResourceAsStream("province.properties");
Set<Object> keySet = prop.keySet();
for(String key: keySet.toArray(new String[keySet.size()])) {
String line = prop.getProperty(key);
JSONObject json = JSONObject.fromObject(line);
JSONArray jsonArray = json.getJSONArray("districts");
for(int i=0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String name = jsonObject.getString("name");
System.out.println(name);
}
}
}
```
api层面的使用方式很简单使用Properties对象的getProperty方法就可以找到对应属性的值。如果没有找到相应属性则返回null。
## 2.向内存或属性文件中写入键值对
上面的例子中我们演示了如何读取键值对,接下来我们再演示一下如何写入键值对。
```
@Test
public void test() {
try {
OutputStream os = new FileOutputStream("test.properties");
Properties prop = new Properties();
prop.setProperty("name", "leilei");
prop.setProperty("age", "18");
prop.setProperty("interest", "algorithm"); //将键值对写入内存
//通过keySet遍历
Set<Object> keys = prop.keySet();
for (String key : keys.toArray(new String[0])) {
System.out.println(key + "\t" + prop.get(key));
}
//通过entrySet遍历
Set<Map.Entry<Object,Object>> entrys = prop.entrySet();
for(Map.Entry<Object,Object> entry: entrys) {
System.out.println(entry.getKey().toString() + "\t" + entry.getValue().toString());
}
//通过propertyNames遍历
Enumeration<?> e = prop.propertyNames();
while(e.hasMoreElements()) {
String key = (String) e.nextElement();
String value = prop.getProperty(key);
System.out.println(key + "\t" + value);
}
prop.store(os, "test properties " + new Date().toString()); //写入文件
} catch (Exception ex) {
ex.printStackTrace();
}
}
```
Properties类可以通过setProperty方法将键值对保存到内存中此时可以通过getProperty方法读取。如果我们想将键值对持久化到文件中可以使用store()方法将键值对写入到属性文件中。