Java基础
基础
基础中的基础
Java 为什么跨平台?
由于 JVM 的存在。因为 Java 程序编译之后的代码不是能被硬件系统直接运⾏的代码,⽽是⼀种 “ 中间码 “—字节码。然后不同的硬件平台上安装有不同的 Java 虚拟机 (JVM),由 JVM 来把字节码再 “ 翻译 “ 成所对应的硬件平台能够执⾏的代码。因此对于 Java 编程者来说,不需要考虑硬件平台是什么,所以 Java 可以跨平台。
浮点数的计算
BigDecimal类进⾏商业计算,Float和Double只能⽤来做科学计算或者是⼯程计算
/和»运算符?
Java 中的编码
字符、字符集和字符编码
- 字符 (Character) 是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
- 字符集 (Character set) 是一个系统支持的所有抽象字符的集合。通常以二维表的形式存在,二维表的内容和大小是由使用者的语言而定。如 ASCII,GBxxx,Unicode 等。
- 字符编码 (Character encoding) 是把字符集中的字符编码为特定的二进制数,以便在计算机中存储。每个字符集中的字符都对应一个唯一的二进制编码。
常见字符集
- ASCII 字符集
最初 1 个字符总是存储为 1 个字节。一个字节 (8 位) 可能有 256 个不同的可能值。但实际上只使用了前 7 位。所以只定义了 128 个字符。这个集合称为 ASCII 字符集
ASCII 是字符集还是编码?ASCII 是一个字符集。然而,在编程中,charset 和 encoding 被广泛用作同义词。如果我想引用只包含 ASCII 字符而不包含其他字符的编码 (第 8 位总是 0):那就是
US-ASCII
- Unicode
Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。
Unicode 伴随着通用字符集的标准而发展,同时也以书本的形式对外发表。Unicode 至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为 2016 年 6 月 21 日公布的 9.0.0,已经收入超过十万个字符(第十万个字符在 2005 年获采纳)。Unicode 涵盖的数据除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。
常见编码
- GB2312/GBK
GB2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。 GBK 对 GB2312-80 进行扩展, 总计拥有 23940 个码位,共收入 21886 个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号 883 个;GBK 向下完全兼容 GB2312-80 编码。支持 GB2312-80 编码不支持的部分中文姓,中文繁体,日文假名,还包括希腊字母以及俄语字母等字母。不过这种编码不支持韩国字,也是其在实际使用中与 Unicode 编码相比欠缺的部分。
- UTF-8、UTF-16、UTF-32
UTF-16用两个字节来表示 Unicode 转换格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节就是 16bit,所以叫 UTF-16。UTF-16 每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因; UTF-16 统一采用两个字节表示一个字符,有很多字符一个字节就可以了。UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度,不同类型的字符可以是由 1~3 个字节组成。 UTF-32:用固定长度的字节存储字符编码,不管 Unicode 字符编号需要几个字节,全部都用 4 个字节存储,直接存储 Unicode 编号。无需经过字符编号向字符编码的转换步骤,提高效率,用空间换时间。
为什么需要编码?
计算机存储信息的最⼩单元是⼀个字节即 8bit,所以能表示的范围是 0~255,这个范围⽆法保存所有的字符;世界上有很多语言,一个字节不能全表示出来;从 char 到 byte 就是编码,它告诉如何将字符编码成字节序列。
Java 中需要编码的场景
涉及到编码的地方一般都在字符到字节或者字节到字符的转换上
- I/O 操作中存在的编码
- 内存中操作中的编码
URL 的编解码
为什么需要 URL 编码?
对于 Url 来说,之所以要进行编码,是因为 Url 中有些字符会引起歧义,比如 &
=
?
Url 参数字符串中使用 key=value 键值对这样的形式来传参,键值对之间以&符号分隔,如/s?q=abc&ie=utf-8。如果你的 value 字符串中包含了=或者&,那么势必会造成接收 Url 的服务器解析错误,因此必须将引起歧义的&和=符号进行转义,也就是对其进行编码。
什么是 URL 编码?
URL 编码只是简单的在特殊字符的各个字节前加上 %
,这样服务端会把紧跟在 %
后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符。
例如,我们对奇异的字符
name1=va&lu=e1
进行 URL 编码后结果:name1=va%26lu%3D
为什么我们要用 ASCII 传输,可不可以用别的编码?
Url 的编码格式采用的是 ASCII 码,而不是 Unicode,这也就是说你不能在 Url 中包含任何非 ASCII 字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。Url 编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
Java 中的 char
- Java 中的 char 占用两个字节
- Java char 不存 UTF-8,而是 UTF-16
- Unicode 通用字符集占两个字节
- Unicode 是字符集,不是编码
- Java String 的 length 不等于字符数量,用
codePointCount()
可以计算出真实的字符数量
1
2
3
4
5
6
7
public static void main(String[] args) {
String B = "𝄞"; // 这个就是那个音符字符,只不过由于当前的网页没支持这种编码,所以没显示。
String C = "\uD834\uDD1E"; // 这个就是音符字符的UTF-16编码
System.out.println(C);
System.out.println(B.length()); // 2
System.out.println(B.codePointCount(0, B.length())); // 1
}
BigDecimal
什么是 BigDecimal?
Java 在 java.math 包中提供的 API 类 BigDecimal,用来对超过 16 位有效位的数进行精确的运算。双精度浮点型变量 double 可以处理16 位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。
一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用 Float 和 Double 处理,但是 Double.valueOf(String)
和 Float.valueOf(String)
会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用 BigDecimal 类来操作。
BigDecimal 所创建的是对象,故我们不能使用传统的 +
、-
、*
、/
等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是 BigDecimal 的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
使用 BigDecimal
常用构造函数
- BigDecimal(int) 创建一个具有参数所指定整数值的对象
- BigDecimal(double) 创建一个具有参数所指定双精度值的对象
- BigDecimal(long) 创建一个具有参数所指定长整数值的对象
- BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象
示例:
1
2
3
4
5
BigDecimal a =new BigDecimal(0.1);
System.out.println("a values is:"+a);
System.out.println("=====================");
BigDecimal b =new BigDecimal("0.1");
System.out.println("b values is:"+b);
输出:
1
2
3
a values is:0.1000000000000000055511151231257827021181583404541015625
=====================
b values is:0.1
原因分析:
- 参数类型为 double 的构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 newBigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于
0.1000000000000000055511151231257827021181583404541015625
。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。 - String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言, 通常建议优先使用 String 构造方法
- 当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法
BigDecimal 常用方法详解
- add(BigDecimal)
BigDecimal 对象中的值相加,返回 BigDecimal 对象 - subtract(BigDecimal)
BigDecimal 对象中的值相减,返回 BigDecimal 对象 - multiply(BigDecimal)
BigDecimal 对象中的值相乘,返回 BigDecimal 对象 - divide(BigDecimal)
BigDecimal 对象中的值相除,返回 BigDecimal 对象 - toString()
将 BigDecimal 对象中的值转换成字符串 - doubleValue()
将 BigDecimal 对象中的值转换成双精度数 - floatValue()
将 BigDecimal 对象中的值转换成单精度数 - longValue()
将 BigDecimal 对象中的值转换成长整数 - intValue()
将 BigDecimal 对象中的值转换成整数 - int compareTo()
BigDecimal 大小比较
1
2
3
4
int a = bigdemical.compareTo(bigdemical2)
// a = -1, 表示bigdemical小于bigdemical2;
// a = 0, 表示bigdemical等于bigdemical2;
// a = 1, 表示bigdemical大于bigdemical2;
BigDecimal 格式化
由于 NumberFormat 类的 format() 方法可以使用 BigDecimal 对象作为其参数,可以利用 BigDecimal 对超出 16 位有效数字的货币值,百分值,以及一般数值进行格式化控制。
- 案例 1:利用 BigDecimal 对货币和百分比格式化
首先,创建 BigDecimal 对象,进行 BigDecimal 的算术运算后,分别建立对货币和百分比格式化的引用,最后利用 BigDecimal 对象作为 format() 方法的参数,输出其格式化的货币值和百分比。
1
2
3
4
5
6
7
8
9
10
11
NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位
BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率
BigDecimal interest = loanAmount.multiply(interestRate); //相乘
System.out.println("贷款金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest));
输出:
1
贷款金额: ¥15,000.48 利率: 0.8% 利息: ¥120.00
- 案例 2:BigDecimal 格式化保留 2 为小数,不足则补 0
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
private static void test4() {
System.out.println(formatToNumber(new BigDecimal("3.435")));
System.out.println(formatToNumber(new BigDecimal(0)));
System.out.println(formatToNumber(new BigDecimal("0.00")));
System.out.println(formatToNumber(new BigDecimal("0.001")));
System.out.println(formatToNumber(new BigDecimal("0.006")));
System.out.println(formatToNumber(new BigDecimal("0.206")));
// 3.44
// 0.00
// 0.00
// 0.00
// 0.01
// 0.21
}
/**
* 1. 0~1之间的BigDecimal小数,格式化后失去前面的0,则前面直接加上0。
*
* 2. 传入的参数等于0,则直接返回字符串"0.00"
*
* 3. 大于1的小数,直接格式化返回字符串
*
* @param obj 传入的小数
*/
public static String formatToNumber(BigDecimal obj) {
DecimalFormat df = new DecimalFormat("#.00");
if (obj.compareTo(BigDecimal.ZERO) == 0) {
return "0.00";
} else if (obj.compareTo(BigDecimal.ZERO) > 0 && obj.compareTo(new BigDecimal(1)) < 0) {
return "0" + df.format(obj).toString();
} else {
return df.format(obj).toString();
}
}
BigDecimal 常见异常
除法的时候出现异常
1
2
3
4
5
6
7
8
9
10
private static void test5() {
BigDecimal three = new BigDecimal("3");
BigDecimal one = new BigDecimal("1");
System.out.println("one=" + one);
// BigDecimal divide = one.divide(three); // Non-terminating decimal expansion; no exact representable decimal result.
// System.out.println("1/3=" + divide);
BigDecimal divide = one.divide(three, 3, RoundingMode.CEILING);// 0.33
System.out.println("1/3=" + divide);
}
- 异常:
1
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
- 原因分析:
通过 BigDecimal 的 divide 方法进行除法时当不整除,出现无限循环小数时,就会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
. - 解决方法:
1
2
// divide方法设置精确的小数点:
BigDecimal divide = one.divide(three, 3, RoundingMode.CEILING);// 0.33
BigDecimal 工具类
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/**
* 用于高精确处理常用的数学运算
*/
public class ArithmeticUtils {
// 默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @param scale 保留scale 位小数
* @return 两个参数的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @param scale 保留scale 位小数
* @return 两个参数的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数 BigDecimal
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较大小
*
* @param v1 被比较数
* @param v2 比较数
* @return 如果v1 大于v2 则 返回true 否则false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.compareTo(b2) > 0;
}
}
BigDecimal 总结
- 在需要精确的小数计算时再使用 BigDecimal,BigDecimal 的性能比 double 和 float 差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用 BigDecimal。尽量使用参数类型为 String 的构造函数;精度低的用 Double/Float 等,性能好点
- BigDecimal 都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
Ref
DecimalFormat
double 类型如果小数点后为零显示整数否则保留
1
java.lang.String.valueOf(DecimalFormat("###.##").format(float))
保留 2 位小数,如果小数位 0,不显示
案例:
1
2
3
4
DecimalFormat df = new DecimalFormat("###.####");
float f = 20.0f;
System.out.println("你不想要的:" + f);
System.out.println("你想要的答案:" + df.format(f));
float 格式化成 m/k 显示
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
fun formatCoin(coin: Float): String {
val mm = 1000 * 1000
val kk = 1000
var temp = coin
val m = temp / mm
temp %= mm
println("m=$m,temp=$temp")
val k = temp / kk
temp %= kk
println("k=$k,temp=$temp")
val mod = temp
println("mod=$mod")
var format = ""
if (m >= 1) {
format += m.toInt().toString() + "m"
}
if (k >= 1) {
format += k.toInt().toString() + "k"
}
if (mod >= 1) {
format += java.lang.String.valueOf(DecimalFormat("###.##").format(mod))
}
println("format=$format")
return format
}
结果:
1
2
3
4
5
6
fun main() {
val coin = 4434433.35
println(formatCoin(coin))
}
4.43m4433.35k