十一、 常用类
11.1 字符串String相关的类
11.1.1 String的特性
String: 字符串,使用一对 " " 引起来表示。
- String声明为
final
的,不可被继承。 - String实现了
Serializable
接口:表示字符串是支持序列化的。 - 实现了
Comparable
接口:表示String可以比较大小。 - String内部定义了
final char[] value
用于存储字符串数据。 - String:代表不可变的字符序列。简称:不可变性。
- 体现:
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的
value
进行赋值。 - 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的
value
进行赋值。 - 当调用String的
replace()
方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value
进行赋值。 - 通过字面量的方式(区别于
new
)给一个字符串赋值,此时的字符串值声明在字符串常量池中。 - 字符串常量池中是不会存储相同内容的字符串的。
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的
2. String 的不可变性理解
例子1:
@Test
public void test1() {
String s1 = "abc";//字面量
String s2 = "abc";
System.out.println(s1 == s2);
}
输出:
true
- true说明,s1和s2的地址值相同。指向内存空间中相同的地址。说明String不会为相同的
value
开辟第二块内存空间。而是引用相同的地址值。
例子2:
@Test
public void test1() {
String s1 = "abc";//字面量
String s2 = "abc";
s1 = "Hello";
}
- 其内存解析图如下:
- 由于String中
value
被定义为final
的,因此,如果对s1修改的话,不能直接在原有的value
上修改,而必须在新的内存空间中开辟新的value
地址存放修改后的字符串。
例子3:
@Test
public void test1() {
String s2 = "abc";
String s3 = "abc";
System.out.println(s3 == s2);
s3 += "def";
System.out.println(s3 == s2);
System.out.println(s2);
System.out.println(s3);
}
输出:
true
false
abc
abcdef
- 如s3,对字符串进行连接操作时,由于
value
被定义为final
的,因此也必须在内存空间中开辟新地址重新赋值。
例子4:
//replace()测试
String s4 = "abc";
System.out.println(s4);
String s5 = s4.replace("a", "m");
System.out.println(s5);
System.out.println(s4 == s5);
输出:
abc
mbc
false
- 上面的从 “abc” 改写为 “Hello” 还可以解释为
char
型数组长度无法改变,或者从fanal
的角度解释。但这里的replace()
方法把 “a” 替换成 “m”,整个字符串长度没变,但内存空间还是额外新建了一个地址重新赋值。体现了String
类的不可变性。
11.1.2 String类的两种实例化方式
-
String有两种方式实例化:
方式一:通过字面量定义的方式 方式二:通过 new
+ 构造器的方式 -
通过方式1实例化的String,其
value
存在内存的方法区的常量池中,再把常量池该value
的首地址赋给栈中的str1
。如下图中的str1
。 -
通过方式2实例化的String,先在堆中新建一个对象
value
。然后再把方法区常量池中的char
型数组的首地址赋给堆中的value
,堆中value
的首地址再赋给栈中的str2
。
例子1:
@Test
public void test2() {
String s1 = "javaEE";
String s2 = "javaEE";
System.out.println(s1 == s2);
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s3 == s4);
}
输出:
true
false
内存解析:
例子2:Person() 类中的 String属性 name
Person p1 = new Person("Tom", 12);
Person p2 = new Person("Tom", 21);
System.out.println(p1.name.equals(p2.name));
System.out.println(p1.name == p2.name);
输出:
true
true
- 上面
p1
p2
中的 String类属性name
都是以字面量方式实例化的。"Tom"
的值存在方法区的常量池中,再把其首地址值赋给堆中的name
。因此,p1.name
和p2.name
的地址值是相同的。
内存解析:
11.1.3 String拼接操作的对比
@Test
public void test3() {
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
System.out.println(s3 == s7);
System.out.println(s5 == s6);
System.out.println(s5 == s7);
System.out.println(s6 == s7);
String s8 = s5.intern();//返回值得到的s8是常量池中已经存在的"javaEEhadoop"
System.out.println(s3 == s8);
}
输出:
true
false
false
false
false
false
false
true
结论:
-
常量与常量的拼接结果在常量池 。且常量池中不会存在相同内容的常量 。
例子:
@Test public void test3() { String s1 = "javaEEhadoop"; String s2 = "javaEE"; String s3 = s2 + "hadoop"; System.out.println(s1 == s3); final String s4 = "javaEE"; String s5 = s4 + "hadoop"; System.out.println(s1 == s5); }
输出:
false true
- 因为 s4 加了
final
修饰,因此变成了常量。常量和常量拼接是在常量池的。
- 因为 s4 加了
-
只要其中有一个是变量,结果就在堆中。相当于以
new
方式实例化了。 -
如果拼接的结果调用
intern()
方法,返回值就在常量池中。
11.1.4 String练习
题目:
public class StringTest {
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.println(ex.str);
System.out.println(ex.ch);
}
}
输出:
good
best
- 这题我做错了。把
good
觉得是test ok
。 - 这题考的是形参的值传递机制和 String 的不可变性。
change(String str, char ch[])
方法中,传递给形参String str
的是属性str
的地址值,但属性str
的地址值所指向的是堆空间的value
,而堆空间的value
的地址值指向常量池中的 “good” 。change(String str, char ch[])
方法要改变形参String str
的值,由于String的不可变性,只能在内存空间中另外开辟一个空间存放"test ok"。
11.1.5 String的常用方法
1. String常用方法1
String方法 | 作用 |
---|---|
int length() | 返回字符串的长度 |
char charAt(int index) | 返回某索引处的字符 |
boolean isEmpty() | 判断是否是空字符串 |
String toLowerCase() | 所有字符串转换为小写 |
String toUpperCase() | 所有字符串转换为大写 |
String trim() | 返回字符串的副本,忽略前导空白和尾部空白 |
boolean equals(Object obj) | 比较字符串的内容是否相等 |
boolean equalsIgnoreCase(String anotherString) | 与equals()方法类似,但忽略大小写 |
String concat(String str) | 将指定字符串连接到此字符串的结尾。等价于"+" |
int compareTo(String anotherString) | 比较两个字符串的大小 |
String substring(int beginIndex) | 返回一个新的字符串。它是此字符串的从 beginIndex开始截取到最后的一个子字符串 |
String substring(int beginIndex, int endIndex) | 返回一个新的字符串。它是此字符串的从 beginIndex开始截取到endIndex(不包含)的一个子字符串 |
例子:
@Test
public void test4() {
String s1 = "HelloWorld";
System.out.println(s1.length());
System.out.println(s1.charAt(8));
System.out.println(s1.isEmpty());
System.out.println(s1.toLowerCase());
System.out.println(s1.toUpperCase());
System.out.println(s1.trim());
System.out.println(s1.equals("helloworld"));
System.out.println(s1.equalsIgnoreCase("helloworld"));
System.out.println(s1.concat("Java"));
System.out.println(s1.compareTo("HelloWorldJava"));
System.out.println(s1.substring(3));
System.out.println(s1.substring(5,10));
}
输出:
10
l
false
helloworld
HELLOWORLD
HelloWorld
false
true
HelloWorldJava
-4
loWorld
World
- 体会:其中
trim()
方法可以在用户输入用户名或者密码,不小心在前面和后面敲了空格的时候可以去除。 - 其中,
compareTo()
方法是逐一比较两个字符串的字符的 ASCII码值的大小。如 “abc”.compareTo(“abe”); 的输出结果就是 − 2 -2 −2。因为字符 ‘c’ 的ASCII码值为 99 99 99 ,而字符 ‘e’ 的ASCII码值为 101 101 101 。所以 99 − 101 = − 2 99-101=-2 99−101=−2 。此方法可以用在手机联系人按姓名拼音首字母排序。
2. String常用方法2:前后缀
String方法 | 作用 |
---|---|
boolean endsWith(String suffix) | 判断此字符串是否以指定的后缀结束 |
boolean startsWith(String prefix) | 判断此字符串是否以指定的前缀开始 |
boolean startsWith(String prefix, int toffset) | 判断此字符串从指定索引开始的子字符串是否以指定前缀开始 |
例子:
@Test
public void test5() {
String s1 = "OceanUniversityOfChina";
System.out.println(s1.endsWith("na"));
System.out.println(s1.startsWith("oce"));
System.out.println(s1.startsWith("Univ", 5));
}
输出:
true
false
true
3. String常用方法3:查找
String方法 | 作用 |
---|---|
boolean contains(CharSequence s) | 当且仅当此字符串包含指定的char值序列时,返回 true |
int indexOf(String str) | 返回指定子字符串在此字符串中第一次出现处的索引 |
int indexOf(String str, int fromIndex) | 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始 |
int lastIndexOf(String str) | 返回指定子字符串在此字符串中最右边出现处的索引 |
int lastIndexOf(String str, int fromIndex) | 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索 |
注:indexOf和lastIndexOf方法如果未找到都是返回 − 1 -1 −1 。
例子:
@Test
public void test6() {
String s1 = "OceanUniversityOfChina";
String s2 = "ver";
System.out.println(s1.contains("anUni"));
System.out.println(s1.contains(s2));
System.out.println(s1.indexOf("ea"));
System.out.println(s1.indexOf("e", 5));
System.out.println(s1.lastIndexOf("na"));
System.out.println(s1.lastIndexOf("n", 19));
}
输出:
true
true
2
9
20
6
- 当str只有一个或没有时,
indexOf()
和lastIndexOf()
返回的值相同。
4. String常用方法4:替换
String方法 | 作用 |
---|---|
String replace(char oldChar, char newChar) | 返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的 |
String replace(charSqeuence target, CharSequence replacement) | 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串 |
String replaceAll(String regex, String replacement) | 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串 |
String replaceFirst(String regex, String replacement) | 使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串 |
例子1:
@Test
public void test7() {
String s1 = "OceanUniversityOfChina,SouthChinaUniversityOfTechnology";
System.out.println(s1.replace('n', 'i'));
System.out.println(s1.replace("China", "Qingdao"));
System.out.println(s1.replaceAll("University", "College"));
System.out.println(s1.replaceFirst("University", "College"));
}
输出:
OceaiUiiversityOfChiia,SouthChiiaUiiversityOfTechiology
OceanUniversityOfQingdao,SouthQingdaoUniversityOfTechnology
OceanCollegeOfChina,SouthChinaCollegeOfTechnology
OceanCollegeOfChina,SouthChinaUniversityOfTechnology
例子2:
@Test
public void test7_1() {
String s1 = "3123Ocean439University785Of032China";
String s2 = s1.replaceAll("\\d+", ", ").replaceAll("^,|,$", "");
System.out.println(s2);
}
输出:
Ocean, University, Of, China
- 其中,
regex
是正则表达式的意思。上述代码中,"\\d+", ", "
的意思是把此字符串中多个数字都替换成", "
。然后再把开头或者结尾是", "
的替换成""
。 "^, | , $"
中,^
是开头的意思,|
是或者的意思,$
是结尾的意思。
例子3:
@Test
public void test7_2() {
//判断s1字符串中是否全部由数字组成,即有1~n个数字组成
String s1 = "07582959956";
boolean b1 = s1.matches("\\d+");
System.out.println(b1);
//判断这是否是一个肇庆的固定电话
String tel = "0758-2959956";
boolean b2 = tel.matches("0758-\\d{7,8}");
System.out.println(b2);
}
输出:
true
true
- 其中,正则表达式regex
"0758-\\d{7, 8}"
表示0758-后面是否还跟着7位或8位的数字。 - 这些正则表达式
regex
可应用于网页用户输入账户密码邮箱等登录信息的规范化。
5. String常用方法5:匹配
String方法 | 作用 |
---|---|
boolean matches(String regex) | 判断此字符串是否匹配给定的正则表达式 |
例子:
@Test
public void test8() {
String s1 = "Ocean";
System.out.println(s1.matches("Ocean"));
System.out.println(s1.matches("Ocen"));
}
输出:
true
false
6. String常用方法6:切片
String方法 | 作用 |
---|---|
String[] split(String regex) | 根据给定正则表达式的匹配拆分此字符串 |
String[] split(String regex, int limit) | 根据给定正则表达式的匹配拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中 |
例子1:
@Test
public void test9() {
String s1 = "OceanUniversityOfChina,SouthChinaUniversityOfTechnology";
String[] s1s = s1.split("Of");
System.out.println(s1s.length);
for (int i = 0; i < s1s.length; i++) {
System.out.println(s1s[i]);
}
String[] s2s = s1.split("China", 2);
System.out.println(s2s.length);
for (int i = 0; i < s2s.length; i++) {
System.out.println(s2s[i]);
}
}
输出:
3
OceanUniversity
China,SouthChinaUniversity
Technology
2
OceanUniversityOf
,SouthChinaUniversityOfTechnology
例子2:
@Test
public void test9_1() {
String s1 = "Ocean|University|Of|China|South|China|University|Of|Technology";
String[] s1s = s1.split("\\|");
System.out.println(s1s.length);
for (int i = 0; i < s1s.length; i++) {
System.out.println(s1s[i]);
}
String s2 = s1.replaceAll("\\|", "\\.");
System.out.println(s2);
String[] s2s = s2.split("\\.");
System.out.println(s2s.length);
for (int i = 0; i < s2s.length; i++) {
System.out.println(s2s[i]);
}
}
输出:
9
Ocean
University
Of
China
South
China
University
Of
Technology
Ocean.University.Of.China.South.China.University.Of.Technology
9
Ocean
University
Of
China
South
China
University
Of
Technology
11.1.6 String与各种类型之间的转换
1. String与基本数据类型、包装类之间的转换
转换情形 | 方法 |
---|---|
String–>基本数据类型、包装类 | 包装类.praseXxx(String str) |
基本数据类型、包装类–>String | String.valueOf(xxx) |
2. String与char[]之间的转换
转换情形 | 方法 |
---|---|
String–>char[] | str.toCharArray() |
char[]–>String | new String(char[]) |
例子:
@Test
public void test1() {
//字符串-->char[]
String s1 = "OUCer";
char[] chars = s1.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i]);
}
//char[]-->字符串
char[] chars1 = new char[]{'O', 'U', 'C', 'e', 'r'};
String s2 = new String(chars1);
System.out.println(s2);
}
输出:
O
U
C
e
r
OUCer
- 注意,这里直接
System.out.println(chars);
不用遍历也可以输出 char[] 中的内容,而不是chars
的地址。
3. String与byte[]之间的转换
- 这就是字符的编码和解码。从 String 转变到
byte[]
是对字符的编码 (从人看得懂的字符转变到看不懂的编码);从byte[]
转变到 String 是对字符的解码 (从人看不到的编码变为看得懂的字符)。 - 在Java中一般有两套字符集:
UTF-8
和gbk
。其中gbk
是专门针对中文编码的,用 2 2 2 个编码表示。 - 在下面转换中,如果不指定字符集,IDEA默认使用我设置的
UTF-8
字符集。必须指定gbk
字符集,才会使用gbk
字符集编码解码。
转换情形 | 方法 |
---|---|
String --> byte[] | str.getBytes() 或 str.getBytes(“gbk”) |
byte[] --> String | String(byte[]) 或 String(byte[], “gbk”) |
例子:
@Test
public void test5() throws UnsupportedEncodingException {
String s1 = "abc123中国";
//String --> byte[]
byte[] bytes = s1.getBytes();//使用默认UTF-8字符集编码
System.out.println(Arrays.toString(bytes));
byte[] gbks = s1.getBytes("gbk");//使用gbk字符集编码
System.out.println(Arrays.toString(gbks));
//byte[] --> String
String s2 = new String(bytes);//使用默认UTF-8字符集解码
System.out.println(s2);
String s3 = new String(gbks, "gbk");//使用gbk字符集解码
System.out.println(s3);
}
输出:
[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
[97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
abc123中国
abc123中国
-
使用
gbks
的byte[]
解码时,如果不特意声明字符集,就会使用默认字符集UTF-8
,解码后就会出现乱码:String s3 = new String(gbks);//使用gbk字符集解码 System.out.println(s3);
输出:
abc123�й�
- 所以,编码和解码必须使用同一种字符集。否则会出现乱码。
-
String() 构造器中可以直接放入
bytes[]
,完成解码:
4. String与StringBuffer、StringBuilder之间的转换
转换情形 | 方法 |
---|---|
String–>StringBuffer、StringBuilder | 调用StringBuffer、StringBuilder构造器:StringBuffer(String str) |
StringBuffer、StringBuilder–>String | ① 调用String构造器;②调用StringBuffer、StringBuilder的toString() 方法 |
11.1.7 可变字符序列StringBuffer、StringBuilder
1. 含义
- String:从JDK1.0开始就有,是不可变的字符序列。
- StringBuffer:也是从JDK1.0就有,是可变的字符序列。
- StringBuilder:从JDK5.0新增的可变字符序列。新增原因是解决StringBuffer因为线程安全
synchronized
同步方法导致的效率低的问题。虽然效率高,但是线程不安全。 - 在开发时,如果该字符串作为共享数据,会产生线程安全问题时,要使用StringBuffer。如果不涉及到多线程问题,则推荐使用StringBuilder提高效率。
相同点 | 不同点 |
---|---|
三者底层都是用char[]型数组存储的 | String的char[]数组被声明为final的,不可变。而另外两个没有被声明为final,因此可变。 |
2. 使用说明
可变性:
-
StringBuffer和StringBuilder在进行替换、切片、查找、增加等字符串操作时,均不会另外返回一个新的String,而会在原字符串的基础上进行修改操作。
public void test1() { StringBuffer sb1 = new StringBuffer("OUCer"); sb1.setCharAt(2, 'G');//把索引为2的字符替换成'G' System.out.println(sb1); }
输出:
OUGer
3. StringBuffer源码解析
-
探究一下StringBuffer可变性在底层源码的体现:
StringBuffer sb1 = new StringBuffer();
新建了一个StringBuffer对象
sb1
,是空参。去看底层源码:char[] value = new char[16];
发现是底层创建了一个长度是16的char[]数组。往上面添加字符时,底层源码为:
sb1.append('a'); //value[0] = 'a'; sb1.append('b'); //value[1] = 'b';
-
当创建的StringBuffer不是空参时:
StringBuffer sb2 = new StringBuffer("abc");
底层源码为:在当前字符串长度的基础上再加16个长度创建char[]数组。
char[] value = new char["abc".length() + 16];
-
但这样会带来 2 2 2 个问题:
- 问题1:空参时,如果我调用
sb1.length()
,返回的到底是 0 0 0 还是 16 16 16 呢?答案是 0 0 0 ,因为底层源码已经重写了length()
方法,返回已经存入的字符数。 - 问题2:如果要添加的字符长度超过16个怎么办?答案是:会扩容底层数组。默认情况下,每次扩容为原来的2倍+2,同时将原来数组中的元素复制到新的扩容数组当中。
- 问题1:空参时,如果我调用
-
指导意义:在开发中为了避免扩容造成的效率低问题,应该在创建时就提前声明好StringBuffer的长度 (capacity):
StringBuffer(int capacity);
StringBuilder(int capacity);
4. StringBuffer常用方法
增删改:
方法 | 作用 |
---|---|
StringBuffer append(xxx) | 进行字符串拼接 |
StringBuffer delete(int start, int end) | 删除指定位置的内容 |
StringBuffer replace(int start, int end, String str) | 把[start, end)位置替换成str |
StringBuffer insert(int offset, xxx) | 在指定位置插入xxx |
StringBuffer reverse() | 把当前字符序列逆转 |
- 注:StringBuilder只是线程安全区别,其他方法与StringBuffer完全一致。因此不再赘述。
例子:
@Test
public void test2() {
StringBuffer s1 = new StringBuffer("Ocean");
//append
s1.append(1);
s1.append("University");
System.out.println(s1);
//delete
s1.delete(5, 6);
System.out.println(s1);
//replace
s1.replace(0, 5, "Technology");
System.out.println(s1);
//insert
s1.insert(10, "SouthChina");//要从结束后一位的索引开始
System.out.println(s1);
//reverse
s1.reverse();
System.out.println(s1);
}
输出:
Ocean1University
OceanUniversity
TechnologyUniversity
TechnologySouthChinaUniversity
ytisrevinUanihChtuoSygolonhceT
String中有的方法:
方法 | 作用 |
---|---|
int indexOf(String str) | 返回指定子字符串在此字符串中第一次出现处的索引 |
String substring(int start, int end) | 返回一个新的字符串。它是此字符串的从 beginIndex开始截取到endIndex(不包含)的一个子字符串 |
int length() | 返回字符串的长度 |
char charAt(int index) | 返回某索引处的字符 |
void setCharAt(int n, char ch) | 把索引n的字符设置为ch |
-
注意:substring() 是有返回值的,不会在原有StringBuffer上修改。
//substring String s2 = s1.substring(0, 10); System.out.println(s1); System.out.println(s2);
输出:
TechnologySouthChinaUniversity Technology
5. String、StringBuffer和StringBuilder的效率对比
@Test
public void test3() {
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
//StringBuffer执行时间计算
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));//int转变成String
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer执行时间:" + (endTime - startTime));
//StringBuilder执行时间计算
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));//int转变成String
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder执行时间:" + (endTime - startTime));
//String执行时间计算
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
// text += i;
}
endTime = System.currentTimeMillis();
System.out.println("String执行时间:" + (endTime - startTime));
}
输出:
StringBuffer执行时间:4
StringBuilder执行时间:2
String执行时间:1120
- String因为不可变需要不断地新建String,因此效率最慢。
- StringBuilder因为不是线程安全的,可以多线程并行,因此效率最快。
- String的执行时间是StringBuilder的560倍。StringBuilder的效率是StringBuffer的2倍。
6. null的赋值注意事项
@Test
public void test4() {
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());
System.out.println(sb);
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb1);
}
输出:
4
null
java.lang.NullPointerException
-
上述代码如果没有看过源码就会进坑。手动点进去看源码或者使用debug功能看源码,就能发现 StringBuffer的
append()
方法在遇到null
时的底层源码是这样的,因此不会报空指针的异常。 -
而如果直接把
null
赋值给 StringBuffer 构造器就会出现空指针异常。
11.1.8 String常见算法题
题目1:指定反转字符串
将一个字符串进行反转。将字符串中指定部分进行反转 。比如 “abcdefg” 反转为 “abfedcg”。
我的首次答案:
public class StringDemo {
/**
* 将一个字符串进行反转。将字符串中指定部分进行反转 。
* 比如 "ab cdef g" 反转为 "ab fedc g"。
*/
public static String reverse(int start, int end, String str) {
StringBuilder builder = new StringBuilder(str);
StringBuilder substring = new StringBuilder(builder.substring(start, end));
StringBuilder reverse = substring.reverse();
StringBuilder replace = builder.replace(start, end, new String(reverse));
return replace.toString();
}
@Test
public void test1() {
String s1 = "abcdefg";
long start = System.currentTimeMillis();
int i = 500000;
String s2 = null;
while (i > 0) {
s2 = reverse(2, 5, s1);
i--;
}
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
System.out.println(s1);
System.out.println(s2);
}
输出:
运行时间:68
abcdefg
abfedcg
老师的方法
方法一:转换成char[]数组,双指针循环交换
//方法2:转换成char[]数组,双指针循环交换
public String reverse1(int start, int end, String str) {
char[] arr = str.toCharArray();
for (int i = start, j = end; i < j; i++, j--) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return new String(arr);
}
@Test
public void test() {
String s1 = "abcdefg";
long start = System.currentTimeMillis();
int i = 500000;
String s2 = null;
while (i > 0) {
s2 = reverse1(2, 5, s1);
i--;
}
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
System.out.println(s1);
System.out.println(s2);
}
输出:
运行时间:29
abcdefg
abfedcg
方法二:使用String三部分切割法,再拼接
//方法3:使用String三部分切割法,再拼接
public String reverse2(int start, int end, String str) {
//第一部分:0-start
String reverse = str.substring(0, start);
//第二部分:end~start
for (int i = end; i >= start; i--) {
reverse += str.charAt(i);
}
//第三部分:end~末尾
reverse += str.substring(end + 1);
return reverse;
}
@Test
public void test() {
String s1 = "abcdefg";
long start = System.currentTimeMillis();
int i = 500000;
String s2 = null;
while (i > 0) {
s2 = reverse2(2, 5, s1);
i--;
}
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
System.out.println(s1);
System.out.println(s2);
}
输出:
运行时间:122
abcdefg
abfedcg
方法3:使用StringBuilder三部分切割法,再拼接
//使用StringBuilder三部分切割法,再拼接
public String reverse4(int start, int end, String str) {
StringBuilder builder = new StringBuilder(str.length());
//第一部分:0-start
builder.append(str.substring(0, start));
//第二部分:end~start
for (int i = end; i >= start; i--) {
builder.append(str.charAt(i));
}
//第三部分:end~末尾
builder.append(str.substring(end + 1));
return builder.toString();
}
@Test
public void test() {
String s1 = "abcdefg";
long start = System.currentTimeMillis();
int i = 500000;
String s2 = null;
while (i > 0) {
s2 = reverse4(2, 5, s1);
i--;
}
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
System.out.println(s1);
System.out.println(s2);
}
输出:
运行时间:63
abcdefg
abfedcg
- 从上面可以看出,老师的三种方法中,效率最高的是方法一:使用char[]数组的双指针循环交换。感觉越靠近底层,算法的执行效率越高。
- 老师的三种方法效率从高到底排序:方法一>方法三>方法二。
题目2:获取字符串出现次数
获取一个字符串在另一个字符串中出现的次数。
比如:获取 “ab” 在 “abkkcadkabkebfkabkskab” 中出现的次数。
我的首次答案:
public int num(String origin, String target) {
int total = 0;
int count = 0;
int i = 0;
char[] orArr = origin.toCharArray();
char[] tgArr = target.toCharArray();
for (int j = 0; j < orArr.length - 1; j++) {
for (int k = 0; k < tgArr.length; k++) {
if (orArr[j + i] != tgArr[k]) {
break;
}
count++;
i++;
}
if (count == tgArr.length) {
total++;
}
count = 0;
i = 0;
}
return total;
}
@Test
public void test() {
String origin = "abkkcadkabkebfkabkskab";
String target = "ab";
int num = 0;
int i = 500000;
long start = System.currentTimeMillis();
while (i > 0) {
num = num(origin, target);
i--;
}
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start));
System.out.println(num);
}
输出:
执行时间:45
4
老师的方法
方法一:String的IndexOf()方法
//方法一:String的IndexOf()方法
public int getCount1(String mainStr, String subStr) {
int mainLength = mainStr.length();
int subLength = subStr.length();
int count = 0;
int index = 0;
if (mainLength > subLength) {
while ((index = mainStr.indexOf(subStr)) != -1) {
count++;
mainStr = mainStr.substring(index + subLength);
}
return count;
} else {
return 0;
}
}
@Test
public void test() {
String origin = "abkkcadkabkebfkabkskab";
String target = "ab";
int num = 0;
int i = 500000;
long start = System.currentTimeMillis();
while (i > 0) {
num = getCount1(origin, target);
i--;
}
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start));
System.out.println(num);
}
输出:
执行时间:63
4
-
此方法在
mainStr = mainStr.substring(index + subLength);
处会不断地新建String字符串,导致效率变慢。因此考虑一种不新建String字符串的优化方法:
方法二:String的IndexOf(String str, int fromIndex)方法
//方法二:String的IndexOf(String str, int fromIndex)方法
public int getCount2(String mainStr, String subStr) {
int mainLength = mainStr.length();
int subLength = subStr.length();
int count = 0;
int index = 0;
if (mainLength > subLength) {
while ((index = mainStr.indexOf(subStr, index)) != -1) {
count++;
index += subLength;
}
return count;
} else {
return 0;
}
}
@Test
public void test() {
String origin = "abkkcadkabkebfkabkskab";
String target = "ab";
int num = 0;
int i = 500000;
long start = System.currentTimeMillis();
while (i > 0) {
num = getCount2(origin, target);
i--;
}
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start));
System.out.println(num);
}
输出:
执行时间:30
4
- 方法二的效率最高。
题目3:获取两个字符串中最大相同子串
比如:
str1 = “abcwerthelloyuiodef”; str2 = “cvhellobnm”
提示:将短的那个串进行长度依次递减的子串与较长的串比较。
我的首次答案:
public String getMaxSameString(String str1, String str2) {
//如果两个字符串都一样,直接输出其中一个
if (str1.equals(str2)) {
return str1;
}
//初始化属性
String maxSameString = null;//最大相同子串
int maxLength = 0;//存放当前最大子串长度
int index = 0;
//获取字符串长度
int str1Length = str1.length();
int str2Length = str2.length();
//固定str1为长串
if (str1Length < str2Length) {
String temp = str1;
str1 = str2;
str2 = temp;
}
//对短串的递减匹配
StringBuilder str2Bd1 = new StringBuilder(str2);
for (int j = 0; j < str2Length; j++) {
StringBuilder str2Bd = new StringBuilder(str2Bd1.toString());
//内层递减
for (int i = 0; i < str2Bd1.length(); i++) {
index = str1.indexOf(str2Bd.toString());
if (index == -1) {
str2Bd.delete(str2Bd1.length() - 1 - i, str2Length);
} else {
if (str2Bd.length() > maxLength) {
maxSameString = str2Bd.toString();
maxLength = str2Bd.length();
}
}
}
//外层递减
str2Bd1.delete(0, 1);
}
return maxSameString;
}
@Test
public void test() {
String str1 = "abcwerthelloyuiodef", str2 = "cvhellobnm";
int i = 500000;
String maxSameString = null;
long start = System.currentTimeMillis();
while (i > 0) {
maxSameString = getMaxSameString(str1, str2);
i--;
}
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start));
System.out.println(maxSameString);
}
输出:
执行时间:720
hello
- 这道题对于首次解除算法的我来说真的好难,写了差不多一个下午。基本思路就是把短串变为 StringBuilder
str2Bd1
,外循环是递减短串第一个字符,内循环递减短串末尾字符str2Bd
,不断 indexOf()去查询是否有匹配的,并依据当前最大子串长度maxLength
判断是否更新最大相同子串maxSameString
,最后返回maxSameString
。 - 这个答案的效率明显非常低,时间复杂度为 o ( n 2 ) o(n^2) o(n2) 。但这是我独立思考得出来的结果,还是非常有成就感的。
老师的方法
外循环递减字符个数,内循环遍历减去固定字符个数的所有可能,然后再去contains()。
我按老师思路自己写的:
//外循环递减字符个数,内循环遍历减去固定字符个数的所有可能,然后再去contains()
public String getMaxSameString1(String str1, String str2) {
//如果两个字符串都一样,直接输出其中一个
if (str1.equals(str2)) {
return str1;
}
//初始化属性
String maxSameString = null;//最大相同子串
//确定长、短串
String maxString = str1.length() >= str2.length() ? str1 : str2;
String minString = str1.length() < str2.length() ? str1 : str2;
//获取字符串长度
int maxStringLength = maxString.length();
int minStringLength = minString.length();
//外循环递减字符个数
for (int i = 0; i < minStringLength; i++) {
for (int j = 0; j <= i; j++) {
String substring = minString.substring(j, minStringLength - i + j);
if (maxString.contains(substring)) {
maxSameString = substring;
break;
}
}
//如果最大相同子串里不是空的,就跳出循环了
if (maxSameString != null) {
break;
}
}
return maxSameString;
}
@Test
public void test() {
String str1 = "abcwerthelloyuiodef", str2 = "cvhellobnm";
int i = 500000;
String maxSameString = null;
long start = System.currentTimeMillis();
while (i > 0) {
maxSameString = getMaxSameString1(str1, str2);
i--;
}
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start) + "ms");
System.out.println(maxSameString);
}
输出:
执行时间:207ms
hello
老师亲手写的:
public String getMaxSameString2(String str1, String str2) {
//如果两个字符串都一样,直接输出其中一个
if (str1 == null || str2 == null) {
return null;
}
if (str1.equals(str2)) {
return str1;
}
//初始化属性
String maxSameString = null;//最大相同子串
//确定长、短串
String maxString = str1.length() >= str2.length() ? str1 : str2;
String minString = str1.length() < str2.length() ? str1 : str2;
//获取字符串长度
int minStringLength = minString.length();
//外循环递减字符个数
for (int i = 0; i < minStringLength; i++) {
for (int x = 0, y = minStringLength - i; y <= minStringLength; x++, y++) {
String substring = minString.substring(x, y);
if (maxString.contains(substring)) {
maxSameString = substring;
break;
}
}
//如果最大相同子串里不是空的,就跳出循环了
if (maxSameString != null) {
break;
}
}
return maxSameString;
}
输出:
执行时间:210ms
hello
- 老师的这种方法使得子串从大到小匹配,一旦匹配成功就
break
循环,返回最大相同字串。 - 老师的方法的执行时间只有我的方法的 30 30 30%, 能有效地提高效率。
11.2 JDK8之前的日期时间API
11.2.1 System类中的时间API
public static long currentTimeMillis()
返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
- 此方法适用于计算时间差。比如在一段程序的执行前和执行后分别获取
currentTimeMillis()
,然后作差,就可以获得这段代码的执行时间。
11.2.2 java中两个Date类的使用
-
Java下有两个Date类:① java.util.Date类;② java.sql.Date类
-
其中, java.sql.Date类是 java.util.Date类的子类。
-
需要掌握2个构造器和2个方法的使用。
1. 两个构造器
构造器 | 作用 |
---|---|
Date() | 空参构造器:创建当前时间的Date对象 |
Date(long millis) | 创建指定毫秒数的Date对象 |
例子:
@Test
public void test1(){
//构造器一:Date():创建当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());
//构造器二:Date(long millis)创建指定毫秒数的Date对象
Date date2 = new Date(199811041517L);
System.out.println(date2);
}
输出:
Tue Mar 22 11:05:13 CST 2022
Sat May 01 23:04:01 CST 1976
- 可以看到,创建的Date对象无论是否使用
toString()
方法,都可以输出 星期、月、日、时、分、秒、年。
2. 两个方法
方法 | 作用 |
---|---|
toString() | 显示当前Date对象的 星期、月、日、时、分、秒、年 |
getTime() | 获取当前Date对象对应的毫秒数 |
例子:
Date date1 = new Date();
System.out.println(date1.toString());
System.out.println(date1.getTime());
输出:
Tue Mar 22 11:43:43 CST 2022
1647920623302
3. java.sql.Date类
-
是对应着SQL数据库中的日期类型变量。
-
创建java.sql.Date类的对象:
//java.sql.Date类 java.sql.Date date3 = new java.sql.Date(1647920623302L); System.out.println(date3);
输出:
2022-03-22
- 可以看到,java.sql.Date类的对象的构造器只能放入long型毫秒数,输出也只显示 年-月-日。其实该对象的毫秒信息被隐藏起来而已,实际上还是存在的。
-
掌握与java.util.Date类之间的相互转换:
转换情形 | 方法 |
---|---|
java.sql.Date–>java.util.Date | 多态 |
java.util.Date–>java.sql.Date | new java.sql.Date(java.util.Date.getTime()) |
例子:
//java.util.Date-->java.sql.Date
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
System.out.println(date7);
输出:
2022-03-22
11.2.3 SimpleDateFormat的使用
1.作用
用于输出指定格式的日期时间格式。对Date类对象的格式化和解析。
2.使用
需要掌握两个方法:
-
格式化:format(Date); 把Date类对象转换成指定格式的日期字符串。
步骤1:实例化SimpleDateFormat;
步骤2:使用 format(Date) 方法,返回String类型的格式化日期;
//格式化 Date date = new Date();//实例化Date类 System.out.println(date); SimpleDateFormat sdf = new SimpleDateFormat();//1.实例化SimpleDateFormat类 String format = sdf.format(date);//2.format方法,返回String类型的格式化日期 System.out.println(format);
输出:
Sun Mar 27 09:18:24 CST 2022 22-3-27 上午9:18
-
解析:是格式化的逆过程,把String型的已格式化日期转换为Date类的对象。
步骤1:实例化SimpleDateFormat;;
步骤2:使用 parse(String) 方法,返回Date类对象;
@Test public void test1() throws ParseException { //解析 String str = "22-11-04 上午9:18"; Date date1 = sdf.parse(str); System.out.println(date1); }
输出:
Fri Nov 04 09:18:00 CST 2022
- 注意:放入 parse(String str) 中的日期字符串必须符合SimpleDateFormat类规定的格式,否则会解析失败。
- 第二,parse(String str) 方法会抛出异常
ParseException
。为的就是应对输入 String 格式错误的问题。
3.自定义日期格式
在开发中,默认的格式不好用,更常使用SimpleDateFormat(pattern) 构造器实例化对象,来自定义日期格式化。其中 pattern
是自定义的日期格式。
符号 | 意义 |
---|---|
yyyy | 4位数年份 |
MM | 2位数月份 (为和"分"区分定义为大写) |
dd | 2位数日期 |
EEE | 3字符串星期 |
hh | 时 |
mm | 分 |
ss | 秒 |
例子:
//自定义日期格式化
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd EEE hh:mm:ss");
String format1 = sdf1.format(date);
System.out.println(format1);
//自定义日期格式解析
Date date2 = sdf1.parse("2020-11-04 星期三 08:00:00");
System.out.println(date2);
输出:
2022-03-27 星期日 09:37:32
Wed Nov 04 08:00:00 CST 2020
体会:
① 一个 SimpleDateFormat 对象只对应一种格式。无论格式化还是解析都要遵循SimpleDateFormat 对象实例化时构造器中形参的格式。
② 上述例子我耍小聪明加了星期,在格式化的时候还好,觉得很方便,能看到星期。但再解析回来就惨了,parse(String str) 中传递的形参必须按照 "yyyy-MM-dd EEE hh:mm:ss"
的格式来写,但是我不知道那天对应是星期几啊。所以加星期信息一定要慎重。
4.练习
练习1:字符串"2022-03-27"转换成java.sql.Date
我的首次答案:
@Test
public void test2() throws ParseException {
String str = "2022-03-27";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//解析成java.util.Date类对象
Date date = sdf.parse(str);
//得到的java.util.Date转换成java.sql.Date
long time = date.getTime();
java.sql.Date sqlDate = new java.sql.Date(time);
System.out.println(sqlDate);
//查看sqlDate的类型
System.out.println(sqlDate.getClass());
}
输出:
2022-03-27
class java.sql.Date
练习2:三天打鱼两天晒网。1990-01-01开始,问xxxx-XX-xx是在打鱼还是晒网?
我的首次答案:
核心思想:5天一个循环,关键是计算出1990-01-01到 xxxx-XX-xx 一共过去的天数days,转换为毫秒数相减,再换算成天数。再用 days % 5 得数进行判断:<=3 的是在打鱼,否则在晒网。
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("1990-01-01");
//获取当前要查询的日期
// Scanner scanner = new Scanner(System.in);
// System.out.print("请输入日期(yyyy-MM-dd):");
// System.out.println();
String str = "1990-01-06";
Date date1 = sdf.parse(str);
//获取毫秒数时间相减
long diff = date1.getTime() - date.getTime();
//毫秒转换成日期
long days = diff / 1000 / 60 / 60 / 24;
// System.out.println(days);
//5天循环取模判断
System.out.println(mod);
if (mod <= 3) {
System.out.println("打鱼");
} else {
System.out.println("晒网");
}
}
输出:
1
打鱼
11.2.4 Calendar(日历)类
1.作用
Calendar是一个抽象类,用于完成日期字段之间互相操作的功能。
查看 Calendar 类源码,发现是 abstract
抽象类。意味着不能实例化,只能通过其子类来实例化。
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
...
}
2.实例化
- 方式一:创建其子类
GregorianCalendar
的对象 - 方式二:调用其静态方法 getInstance() .
//通过方式二创建对象
Calendar calendar = Calendar.getInstance();
//查看calendar的类
System.out.println(calendar.getClass());
输出:
class java.util.GregorianCalendar
可以看出,通过方式二创建对象,其类还是 GregorianCalendar
。因此,本质上方式一和方式二都是一样的。
3.常用方法
方法 | 作用 |
---|---|
get() | 获取第几天之类的信息 |
set() | 把当前calendar信息自身更改了 |
add() | 把当前calendar信息自身添加/减天数 |
getTime() | 转换成Date对象 |
setTime() | getTime()的逆过程,把Date对象转换成Calendar对象 |
4.例子
① get()
//获取当前时间是这个月的第几天
int days = calendar.get(Calendar.DAY_OF_MONTH);
//获取当前时间是这一年的第几天
int days1 = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println(days);
System.out.println(days1);
输出:
27
86
② set()
//set()设置
calendar.set(Calendar.DAY_OF_MONTH, 20);//返回void,把自身改了
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
输出:
20
③ add()
//add()添加
calendar.add(Calendar.DAY_OF_MONTH, 7);//返回void,把自身改了
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
输出:
27
④ getTime()
//getTime()转换成Date对象
Date date = calendar.getTime();
System.out.println(date);
输出:
Sun Mar 27 12:48:30 CST 2022
⑤ setTime()
//setTime()逆过程,把Date对象转换成Calendar对象
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
输出:
27
5.注意
① 获取月份时,一月是0,二月是1,……, 十二月是11;
② 获取星期时,周日是1,周一是2, ……, 周六是7;
11.3 JDK8中新日期时间API
11.3.1 简介
为什么要增加新的实践API呢?因为Date类很多方法都是过时不推荐使用,而Calendar类是可变的,会修改自身的值。而开发中我们通常希望时间具有像String那样的不可变性,即对当前时间对象修改是返回一个新的时间对象,而不改变原有对象的值。
新时间日期API:
包名 | 作用 |
---|---|
java.time | 包含值对象的基础包 |
java.time.chrono | 提供对不同的日历系统的访问 |
java.time.format | 格式化和解析时间和日期 |
java.time.temporal | 包括底层框架和扩展特性 |
java.time.zone | 包含时区支持的类 |
说明:绝大多数开发者只会用到基础包和format包,也有可能会用到temporal包。
11.3.2 LocalDate、LocalTime、LocalDateTime类
说明:在开发中,LocalDateTime使用频率远远高于其他两个。因为其同时包含了日期和时间。所以下面例子绝大多数以LocalDateTime为例。
1.创建对象
方法 | 作用 |
---|---|
now() | 创建对象 |
@Test
public void test1() {
//1.now()创建对象
LocalDate localDate = LocalDate.now();//创建日期对象
LocalTime localTime = LocalTime.now();//创建时间对象
LocalDateTime localDateTime = LocalDateTime.now();//创建日期时间对象
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
}
输出:
2022-03-28
10:15:32.031
2022-03-28T10:15:32.031
2.获取
方法 | 作用 |
---|---|
getDayOfWeek() | 获取星期几(英文) |
getDayOfMonth() | 获取这个月的第几天 |
getDayOfYear() | 获取这年的第几天 |
getMonth() | 获取月份(英文) |
getMonthValue() | 获取int型月份 |
getYear() | 获取年份 |
getHour() | 获取小时 |
getMinute() | 获取分钟 |
getSecond() | 获取秒 |
@Test
public void test1() {
//2.get()获取
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();//获取星期几
int dayOfMonth = localDateTime.getDayOfMonth();//获取这个月第几天
int dayOfYear = localDateTime.getDayOfYear();//获取这一年第几天
Month month = localDateTime.getMonth();//获取月份
int monthValue = localDateTime.getMonthValue();//获取int型月份
int year = localDateTime.getYear();//获取年份
int hour = localDateTime.getHour();//获取小时
int minute = localDateTime.getMinute();//获取分钟
int second = localDateTime.getSecond();//获取秒
System.out.println(dayOfWeek);
System.out.println(dayOfMonth);
System.out.println(dayOfYear);
System.out.println(month);
System.out.println(monthValue);
System.out.println(year);
System.out.println(hour);
System.out.println(minute);
System.out.println(second);
}
输出:
MONDAY
28
87
MARCH
3
2022
10
27
43
3.设置
LocalDateTime里的设置和修改具有不可变性,即修改后返回新的对象,不改变原有对象的值。这就克服了Calendar类中的可变性的缺陷。
方法 | 作用 |
---|---|
withDayOfMonth(int month) | 设置月份 |
withDayOfYear(int dayOfYear) | 设置一年中的第几天 |
withYear(int year) | 设置年份 |
withMonth(int month) | 设置月份 |
withHour(int hour) | 设置小时 |
withMinute(int minute) | 设置分钟 |
withSecond(int second) | 设置秒 |
@Test
public void test1() {
//3.设置
LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(22);//设置月份
LocalDateTime localDateTime2 = localDateTime.withDayOfYear(99);//设置一年中的第几天
LocalDateTime localDateTime3 = localDateTime.withYear(1998);//设置年份
LocalDateTime localDateTime4 = localDateTime.withMonth(11);//设置月份
LocalDateTime localDateTime5 = localDateTime.withHour(7);//设置小时
LocalDateTime localDateTime6 = localDateTime.withMinute(30);//设置分钟
LocalDateTime localDateTime7 = localDateTime.withSecond(55);//设置秒
System.out.println(localDateTime);
System.out.println(localDateTime1);
System.out.println(localDateTime2);
System.out.println(localDateTime3);
System.out.println(localDateTime4);
System.out.println(localDateTime5);
System.out.println(localDateTime6);
System.out.println(localDateTime7);
}
输出:
2022-03-28T10:44:05.434
2022-03-22T10:44:05.434
2022-04-09T10:44:05.434
1998-03-28T10:44:05.434
2022-11-28T10:44:05.434
2022-03-28T07:44:05.434
2022-03-28T10:30:05.434
2022-03-28T10:44:55.434
第一行是最初的LocalDateTime对象的值,在对其进行了7次修改后仍然没有改变其原始的值,具有不可变性。
4.加
加操作同样具有不可变性。
方法 | 作用 |
---|---|
plusYear(long years) | 添加年份 |
plusMonths(long months) | 添加月份 |
plusWeeks(long weeks) | 添加周数 |
plusDays(long days) | 添加天数 |
plusHours(long hours) | 添加小时 |
plusMinutes(long minutes) | 添加分钟 |
plusSeconds(long seconds) | 添加秒 |
@Test
public void test1() {
//4.加
LocalDateTime localDateTime8 = localDateTime.plusYears(5);//添加年份
LocalDateTime localDateTime9 = localDateTime.plusMonths(8);//添加月份
LocalDateTime localDateTime10 = localDateTime.plusWeeks(10);//添加周数
LocalDateTime localDateTime11 = localDateTime.plusDays(3);//添加天数
LocalDateTime localDateTime12 = localDateTime.plusHours(10);//添加小时
LocalDateTime localDateTime13 = localDateTime.plusMinutes(30);//添加分钟
LocalDateTime localDateTime14 = localDateTime.plusSeconds(20);//添加秒
System.out.println(localDateTime);
System.out.println(localDateTime8);
System.out.println(localDateTime9);
System.out.println(localDateTime10);
System.out.println(localDateTime11);
System.out.println(localDateTime12);
System.out.println(localDateTime13);
System.out.println(localDateTime14);
}
输出:
2022-03-28T10:59:17.190
2027-03-28T10:59:17.190
2022-11-28T10:59:17.190
2022-06-06T10:59:17.190
2022-03-31T10:59:17.190
2022-03-28T20:59:17.190
2022-03-28T11:29:17.190
2022-03-28T10:59:37.190
5.减
方法 | 作用 |
---|---|
minusYear(long years) | 减年份 |
minusMonths(long months) | 减月份 |
minusWeeks(long weeks) | 减周数 |
minusDays(long days) | 减天数 |
minusHours(long hours) | 减小时 |
minusMinutes(long minutes) | 减分钟 |
minusSeconds(long seconds) | 减秒 |
@Test
public void test1() {
//5.减
LocalDateTime localDateTime15 = localDateTime.minusYears(2);//减年份
LocalDateTime localDateTime16 = localDateTime.minusMonths(3);//减月份
LocalDateTime localDateTime17 = localDateTime.minusWeeks(1);//减星期
LocalDateTime localDateTime18 = localDateTime.minusDays(9);//减天数
LocalDateTime localDateTime19 = localDateTime.minusHours(2);//减小时
LocalDateTime localDateTime20 = localDateTime.minusMinutes(14);//减分钟
LocalDateTime localDateTime21 = localDateTime.minusSeconds(30);//减秒
System.out.println(localDateTime);
System.out.println(localDateTime15);
System.out.println(localDateTime16);
System.out.println(localDateTime17);
System.out.println(localDateTime18);
System.out.println(localDateTime19);
System.out.println(localDateTime20);
System.out.println(localDateTime21);
}
输出:
2022-03-29T09:16:38.685
2020-03-29T09:16:38.685
2021-12-29T09:16:38.685
2022-03-22T09:16:38.685
2022-03-20T09:16:38.685
2022-03-29T07:16:38.685
2022-03-29T09:02:38.685
2022-03-29T09:16:08.685
11.3.3 Instant(瞬时)类
Instant类有点像java.util.Date,用得最多的是返回一个类似Date类中的 getTime() 方法的一个毫秒数时间戳。是从1970年1月1日0时0分0秒开始至今的毫秒数(英国格林尼治时区,不是东八区的北京时间)。Instant类只需要掌握4个方法:
方法 | 作用 |
---|---|
now() | 返回Instant类对象,获取本初子午线的时间 |
atOffset(ZoneOffset offset) | 结合即时的偏移创建一个OffsetDateTime |
toEpochMilli() | 获取从1970年1月1日0时0分0秒开始至今的毫秒数 |
ofEpochMilli(long epochMilli) | 根据毫秒数构造器创建Instant对象 |
1.创建对象
@Test
public void test2() {
//1.创建Instant类对象,获取本初子午线的时间
Instant instant = Instant.now();
System.out.println(instant);
}
输出:
2022-03-29T02:00:02.336Z
注:当时的日期时间是:2022-03-29T10:00:02.336Z。但输出的时间慢了8小时,说明输出的是格林尼治时间的本初子午线时间,不是东八区的北京时间。
2.加时间偏移量创建对象
@Test
public void test2() {
//1.创建Instant类对象,获取本初子午线的时间
Instant instant = Instant.now();
System.out.println(instant);
//2.结合即时的偏移创建一个OffsetDateTime,加8小时获得北京时间
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
}
输出:
2022-03-29T02:13:24.009Z
2022-03-29T10:13:24.009+08:00
3.获取从1970年1月1日0时0分0秒开始至今的毫秒数
@Test
public void test2() {
//1.创建Instant类对象
Instant instant = Instant.now();
System.out.println(instant);
//2.结合即时的偏移创建一个OffsetDateTime
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
//3.获取瞬时毫秒数
long milli = instant.toEpochMilli();
System.out.println(milli);
}
输出:
2022-03-29T02:20:51.130Z
2022-03-29T10:20:51.130+08:00
1648520451130
4.根据毫秒数构造器创建Instant对象
@Test
public void test2() {
//1.创建Instant类对象
Instant instant = Instant.now();
System.out.println(instant);
//2.结合即时的偏移创建一个OffsetDateTime
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
//3.获取瞬时毫秒数
long milli = instant.toEpochMilli();
System.out.println(milli);
//4.根据毫秒数构造器创建Instant对象
Instant instant1 = Instant.ofEpochMilli(1648520451130L);
System.out.println(instant1);
}
输出:
2022-03-29T02:24:48.215Z
2022-03-29T10:24:48.215+08:00
1648520688215
2022-03-29T02:20:51.130Z
11.3.4 DateTimeFormat类
是java.time.format.DateTimeFormat 类。如果涉及到需要格式化或者解析日期时间,可以用这个类替换以前的 SimpleDateFormat 类。DateTimeFormat 类提供了三种格式化方法:
方法 | 作用 |
---|---|
DateTimeFormatter.ISO_LOCAL_DATE_TIME | 方式一:预定义的标准格式 |
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.XXX) | 方式二:本地化相关的格式 |
DateTimeFormatter.ofPattern(“yyyy-MM-dd hh:mm:ss E”) | 方式三:自定义格式 |
格式化和解析必须基于相同格式标准的 formatter。
1.方式一:预定义的标准格式
如:ISO_LOCAL_DATE_TIME; ISO_LOCAL_DATE; ISO_LOCAL_TIME
@Test
public void test3() {
//1.方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME; ISO_LOCAL_DATE; ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
//解析:字符串-->日期
TemporalAccessor parse = formatter.parse("2022-03-29T11:00:10.737");
System.out.println(str1);
System.out.println(parse);
}
输出:
2022-03-29T11:01:25.691
{},ISO resolved to 2022-03-29T11:00:10.737
2.方式二:本地化相关的格式
以下两种方式的 formatter 格式都是Java里写死的了,不能改变。如果这些格式都无法满足需求,则需要用方式三:自定义格式。
本地化相关的格式1:ofLocalDateTime()
FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDateTime
//2.方式二:
// 本地化相关的格式。如:ofLocalDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化:日期-->字符串
String str2 = formatter1.format(LocalDateTime.now());
//解析:字符串-->日期
TemporalAccessor parse1 = formatter1.parse("2022年3月30日 上午11时30分18秒");
System.out.println(str2);
System.out.println(parse1);
输出:
2022年3月29日 下午01时18分58秒
{},ISO resolved to 2022-03-30T11:30:18
本地化相关的格式2:ofLocalDate()
FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDate
//2.方式二:
// 本地化相关的格式。如:ofLocalDate()
// FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
//格式化:日期-->字符串
String str3 = formatter2.format(LocalDate.now());
//解析:字符串-->日期
TemporalAccessor parse2 = formatter2.parse("2022年3月25日 星期五");
System.out.println(str3);
System.out.println(parse2);
输出:
2022年3月29日 星期二
{},ISO resolved to 2022-03-25
3.方式三:自定义格式
在真正的开发中,更常用自定义的格式:ofPattern(“yyyy-MM-dd hh:mm:ss E”)
方法 | 作用 |
---|---|
ofPattern(“yyyy-MM-dd hh:mm:ss E”) | 自定义日期时间格式 |
//3.方式三:自定义格式。用:ofPattern("yyyy-MM-dd hh:mm:ss E")
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss E");
//格式化:日期-->字符串
String str4 = formatter3.format(LocalDateTime.now());
//解析:字符串-->日期
TemporalAccessor parse3 = formatter3.parse("2022-05-09 09:27:19 星期一");
System.out.println(str4);
System.out.println(parse3);
输出:
2022-03-29 01:28:59 星期二
{NanoOfSecond=0, MilliOfSecond=0, MinuteOfHour=27, SecondOfMinute=19, MicroOfSecond=0, HourOfAmPm=9},ISO resolved to 2022-05-09
11.3.5 其他日期时间API
API | 作用 |
---|---|
ZoneId | 该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris |
ZonedDateTime | 一个在ISO-8601日历系统时区的日期时间,如2007-12-03T10:15:30+01:00 Europe/Paris |
Clock | 使用时区提供对当前即时、日期和时间的访问的时钟 |
Duration | 持续时间,用于计算两个时间间隔 |
Period | 日期间隔,用于计算两个日期间隔 |
TemporalAdjuster | 时间校正器。有时我们可能需要获取例如:将日期调整到”下一个工作日“等操作 |
TemporalAdjusters | 该类通过静态方法 firstDayOfXxx()/lastDayOfXxx()/nextXxx() 提供了大量的常用 TemporalAdjuster 的实现 |
11.3.6 与传统日期处理的转换
类 | To遗留类 | From遗留类 |
---|---|---|
java.time.Instant与 java.util.Date | Date.from(instant) | date.toInstant() |
java.time.Instant与 java.sql.Timestamp | Timestamp.from(instant) | timestamp.toInstant() |
java.time.ZonedDateTime与java.util.GregorianCalendar | GregorianCalendar.from(zonedDateTime) | cal.toZonedDateTime() |
java.time.LocalDate与 java.sql.Time | Date.valueOf(localDate) | date.toLocalDate() |
java.time.LocalTime与 java.sql.Time | Date.valueOf(localDate) | date.toLocalTime() |
java.time.LocalDateTime与java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
java.time.ZoneId与 java.util.TimeZone | Timezone.getTimeZone(id) | timeZone.toZoneId() |
java.time.format.DateTimeFormatter与java.text.DateFormat | formatter.toFormat() | 无 |
11.4 Java比较器
实际开发中,我们需要对对象比较大小,比如商品按价格排序、按销量排序、按评论数排序。这其实不是说真的对象比较大小,而是对象的属性比较大小。但是一般的 <
、>
这些只能比较基本数据类型。因此诞生出了Java比较器。
我们需要掌握两个API接口:
API | 描述 |
---|---|
java.lang.Comparable | 自然排序 |
java.util.Comparator | 定制排序 |
个人思考:京东淘宝上的 “综合排序” ,是否是对象的每一个属性都赋予一个权重,最后计算加权和,根据加权和的大小比较出综合排序。
11.4.1 Comparable接口
1.使用
- 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
- 像String、包装类重写compareTo()方法以后,进行了从小到大的排列。
- 重写compareTo(obj)的规则:
- 如果当前对象this大于形参对象obj,则返回正整数;
- 如果当前对象this小于形参对象obj,则返回负整数;
- 如果当前对象this等于形参对象obj,则返回零。
- 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写
compareTo(obj)
方法。在compareTo(obj)
方法中指明如何排序。 - 只要实现了 Comparable 接口的类,都可以进行比较。
2.例子
创建商品类 Goods,实现 Comparable 接口,重写 CompareTo() 方法,使得商品对象按价格排序,若价格相同,再按产品名称排序。
public class Goods implements Comparable {
private String name;//商品名称
private double price;//价格
//构造器
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
//get、set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
//重写toString方法
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明商品的比较方式:按价格从低到高排序,若价格相同,再按产品名称排序
@Override
public int compareTo(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods) o;
if (this.price > goods.price) {
return 1;
} else if (this.price < goods.price) {
return -1;
}else {
//价格相同,再按产品名称排序
return this.name.compareTo(goods.name);
}
}
throw new RuntimeException("对不起,传入的数据类型不一致!");
}
}
测试:我的首次代码。我还自己傻乎乎地手写冒泡排序,其实可以直接调用 Array.sort() 方法来排序即可。
@Test
public void test1() {
//对商品对象进行排序
Goods[] goodsArr = new Goods[5];
goodsArr[0] = new Goods("LogitechMouse", 399.0);
goodsArr[1] = new Goods("LenovoMouse", 199.0);
goodsArr[2] = new Goods("DellMouse", 99.0);
goodsArr[3] = new Goods("XiaomiMouse", 59.0);
goodsArr[4] = new Goods("HPMouse", 199.0);
System.out.println(Arrays.toString(goodsArr));
//排序: 冒泡排序,从小到大
Goods temp;
for (int i = 0; i < goodsArr.length; i++) {
for (int j = 0; j < goodsArr.length - i - 1; j++) {
if (goodsArr[j].compareTo(goodsArr[j + 1]) == 1) {
temp = goodsArr[j];
goodsArr[j] = goodsArr[j + 1];
goodsArr[j + 1] = temp;
}
}
}
//输出排序后的数组
System.out.println(Arrays.toString(goodsArr));
}
优化代码:
@Test
public void test1() {
//对商品对象进行排序
Goods[] goodsArr = new Goods[5];
goodsArr[0] = new Goods("LogitechMouse", 399.0);
goodsArr[1] = new Goods("LenovoMouse", 199.0);
goodsArr[2] = new Goods("DellMouse", 99.0);
goodsArr[3] = new Goods("XiaomiMouse", 59.0);
goodsArr[4] = new Goods("HPMouse", 199.0);
System.out.println(Arrays.toString(goodsArr));
//直接调用Array.sort()方法来排序,它会自动按照重写的compareTo()方法比较
Arrays.sort(goodsArr);
//输出排序后的数组
System.out.println(Arrays.toString(goodsArr));
}
输出:
[Goods{name='LogitechMouse', price=399.0}, Goods{name='LenovoMouse', price=199.0}, Goods{name='DellMouse', price=99.0}, Goods{name='XiaomiMouse', price=59.0}, Goods{name='HPMouse', price=199.0}]
[Goods{name='XiaomiMouse', price=59.0}, Goods{name='DellMouse', price=99.0}, Goods{name='HPMouse', price=199.0}, Goods{name='LenovoMouse', price=199.0}, Goods{name='LogitechMouse', price=399.0}]
可以看到,LenovoMouse 和 HPMouse 价格都是 199.0,但是按产品名称排序,"H"排在 “L” 前,因此 HPMouse 就排到 LenovoMouse 前面去了。
11.4.2 Comparator接口
1.背景
当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码 (比如JDK中定义的一些类没有实现Comparable 接口,不可能再修改而我们又想对其对象进行排序),或者实现了 java.lang.Comparable 接口的排序规则不适合当前的操作 (比如String已经实现了Comparable 接口,但我想按字母从大到小排序,需要定制),那么可以考虑使用 Comparator 的对象来排序。
2.使用
- 重写
compare(Object o1, Object o2)
方法,比较o1和o2的大小规则:- 如果方法返回正整数,则表示o1大于o2;
- 如果返回0,表示相等;
- 返回负整数,表示o1小于o2。
- 由于 Comparator 接口是用在 Array.sort() 中,只用一次,因此通常是通过创建匿名实现类的匿名对象来调用。
3.例子
例子1:按字母表从大到小排序
@Test
//按字母从大到小排序
public void test2() {
String[] arr = new String[]{"AA", "CC", "KK", "MM", "GG", "JJ", "DD"};
System.out.println(Arrays.toString(arr));
//创建Comparator接口的匿名实现类的匿名对象
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
//String类本身就实现了Comparable接口,加负号即可变为从大到小排序
return -s1.compareTo(s2);
}
throw new RuntimeException("对不起,传入的数据类型不一致!");
}
});
//输出排序后的数组
System.out.println(Arrays.toString(arr));
}
输出:
[AA, CC, KK, MM, GG, JJ, DD]
[MM, KK, JJ, GG, DD, CC, AA]
上面的代码中,使用 Array.sort(arr, Comparator实现类对象) 方法进行排序。String类本身就实现了Comparable接口,加负号即可变为从大到小排序。
例子2:
还是接着用上面商品类 Goods,但不实现 Comparable 接口和不重写 CompareTo() 方法。要求使得商品对象按产品名称从小到大排序,若产品名称相同,则按价格从大到小排序。
Goods类:
public class Goods {
private String name;//商品名称
private double price;//价格
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
测试:
@Test
public void test3() {
//对商品对象进行排序
Goods[] goodsArr = new Goods[5];
goodsArr[0] = new Goods("LogitechMouse", 399.0);
goodsArr[1] = new Goods("LenovoMouse", 199.0);
goodsArr[2] = new Goods("DellMouse", 99.0);
goodsArr[3] = new Goods("XiaomiMouse", 59.0);
goodsArr[4] = new Goods("DellMouse", 299.0);
System.out.println(Arrays.toString(goodsArr));
//直接调用Array.sort(arr, Comparator实现类对象)方法来排序,
//它会自动按照重写的compare()方法比较
Arrays.sort(goodsArr, new Comparator() {
@Override
//按产品名称从小到大排序,若产品名称相同,则按价格从大到小排序
public int compare(Object o1, Object o2) {
if (o1 instanceof Goods && o2 instanceof Goods) {
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o2;
//产品名称相同,再按价格从大到小排序
if (g1.getName().equals(g2.getName())) {
//方式一:
// if (g1.getPrice() > g2.getPrice()) {
// return -1;
// } else if (g1.getPrice() < g2.getPrice()) {
// return 1;
// } else {
// return 0;
// }
//方式二:加负号即可变为从大到小排序
return -Double.compare(g1.getPrice(), g2.getPrice());
} else {
//按产品名称从小到大排序
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("对不起,传入的数据类型不一致!");
}
});
//输出排序后的数组
System.out.println(Arrays.toString(goodsArr));
}
输出:
[Goods{name='LogitechMouse', price=399.0}, Goods{name='LenovoMouse', price=199.0}, Goods{name='DellMouse', price=99.0}, Goods{name='XiaomiMouse', price=59.0}, Goods{name='DellMouse', price=299.0}]
[Goods{name='DellMouse', price=299.0}, Goods{name='DellMouse', price=99.0}, Goods{name='LenovoMouse', price=199.0}, Goods{name='LogitechMouse', price=399.0}, Goods{name='XiaomiMouse', price=59.0}]
可以看到,5个对象都先按字母表顺序从小到大排列,两个产品名称同为 DellMouse
的对象,按其价格从大到小排序。
体会:
- 两个 double 类型的数据比较可以直接使用
Double.compare(double b1, double b2)
,也是 b1 比 b2 大返回正整数、b1 比 b2 小返回负整数、两者相等返回0。 - 加负号即可变为从大到小排序。
11.4.3 Comparable与Comparator的对比
- Comparable接口的 compareTo() 方法一旦确定,Comparable接口实现类的对象在任何位置都可以比较大小。是一劳永逸的。
- Comparator接口属于临时性的比较。大多数场景下是一次性的,因此都只是创建 Comparator 接口的匿名实现类的模拟对象使用。
11.5 System类
11.5.1 介绍
- System 类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于 java.lang 包 。
- 由于该类的构造器 是 private 的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是 static 的, 所以也可以很方便地进行调用 。
- System 类内部包含
in
、out
和err
三个成员变量,分别代表标准输入流 (键盘输入),标准输出流 (显示器) 和标准错误输出流 (显示器) 。
11.5.2 常用方法
方法 | 作用 |
---|---|
native long currentTimeMillis() | 返回当前毫秒数 |
void exit(int status) | 该方法的作用是退出程序。其中 status 的值为 0 代表正常退出,非零代表异常退出。 使用该方法可以在图形界面编程中实现程序的退出功能 等 。 |
void gc() | 该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。 |
String getProperty(String key) | 该方法的作用是获得系统中属性名为 key 的属性对应的值。系统中常见的属性名以及属性的作用如下表所示: |
属性名 | 属性说明 |
---|---|
java.version | Java运行时环境版本 |
java.home | Java 安装目录 |
os.name | 操作系统的名称 |
os.version | 操作系统的版本 |
user.name | 用户的账户名称 |
user.home | 用户的主目录 |
user.dir | 用户的当前工作目录 |
11.5.3 例子
String getProperty(String key)
@Test
public void test1() {
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String osHome = System.getProperty("user.home");
System.out.println("user的home:" + osHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);
}
输出:
java的version:1.8.0_321
java的home:C:\Program Files\Java\jdk1.8.0_321\jre
os的name:Windows 10
os的version:10.0
user的name:92490
user的home:C:\Users\92490
user的dir:F:\OneDrive - stu.ouc.edu.cn\MarkDown\2-后端开发\IdeaProject\JavaSE\ch11
11.6 Math类
11.6.1 介绍
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为 double 型。
11.6.2 常用方法
方法 | 作用 |
---|---|
abs | 绝对值 |
acos, asin, atan, cos, sin, tan | 三角函数 |
sqrt | 平方根 |
pow(double a, double b) | a的b次幂 |
log | 自然对数 |
exp | e为底指数 |
max(double a, double b) | 返回a和b中最大值 |
min(double a, double b) | 返回a和b中最小值 |
random() | 返回0.0到1.0的随机数 |
long round(double a) | double型数据a转换为long型(四舍五入) |
toDegrees(double angrad) | 弧度 --> 角度 |
toRadians(double angdeg) | 角度 --> 弧度 |
11.7 BigInteger与BigDecimal
11.7.1 介绍
1.BigIntegeger
- 可以理解为能表示很大的整数。Integer 类作为 int 的包装类,能存储的最大整型值为 2 31 − 1 2^{31}- 1 231−1 ,Long 类也是有限的,最大为 2 63 − 1 2^{63}-1 263−1 。 如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。
- java.math 包的 BigInteger 可以表示不可变的任意精度的整数 。 BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。
- 构造器
BigInteger (String val):根据字符串构建 BigInteger 对象。
2.BigDecimal
-
可以理解为精度很高的浮点数。一般的 Float 类和 Double 类可以用来做科学计算或工程计算,但在 商业计算中,要求数字精度比较高,故用到 java.math.BigDecimal 类 。
-
BigDecimal 类支持不可变的、任意精度的有符号十进制定点数 。
-
构造器
public BigDecimal(double val); public BigDecimal(String val);
11.7.2 常用方法
1.BigIntegeger
方法 | 作用 |
---|---|
public BigInteger abs() | 绝对值:返回此 BigInteger 的绝对值的 BigInteger |
BigInteger add (BigInteger val) | 加:返回其值为(this + val) 的 BigInteger |
BigInteger subtract (BigInteger val) | 减:返回其值为 (this - val) 的 BigInteger |
BigInteger multiply (BigInteger val) | 乘:返回其值为(this * val) 的 BigInteger |
BigInteger divide (BigInteger val) | 除:返回其值为 (this / val) 的 BigInteger 。整数相除只保留整数部分 。 |
BigInteger remainder (BigInteger val) | 取模:返回其值为 (this % val) 的 BigInteger 。 |
BigInteger [] divideAndRemainder (BigInteger val) | 除和取模:返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组 。 |
BigInteger pow (int exponent) | 指数:返回其值为 ( t h i s e x p o n e n t this^{ exponent} thisexponent ) 的 BigInteger 。 |
2.BigDecimal
方法 | 作用 |
---|---|
public BigDecimal add (BigDecimal) | 加 |
public BigDecimal subtract (BigDecimal) | 减 |
public BigDecimal multiply (BigDecimal) | 乘 |
public BigDecimal divide (BigDecimal divisor, int scale, int roundingMode) | 除 |
@Test
public void mathTest() {
BigInteger bi = new BigInteger("123456789123456789123456789123456789123456789");
BigDecimal bd = new BigDecimal("12345.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));//错误写法,没有指定计算精度
//除法计算,指定四舍五入
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
//除法计算,指定保留25位有效数字,并四舍五入
System.out.println(bd.divide(bd2, 25, BigDecimal.ROUND_HALF_UP));
}
输出:
123456789123456789123456789123456789123456789
1122.305
1122.3046363636363636363636364