09-Java常用类

09—Java常用类

1.字符串相关类

1.1 String特性
  • String类:代表字符串。Java程序中所有的字符串字面值(如“abc”)都作为此类的实例实现

  • String是一个final类,代表不可变的字符序列

  • 字符串是常量,用双引号引起来表示。他们的值在创建之后不能更改

  • String对象的字符内容是存储在一个字符数组value[]中的

  • public final class String implements java.io.Serializable,Comparable<String>,CharSequence{
        //The vlue is used for character storage
        private final char value[];
        //Cache the hash code for the string
        private int hash;//Default to 0
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16


    ###### 1.1.1 String对象的创建

    ```java
    String str = "hello";

    //本质上this.value = new char[0];
    String s1 = new String();

    //本质上this.value = original.value;
    String s2 = new String(String original);

    //本质上this.value = Array.copyOf(value, value.length);
    String s3 = new String(char[] a);
    String s4 = new String(char[] a, int startIndex, int count);
1.2 String 内存解析
image-20210716201332311 image-20210716201418152

image-20210816164410800

image-20210816164540235

image-20210816164635997

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
import org.junit.Test;

/**
* String的使用
*/

class Person {

String name;
int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public Person() {
}
}
public class StringTest {
/*
String:字符串,使用一对""引起来表示。
1.String声明为final的,不可被继承
2.String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
3.String内部定义了final char[] value用于存储字符串数据
4.String:代表不可变的字符序列。简称:不可变性。
体现:1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
5.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
6.字符串常量池中是不会存储相同内容的字符串的。
*/
@Test
public void test1(){
String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";

System.out.println(s1 == s2);//比较s1和s2的地址值

System.out.println(s1);//hello
System.out.println(s2);//abc

System.out.println("*****************");

String s3 = "abc";
s3 += "def";
System.out.println(s3);//abcdef
System.out.println(s2);

System.out.println("*****************");

String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4);//abc
System.out.println(s5);//mbc

}

/*
String的实例化方式:
方式一:通过字面量定义的方式
方式二:通过new + 构造器的方式

面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"

*/
@Test
public void test2(){
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");

System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false

System.out.println("***********************");
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);

System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true

p1.name = "Jerry";
System.out.println(p2.name);//Tom
}

/*
结论:
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中有一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中
*/
@Test
public void test4(){
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false

final String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true

}

@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);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false

String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true


}

}

1.2.1 String使用陷阱
  • String s1 = “a”;

    说明:在字符串常量池中创建了一个字面量为”a”的字符串。

  • s1 = s1 + “b”;

    说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+”b”(也就是”ab”)。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响 程序的性能。

  • String s2 = “ab”;

    说明:直接在字符串常量池中创建一个字面量为”ab”的字符串。

  • String s3 = “a” + “b”;

    说明:s3指向字符串常量池中已经创建的”ab”的字符串。

  • String s4 = s1.intern();

    说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的”ab”字符串 赋值给s4。

image-20210716202411258

面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);//good
System.out.println(ex.ch);//best
}
}
1.3 字符串相关的类:String常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
 String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
 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(不包含)的一个子字符串。

1
2
3
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
1
2
3
4
5
6
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
2
3
4
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 
 String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
 String replaceAll(String regex, String replacement) :使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
 String replaceFirst(String regex, String replacement) :使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
1
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
1
2
 String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。 
 String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
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
package String类;

import org.junit.Test;

public class StringMethodTest {
/*
boolean matches(String regx):返回此字符串是否匹配给定的正则表达式
String[] split(String regx):根据正则表达式的匹配来拆分此字符串
String[] split(String regx, int limit):根据匹配的给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部放到最后一个元素中
*/
@Test
public void test1(){
String str = "#X#I#O#N#G";
boolean boo = str.matches("(#[A-Z])+");
System.out.println(boo);
String[] sp = str.split("#");
String[] sp2 = str.split("#",3);
for(String s : sp)
System.out.print(s);
for (String a : sp2) {
System.out.print(a);
}

}
/*
int length():返回字符串的长度:return value.length
char charAt(int index):返回某索引处的字符return value[index]
boolean isEmpty():判断是否为空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将String中的所有字符转为小写
String toUpperCase():使用默认语言环境,将String中的所有字符转为大写
String trim():返回字符串副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anthorString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。等价用+
int comapreTo(String anotherString):比较两字符串的大小
String substring(int beginIndex):返回一个新的字符串,他是此字符串从beginIndex开始截取到最后的一个子字符串
String substring(int beginIndex, int endIndex):返回一个新的字符串,他是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串
*/
@Test
public void test2(){
String str = " xiongZhuO ";
System.out.println("原始字符串为:" + str);
System.out.println("返回字符串长度:str.length() = "+str.length());
System.out.println("寻找字符串中索引为1的字符:str.charAt(1) = " + str.charAt(str.indexOf('x')));
System.out.println("判断字符串是否为空:str.isEmpty() = "+str.isEmpty());
System.out.println("将字符串全部转为大写:str.toUppercase() = " + str.toUpperCase());
System.out.println("将字符串全部转为小写:str.toLowerCase() = " + str.toLowerCase());
System.out.println("去除字符串中的前后空白符:str.trim() = " + str.trim());
System.out.println("比较\" xiongzhuo \"是否与原字符串相等:\" xiongzhuo \".equals(str) = "+" xiongzhuo ".equals(str));
System.out.println("忽略大小写比较\" xiongzhuo \"是否与原字符串相等:\" xiongzhuo \".equalsIngoreCase(str) = " + " xiongzhuo ".equalsIgnoreCase(str));
System.out.println("将\"GUET\"连接到字符串的起始位置:\"GUET\".concat(str) = " + "GUET".concat(str));
System.out.println("比较\"GUET\"与字符串的大小:\"GUET\".compareTo(str) = " + "GUET".compareTo(str));
System.out.println("从索引6开始截取一个新的子串:str.substring(6) = " + str.substring(6));
System.out.println("从索引1到索引6截取一个新的串:str.substring(1,6) = " + str.substring(1, 6));
}
/*
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWitd(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

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
*/
@Test
public void test3(){
String str = "GGGGUET XXXIONGZHUO";
boolean b1 = str.endsWith("ZHUO");
System.out.println(b1);//true

boolean b2 = str.startsWith("G");
System.out.println(b2);//true

boolean b3 = str.startsWith("XX",8);
System.out.println(b3);//true

boolean b4 = str.contains("XIONG");
System.out.println(b4);//true

int i1 = str.indexOf("X");
System.out.println("X在字符串中的索引位置为:" + i1);//8

int i2 = str.indexOf("XX",7);
System.out.println("XX在字符串中以索引7为开始的搜索位置:" + i2);//8

int i3 = str.lastIndexOf("O");
System.out.println("O在字符串中的索引位置为:" + i3);//18

int i4 = str.lastIndexOf("O",15);
System.out.println("O在字符串中以索引15为开始反向搜索的位置:" + i4);//-1
}
/*
替换:
String replace(char oldChar, char newChar):返回一个新的字符串,他是通过用newChar替换oldChar得到的。
String replace(CharSequence targe, ChaeSequence replacement):使用指定字面值替换序列替换此字符串中所有匹配字面值目标序列的子字符串
String replaceAll(String regex, String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串
String replaceFirst(String regx, String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串
匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
切片:
String[] split(String regx):根据给定的正则表达式拆分此字符串
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串。最多不超过Limit个,如果超过了,剩下的全部放到最后一个元素中
*/
@Test
public void test4(){
String str1 = "路漫漫其修远兮,吾将上下而求索";
String str2 = str1.replace("吾","wo");
String str3 = str1.replace("吾将上下而求索","屈原");
String str4 = str1.replaceAll("(.)\\1+","$1");
String str5 = str1.replaceFirst("漫","man");
System.out.println("Str1: " + str1);//Str1: 路漫漫其修远兮,吾将上下而求索
System.out.println("Str2: " + str2);//Str2: 路漫漫其修远兮,wo将上下而求索
System.out.println("Str3: " + str3);//Str3: 路漫漫其修远兮,屈原
System.out.println("Str4: " + str4);//Str4: 路漫其修远兮,吾将上下而求索
System.out.println("Str5: " + str5);//Str5: 路man漫其修远兮,吾将上下而求索
}
}

1.3.1 String与基本数据类型的转换
  • 字符串 -> 基本数据类型、包装类
    • Integer包装类的public static int parseInt(String s):可以由“数字”字符组成的字符串转为整型
    • 类似的,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型
  • 基本数据类型、包装类 -> 字符串
    • 调用String类中的public String valueOf(int n)可将innt转为字符串
    • 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可以由参数的相应类型到字符串的转换
1.3.2 String与字符数组转化
  • 字符数组 -> 字符串
    • String类的构造器:String(char[])字符数组中的全部字符创建字符串对象
    • String(char[],int offset, int length)用字符数组中的部分字符创建字符串对象
  • 字符串 -> 字符数组
    • public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法
    • public void getChars(int srcBegin, int srcEnd, char[] dst, int detBegin):提供了将指定索引范围内的字符串存放到数组中的方法
1.3.3 String与字节数组的转化
  • 字节数组 -> 字符串

    • String(byte[]):通过使用平台默认的字符集解码指定的byte数组,构造一个新的String
    • String(byte[], int offset, int length):用指定的字节数组的一部分,即总数组的起始位置offset开始取length个字节构造一个字符串对象。
  • 字符串 -> 字节数组

    • public byte[] getBytes():使用平台的默认字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中
    • public byte[] getBytes(String charsetName):使用指定的字符集将此String编码到byte序列,并将结果存储到新的byte数组
  • package String类;
    
    import org.junit.Test;
    
    import java.io.UnsupportedEncodingException;
    import java.util.Arrays;
    
    public class StringTest1 {
    
         /*
        String 与 byte[]之间的转换
        编码:String --> byte[]:调用String的getBytes()
        解码:byte[] --> String:调用String的构造器
    
        编码:字符串 -->字节  (看得懂 --->看不懂的二进制数据)
        解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)
    
        说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
         */
        @Test
        public void test3() throws UnsupportedEncodingException {
            String str1 = "abc123中国";
            byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
            System.out.println(Arrays.toString(bytes));
            //[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]utf-8中文使用三个来编码
    
            byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
            System.out.println(Arrays.toString(gbks));
            //[97, 98, 99, 49, 50, 51, -42, -48, -71, -6]gbk中文使用两个来编码
    
            System.out.println("******************");
    
            String str2 = new String(bytes);//使用默认的字符集,进行解码。
            System.out.println(str2);//abc123中国
    
            String str3 = new String(gbks);
            System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!
    
    
            String str4 = new String(gbks, "gbk");
            System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
    
    
        }
    
        /*
        String 与 char[]之间的转换
    
        String --> char[]:调用String的toCharArray()
        char[] --> String:调用String的构造器
         */
        @Test
        public void test2(){
            String str1 = "abc123";  //题目: a21cb3
    
            char[] charArray = str1.toCharArray();
            for (int i = 0; i < charArray.length; i++) {
                System.out.println(charArray[i]);
            }
    
            char[] arr = new char[]{'h','e','l','l','o'};
            String str2 = new String(arr);
            System.out.println(str2);
        }
    
        /*
        复习:
        String 与基本数据类型、包装类之间的转换。
    
        String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
        基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)
    
         */
        @Test
        public void test1(){
            String str1 = "123";
    //        int num = (int)str1;//错误的
            int num = Integer.parseInt(str1);
    
            String str2 = String.valueOf(num);//"123"
            String str3 = num + "";
    
            System.out.println(str1 == str3);
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    ##### 1.4 字符串相关类--StringBuffer

    * java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象
    * 很多方法与String相同
    * 作为参数传递时,方法内部可以改变值
    * <img src="https://gitee.com/xiongzero/alienware_-pic-go/raw/master/img/20210716212117.png" alt="image-20210716212116312" style="zoom:50%;" />
    * StringBuffer类不同于String,其对象必须使用构造器生成,有三个构造器:
    * StringBuffer():初始容量为16的字符串缓冲区
    * StringBuffer(int size):构造指定容量的字符串缓冲区
    * StringBuffer(String str):将内容初始化为指定字符串内容
    * ![image-20210716212823159](https://gitee.com/xiongzero/alienware_-pic-go/raw/master/img/20210716212826.png)

    ###### 1.4.1 StringBuffer类的常用方法

    ```java
    StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
    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() :把当前字符序列逆转
  • 当append和insert时,如果原来value数组长度不够,可扩容

  • 如上这些方法支持方法链操作

  • 方法链原理

  • @Override
    public StringBuilder append(String str){
        super.append(str);
        return this;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    **append()方法浅解**

    1.String与StringBuffer

    两个类均由public final修饰,意味着是最终类,不能被继承,但是为啥StringBuffer长度和内容可变,而String不可变呢?

    ​ 我们看String的源码中的成员变量:

    ```java
    /** String本质是个char数组.*/
    private final char value[];
    使用了final修饰,一旦创建,该值就不可改变,但是注意final修饰引用类型的变量时,指的是引用类型地址值不能发生改变。但是Array数组是可变的,如果我们不改变指向value[]的地址,而改变value[]的值呢?此时需要注意value[]是采用private修饰的,后面String所有的方法没有动value[]里的元素,而且String类不可被继承,不能重写方法,所以String不可变。 那为啥StringBuffer长度和内容可变?发现StringBuffer没有value[]成员变量,看父类AbstractStringBuilder中value[]成员变量
1
char[] value;

豁然开朗,那就看看StringBuffer的append()方法,了解一下是如何改变添加的

2.查看StringBuffer的append()方法

为了好说明,举个例子:

1
2
3
StringBuffer stringBuffer = new StringBuffer("hello");
stringBuffer.append("world");/*打印出helloworld*/
System.out.println(stringBuffer);
  1. 进入append方法

    1
    2
    3
    4
    5
    @Override    public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
    }

    其中toStringCache是啥呢?看描述是Cleared whenever the StringBuffer is modified.当改变就清空,接下来看调用了父类的append的方法【注意用synchronized修饰了,线程安全】

  2. 进入AbstractStringBuilder的append()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public AbstractStringBuilder append(String str) { 
    if (str == null)
    return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
    }

    如果参数str为空返回appendNull(); 该方法最终返回return this;接下来看ensureCapacityInternal()方法,传入的count+len是hello与world的总长度

  3. 进入ensureCapacityInternal()方法

    1
    2
    3
    4
    5
    6
    private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
    value = Arrays.copyOf(value,newCapacity(minimumCapacity));
    }
    }

    一目了然。 copyOf(char[] original, int newLength)的方法查JDK帮助文档可知: 复制指定的数组,复制具有指定的长度。也就是说 ensureCapacityInternal()方法其实是重新复制了一个新数组,将长度扩大成传入的参数长度,并重新赋值给value

  4. 进入String的getChars()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {//0,len=5,value=[hello],count=5
    if (srcBegin < 0) {
    throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
    throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
    throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

    最终调用的是System.arraycopy的方法:将指定源数组中的数组从指定位置复制到目标数组的指定位置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void arraycopy(Object src,
    int srcPos,
    Object dest,
    int destPos,
    int length)
    /*src - 源数组。
    srcPos - 源数组中的起始位置。
    dest - 目标数组。
    destPos - 目的地数据中的起始位置。
    length - 要复制的数组元素的数量。
    */
    System.arraycopy([world], 0, [hello], 5, 5);

    以上可知,append方法其实是创建了一个新的数组,扩大了长度,将需要添加的字符串给复制到这个新的数组中去。

其他方法

1
2
3
4
5
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
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
package com.atguigu.java;

import org.junit.Test;

/**
* 关于StringBuffer和StringBuilder的使用
*
* @author shkstart
* @create 2019 下午 3:32
*/
public class StringBufferBuilderTest {
/*
对比String、StringBuffer、StringBuilder三者的效率:
从高到低排列:StringBuilder > StringBuffer > String
*/
@Test
public void test3(){
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));

startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));

startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));

}

/*
StringBuffer的常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
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() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)

总结:
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
*遍历:for() + charAt() / toString()
*/
@Test
public void test2(){
StringBuffer s1 = new StringBuffer("abc");
s1.append(1);
s1.append('1');
System.out.println(s1);
// s1.delete(2,4);
// s1.replace(2,4,"hello");
// s1.insert(2,false);
// s1.reverse();
String s2 = s1.substring(1, 3);
System.out.println(s1);
System.out.println(s1.length());
System.out.println(s2);
}


/*
String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

源码分析:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。

指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)


*/
@Test
public void test1(){
StringBuffer sb1 = new StringBuffer("abc");
sb1.setCharAt(0,'m');
System.out.println(sb1);

StringBuffer sb2 = new StringBuffer();
System.out.println(sb2.length());//0
}

}

1.5 字符串相关类:StringBuilder
  • StringBuilder和StringBuffer非常相似,均代表可变的字符序列,而且提供相关功能的方法也一样
  • 面试题:对比String、StringBuffer、StringBuilder
    • String(JDK1.0):不可变字符序列
    • StringBuffer(JDK1.0):可变字符序列、效率低、线程安全
    • StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全
    • 注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值

相关代码测试

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
package String类;

import org.junit.Test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringExer {
/*
将一个字符串进行反转。将字符串中指定部分进行反转,比如“abcdefg”反转为"abfedcg"
*/
@Test
public void test1() {
String str = "abcdefg";
System.out.println(reverse1(2, 5, str));
System.out.println(reverse2(2, 5, str));
System.out.println(reverse3(2, 5, str));
}

//方式一:转为char[] 反转区间为[startIndex, endIndex]
public String reverse1(int startIndex, int endIndex, String str) {
if (str != null) {
char[] arr = str.toCharArray();
for (int x = startIndex, y = endIndex; x < y; x++, y--) {
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
return new String(arr);//返回的字符串
}
return null;
}

//方式二:使用String的拼接 反转区间为[startIndex, endIndex]
public String reverse2(int startIndex, int endIndex, String str) {
if (str != null) {
//第一部分
String newStr = str.substring(0, startIndex);
//第二部分
for (int i = endIndex; i >= startIndex; i--) {
newStr += str.charAt(i);
}
//第三部分
newStr += str.substring(endIndex + 1);
return newStr;
}
return null;
}

//方式三;使用StringBuffer/StringBuilder替换String
public String reverse3(int startIndex, int endIndex, String str) {
if (str != null) {
StringBuilder stringBuilder = new StringBuilder(str.length());//创建一个原字符串长度的StringBuilder变量
//第一部分
stringBuilder.append(str.substring(0, startIndex));
//第二部分
for (int i = endIndex; i >= startIndex; i--) {
stringBuilder.append(str.charAt(i));
}
//第三部分
stringBuilder.append(str.substring(endIndex + 1));
return stringBuilder.toString();
}
return null;
}

/*
获取一个字符串在另一个字符串中出现的次数。比如:获取“ab”在 “abkkcadkabkebfkaabkskab” 中出现的次数
*/
@Test
public void test2() {
String mainStr = "abkkcadkabkebfkaabkskab";
String subStr = "ab";
System.out.println(getCount1(mainStr, subStr));
System.out.println(getCount2(mainStr, subStr));
}

//方式一:使用正则表达式
public int getCount1(String mainStr, String subStr) {
int count = 0;
Pattern pat = Pattern.compile(subStr);
Matcher matcher = pat.matcher(mainStr);
while (matcher.find()) {
count++;
}
return count;
}

//方式二;使用字符串的indexOf方法
public int getCount2(String mainStr, String subStr) {
int count = 0;
int index = 0;
int subLength = subStr.length();

if (mainStr.length() >= subStr.length()) {
//方式一
while ((index = mainStr.indexOf(subStr, index)) != -1) {
count++;
index += subLength;
}
// // 方式二
// while((index = mainStr.indexOf(subStr))!= -1){
// count++;
// mainStr = mainStr.substring(index+subLength);
// }
}
return count;
}

/*
获取两个字符串最大的相同子串
str1 = "abcwerthelloyuiodefabcdef";str2 = "cvhellobnm"
提示:将短的那个串进行长度依次递减的子串与较长的串比较。
*/
@Test
public void test3() {
String str1 = "abcwerthelloyuiodefabcdefxiong";
String str2 = "cvhellodfsaxiong";
System.out.println(getMaxSameString(str1, str2));


}

public String getMaxSameString(String str1, String str2) {
if(str1 != null && str2 != null){
StringBuffer sb1 = new StringBuffer();
String maxString = (str1.length() > str2.length())?str1:str2;
String minString = (str1.length() <= str2.length())?str1:str2;

int len = minString.length();
for (int i = 0; i < len; i++) {
for (int x = 0,y = len -i; y <= len; x++,y++){
String subString = minString.substring(x, y);
if(maxString.contains(subString)){
sb1.append(subString + ",");
}
}
}
if(sb1.length() != 0){
int max = 0;
String dest = null;
String[] spilt = sb1.toString().split(",");
for (int i = 0; i < spilt.length; i++) {
if(spilt[i].length() > max){
max = spilt[i].length();
dest = spilt[i];
}
}
return dest;
}
}

return null;
}


}

2.JDK8之前的日期时间API

image-20210717125817612
2.1 java.lang.System类

System类提供的public static long currentTimeMillis()用来返回当时时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。此方法适用于计算时间差

  • 计算世界时间的主要标准有
    • UTC(Coordinated Universal Time)
    • GMT(Greewich MEan Time)
    • CST(Central Standard Time)
2.2 java.util.Date类

表示特定的瞬间,精确到毫秒

  • 构造器
    • Date():使用无参构造器创建的时间可以获取本地当前时间
    • Date(long date):创建指定毫秒数的Date对象
  • 常用方法
    • getTime():返回自1970年1月1日00:00:00 GMT以来此Date对象表示的毫秒数
    • toString():把此Date对象转化为以下的形式String:dow mon dd hh:mm:ss zzz yyy 其中:dow是一周中的某一天(Sun,Mon,Tue,Wed,Thu,Fri,Sat),zzz是标准时间
    • 其他很多方法都过时了
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
package 日期和时间的API;

import org.junit.Test;

import java.util.Date;

public class DateTimeTest {
//1.System类中的cuurentTimeMillis
@Test
public void test1(){
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
//称为时间戳
System.out.println(time);
}

/*
java.util.Data类
|----java.sql.Data类
1.两个构造器的使用
>构造器一:Data():创建一个对应当前时间的Date对象
>构造器二:创建指定毫秒数的Date对象
2.两个方法的使用
>toString();显示当前的年、月、日、时、分、秒
>getTime():获取当前Date对象对应的毫秒数。(时间戳)
3.java.sql.Data对应着数据库中的日期类型变量
>如何实例化
>如何将java.util.Date对象转化为java.sql.Date对象
*/
@Test
public void test2(){
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1);
System.out.println(date1.getTime());//Tue Jul 20 17:16:29 CST 2021

//构造器二:Date(long date)创建指定毫秒数的Date对象
Date date2 = new Date(1626772568900L);
System.out.println(date2.toString());//Tue Jul 20 17:16:08 CST 2021

//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(1626772568900L);
System.out.println(date3);//2021-07-20

//如何将java.util.Date对象转化为java.sql.Date对象
//情况一:多态
Date date4 = new java.sql.Date(1626772568900L);
java.sql.Date date5 = (java.sql.Date)date4;
System.out.println(date5);//2021-07-20
//情况二:间接传递
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
System.out.println(date7);//2021-07-20
}
}

2.3 java.text.SimpleDateFormat类
  • Date类的API不易国际化,大部分被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类
  • 它允许进行格式化:日期->文本、解析:文本->日期
  • 格式化
    • SimpleDateFormat():默认的模式和语言环境创建对象
    • public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:
    • public String format(Date date):方法格式化时间对象date
  • 解析
    • public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期

image-20210717150157636

2.4 java.util.Calendar(日历类)
  • Calendar是一个抽象基类,主要完成日期字段之间相互操作的功能。

  • 获取Calendar的实例方法

    • 使用Calendar.getInstance()方法
    • 调用它的子类GregorianCalendar的构造器
    • image-20210717171045329
    • 一个Calendar的实例是系统时间的抽象表示,通过get(int filed)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND
      • public void set(int filed, int value)
      • public void add(int filed, int amount)
      • public final Date getTime()
      • public final void setTime(Date date)
    • 注意:
      • 获取月份时:一月是0,二月是1,以此类推
      • 获取星期时:周一时是1,周二是2,以此类推
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Calendar calendar = Calendar.getInstance();
    // 从一个 Calendar 对象中获取 Date 对象
    Date date = calendar.getTime();
    // 使用给定的 Date 设置此 Calendar 的时间
    date = new Date(234234235235L);
    calendar.setTime(date);
    calendar.set(Calendar.DAY_OF_MONTH, 8);
    System.out.println("当前时间日设置为8后,时间是:" + calendar.getTime());
    calendar.add(Calendar.HOUR, 2);
    System.out.println("当前时间加2小时后,时间是:" + calendar.getTime());
    calendar.add(Calendar.MONTH, -2);
    System.out.println("当前日期减2个月后,时间是:" + calendar.getTime());
    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
    package 日期和时间的API;

    import org.junit.Test;

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;

    public class DateTimeTest2 {
    /*
    SimpleDateFormat的使用:simpleDateFormat对日期Date类的格式化和解析
    1.两个操作:
    1.1 格式化:日期->字符串
    1.2 解析:格式化的逆过程,字符串->日期
    2.SimpleDateFormat的实例化
    */
    @Test
    public void test1() throws ParseException {
    //实例化SimpleDateFormat:使用默认构造器
    SimpleDateFormat sdf = new SimpleDateFormat();
    //格式化:日期——>字符串
    Date date = new Date();
    System.out.println(date);//Wed Jul 21 14:07:38 CST 2021
    String format = sdf.format(date);
    System.out.println(format);//21-7-21 下午2:07
    //解析:格式化的逆过程,字符串--->日期
    String str = "21-7-21 下午1:57";
    Date date1 = sdf.parse(str);
    System.out.println(date1);//Wed Jul 21 13:57:00 CST 2021

    //**********按照指定的方式格式化和解析:调用带参的构造器
    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    //格式化
    String format1 = sdf1.format(date);
    System.out.println(format1);//2021-07-21 02:07:38
    //解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现)
    Date date2 = sdf1.parse("2021-07-21 14:07:27");
    System.out.println(date2);//Wed Jul 21 14:07:27 CST 2021

    }

    /*
    将字符串"2021-07-21"转换为java.sql.Date
    */
    @Test
    public void test2() throws ParseException {
    String birth = "2021-07-21";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = sdf.parse(birth);
    java.sql.Date date1 = new java.sql.Date(date.getTime());
    System.out.println(date1);
    }

    /*
    Calendar日历(抽象类)的使用
    */
    @Test
    public void test3(){
    //1.实例化
    //方式一:创建其子类(GregorianCalendar)的对象
    //方式二:调用其静态方法getInstance()
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getClass());//class java.util.GregorianCalendar

    //2.常用方法
    //get()
    int days = calendar.get(Calendar.DAY_OF_MONTH);//一个月中的第几天
    System.out.println(days);
    System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//一年中的第几天

    //set() calendar可变性
    calendar.set(Calendar.DAY_OF_MONTH,22);//改变当前时间为本月中的第22天
    days = calendar.get(Calendar.DAY_OF_MONTH);
    System.out.println(days);

    //add()
    calendar.add(Calendar.DAY_OF_MONTH, -1);
    days = calendar.get(Calendar.DAY_OF_MONTH);
    System.out.println(days);

    //getTime():日历类-->Date
    Date date = calendar.getTime();
    System.out.println(date);

    //setTime():Date--->日历类
    Date date1 = new Date();
    calendar.setTime(date1);
    days = calendar.get(Calendar.DAY_OF_MONTH);
    System.out.println(days);
    }
    }

3.JDK8中新增的日期时间API

3.1 产生背景

​ 如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不 过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了 一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用 了。而Calendar并不比Date好多少。它们面临的问题是:
​ 可变性:像日期和时间这样的类应该是不可变的。
​ 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
​ 格式化:格式化只对Date有用,Calendar则不行。
​ 此外,它们也不是线程安全的;不能处理闰秒等。
​ 总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一。

3.2 新时间日期API
  • 第三次引入的API是成功的,并且Java 8中引入的java.time API 已经纠正了 过去的缺陷,将来很长一段时间内它都会为我们服务。

  • Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。 新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间 (LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime) 和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法, 用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简 化了日期时间和本地化的管理。

  • java.time – 包含值对象的基础包
    java.time.chrono – 提供对不同的日历系统的访问
    java.time.format – 格式化和解析时间和日期
    java.time.temporal – 包括底层框架和扩展特性
    java.time.zone – 包含时区支持的类 说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽 管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

  • LocalDate、LocalTime、LocalDateTime类是其中比较重要的类,他们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间,他们提供了人简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息

    • LocalDate代表ISO格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期
    • LocalTime表示一个时间,而不是日期
    • LocalDateTime是用来表示日期和时间的,这是最常用的类之一
  • 注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

image-20210721153315244
3.3 瞬时:instance
  • Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间 戳。
  • 在处理时间和日期的时候,我们通常会想到年,月,日,时,分,秒。然而,这只是 时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连 续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机 处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中, 也是从1970年开始,但以毫秒为单位。
  • java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间 单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒 数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。
  • (1 ns = 10-9 s) 1秒 = 1000毫秒 =10^6微秒=10^9纳秒

image-20210721195756710

image-20210721195824428

3.4 格式化与解析日期或时间
  • 预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  • 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

image-20210721195958023

3.5 其他API
  • ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris

  • ZonedDateTime:一个在ISO-8601日历系统时区的日期时间,如 2007-12- 03T10:15:30+01:00 Europe/Paris。

    • 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如: Asia/Shanghai等
  • Clock:使用时区提供对当前即时、日期和时间的访问的时钟。

  • 持续时间:Duration,用于计算两个“时间”间隔

  • 日期间隔:Period,用于计算两个“日期”间隔

  • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整 到“下一个工作日”等操作。

  • TemporalAdjusters : 该类通过静态方法 (firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用 TemporalAdjuster 的实现。

  • //ZoneId:类中包含了所有的时区信息
    // ZoneId的getAvailableZoneIds():获取所有的ZoneId
    Set<String> zoneIds = ZoneId.getAvailableZoneIds();
    for (String s : zoneIds) {
    System.out.println(s);
    }
    // ZoneId的of():获取指定时区的时间
    LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(localDateTime);
    //ZonedDateTime:带时区的日期时间
    // ZonedDateTime的now():获取本时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime = ZonedDateTime.now();
    System.out.println(zonedDateTime);
    // ZonedDateTime的now(ZoneId id):获取指定时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(zonedDateTime1);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    * ```java
    //Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
    LocalTime localTime = LocalTime.now();
    LocalTime localTime1 = LocalTime.of(15, 23, 32);
    //between():静态方法,返回Duration对象,表示两个时间的间隔
    Duration duration = Duration.between(localTime1, localTime);
    System.out.println(duration);
    System.out.println(duration.getSeconds());
    System.out.println(duration.getNano());
    LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
    LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
    Duration duration1 = Duration.between(localDateTime1, localDateTime);
    System.out.println(duration1.toDays());
  • //Period:用于计算两个“日期”间隔,以年、月、日衡量LocalDate localDate = LocalDate.now();LocalDate localDate1 = LocalDate.of(2028, 3, 18);Period period = Period.between(localDate, localDate1);System.out.println(period);System.out.println(period.getYears());System.out.println(period.getMonths());System.out.println(period.getDays());Period period1 = period.withYears(2);System.out.println(period1);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    * ```java
    // TemporalAdjuster:时间校正器
    // 获取当前日期的下一个周日是哪天?
    TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
    LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
    System.out.println(localDateTime);
    // 获取下一个工作日是哪天?
    LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
    @Override
    public Temporal adjustInto(Temporal temporal) {
    LocalDate date = (LocalDate) temporal;
    if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
    return date.plusDays(3);
    } else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
    return date.plusDays(2);
    } else {
    return date.plusDays(1);
    }
    }
    });
    System.out.println("下一个工作日是:" + localDate);
  • 传统日期的处理与转换

    image-20210721200404286

    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
    package 日期和时间的API;

    import org.junit.Test;

    import java.time.*;
    import java.time.format.DateTimeFormatter;
    import java.time.format.FormatStyle;
    import java.time.temporal.TemporalAccessor;

    public class JDKDateTimeTest {
    /*
    LocalDate、LocalTime、LocalDateTime的使用
    说明:
    1.LocalDateTime相较于LocalDate、LocalTime,使用频率要高
    2.类似于Calendar
    */
    @Test
    public void test1(){
    //now()获取当前时间
    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println("LocalDate: " + localDate);//LocalDate: 2021-07-21
    System.out.println("LocalTime: " + localTime);//LocalTime: 20:35:54.300
    System.out.println("LocalDateTime: " + localDateTime);//LocalDateTime: 2021-07-21T20:35:54.300

    //of()设置指定的年、月、日、时、分、秒,没有偏移量 此处以LocalDateTime为例
    LocalDateTime localDateTime1 = LocalDateTime.of(2021,7,21,20,38,16);
    System.out.println(localDateTime1);//2021-07-21T20:38:16

    //getXxx()获取当前相关的属性
    System.out.println(localDateTime.getDayOfYear());//返回当前是一年中的第几天
    System.out.println(localDateTime.getDayOfMonth());//返回当前是一个月中的第几天
    System.out.println(localDateTime.getDayOfWeek());//返回房前是周几
    System.out.println(localDateTime.getMonthValue());//返回当前月份 7
    System.out.println(localDateTime.getYear());//返回当前年份
    System.out.println(localDateTime.getMonth());//返回当前月份 JULY

    //withXxx()设置相关属性,体现不可变性
    LocalDateTime localDateTime2 = localDateTime.withDayOfMonth(23);
    System.out.println(localDateTime);//2021-07-21T20:48:26.324
    System.out.println(localDateTime2);//2021-07-23T20:48:26.324

    }

    /*
    Instant的使用
    类似于java.util.Date类
    */
    @Test
    public void test2(){
    //now()获取本初子午线对应的标准时间
    Instant instant = Instant.now();
    System.out.println(instant);//2021-07-21T12:50:33.857Z

    //添加时间偏移量 我们在东八区
    OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
    System.out.println(offsetDateTime);

    //toEpochMill()获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---->Date类的getTime()
    long milli = instant.toEpochMilli();
    System.out.println(milli);

    //ofEpochMill()通过给定毫秒数,获取Instant实例--->Date(long millis)
    Instant instant1 = Instant.ofEpochMilli(1626872518532L);
    System.out.println(instant1);
    }

    /*
    DateTimeFormatter:格式化或解析日期、时间
    类似于SimpleDateFormat
    */
    @Test
    public void test3(){
    //方式一:预定义的标准格式。如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);
    System.out.println(localDateTime);//2021-07-21T21:09:21.825
    System.out.println(str1);//2021-07-21T21:09:21.825
    //解析:字符串--->日期
    TemporalAccessor parse = formatter.parse("2021-07-21T21:09:21.825");
    System.out.println(parse);//{},ISO resolved to 2021-07-21T21:09:21.825

    //方式二:本地化相关的格式。如:ofLocalizedDateTime()
    //FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于LocalDateTime
    DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
    //格式化
    String str2 = formatter1.format(localDateTime);
    System.out.println(str2);//2021年7月21日 下午09时18分57秒

    //方式三:自定义格式。如ofPattern("yyyy-MM-dd hh:mm:ss")
    DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    //格式化
    String str4 = formatter2.format(localDateTime);
    System.out.println(str4);//2021-07-21 09:23:38

    }
    }

4.Java比较器

​ 在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间 的比较问题。

​ Java实现对象排序的方式有两种:

​ ①自然排序:java.lang.Comparable ;②定制排序:java.util.Comparator

4.1 方式一:自然排序:java.lang.Comparable
  • Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
  • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即 通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大 于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回 负整数,如果当前对象this等于形参对象obj,则返回零。
  • 实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或 Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有 序集合中的元素,无需指定比较器。
  • 对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。建议(虽然不是必需的)最好使自然排序与 equals 一致。
  • Comparable 的典型实现:(默认都是从小到大排列的)
    • String:按照字符串中字符的Unicode值进行比较
    • Character:按照字符的Unicode值来进行比较
    • 数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值 大小进行比较
    • Boolean:true 对应的包装类实例大于 false 对应的包装类实例
    • Date、Time等:后面的日期时间比前面的日期时间大
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
package Java比较器;

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;
}

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 + '\'' +
", peice=" + 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("传入的数据类型不一致");
}
}

4.2 方式二:定制排序:java.util.Comparator
  • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那 么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排 序的比较。
  • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返 回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。
  • 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort), 从而允许在排序顺序上实现精确控制。
  • 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的 顺序,或者为那些没有自然顺序的对象 collection 提供排序。
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
package Java比较器;

import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;

public class CompareTest {
/*
一、说明:Java中的对象,正常情况下,只能进行比较:== 或!= 、不能使用 > 或 < 的
但是在开发场景中,我们需要对多个对象进行排序
实现方法:使用两个接口中的任何一个:Comparable 或 Comparator
二、Comparable接口与Comparator的使用的对比:
Comparable接口的方式一旦指定,保证Comparable接口实现类的对象在任何位置都可以比较大小
Comparator接口属于临时性的比较

Comparable接口的使用:自然排序
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3.重写compareTo(obj)的规则
如果当前对象this大于形参obj,则返回正整数
如果当前对象this小于形参obj,则返回负数
如果当前对象this等于形参obj,则返回零
4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法,在compareTo(obj)方法中指明如何排序
*/
@Test
public void test1(){
String[] arr = new String[] {"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));//[AA, CC, DD, GG, JJ, KK, MM]
}

@Test
public void test2(){
Goods[] goods = new Goods[5];
goods[0] = new Goods("lenovoMouse",34);
goods[1] = new Goods("dellMouse",43);
goods[2] = new Goods("xiaomiMouse",23);
goods[3] = new Goods("huaweiMouse",65);
goods[4] = new Goods("microsoftMouse",43);

Arrays.sort(goods);
System.out.println(Arrays.toString(goods));
}

/*
Comparator接口的使用:定制排序
1.背景:
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序
2.重写compare(Object o1, Object o2)方法,比较o1和o2的大小
如果重写方法返回正整数,则表示o1大于o2,如果返回负数则o1小于o2,如果返回0,表示相等
*/
@Test
public void test3(){
Goods[] arr = new Goods[6];
arr[0] = new Goods("lenovoMouse",34);
arr[1] = new Goods("dellMouse",43);
arr[2] = new Goods("xiaomiMouse",12);
arr[3] = new Goods("huaweiMouse",65);
arr[4] = new Goods("huaweiMouse",224);
arr[5] = new Goods("microsoftMouse",43);

Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Goods && o2 instanceof Goods) {
Goods goods1 = (Goods) o1;
Goods goods2 = (Goods) o2;
if(goods1.getName().equals(goods2.getName())){
return -Double.compare(goods1.getPrice(),goods2.getPrice());
}else{
return goods1.getName().compareTo(goods2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
}

5. System类

  • System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。

  • 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。

  • 成员变量

    • System类内部包含in、out和err三个成员变量,分别代表标准输入流 (键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
  • 成员方法

    • native long currentTimeMillis():

      该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时 间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。

    • void exit(int status):

      该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表 异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。

    • void gc():

      该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。

    • String getProperty(String key):

      该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:

      image-20210816171447150

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      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 userHome = System.getProperty("user.home");
      System.out.println("user的home:" + userHome);
      String userDir = System.getProperty("user.dir");
      System.out.println("user的dir:" + userDir);

6. Math类

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回 值类型一般为double型。

abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a)
double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度

7. BigInteger与BigDecimal

  • Integer类作为int的包装类,能存储的最大整型值为2^31^-1,Long类也有限, 最大为2^63^-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类 都无能为力,更不用说进行运算了。

  • java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。 另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。

  • 构造器

    • BigInteger(String val):根据字符串构建BigInteger对象
  • 常用方法

    • 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) :返回其值为 (this^exponent^) 的 BigInteger。
  • 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中, 要求数字精度比较高,故用到java.math.BigDecimal类。

  • BigDecimal类支持不可变的、任意精度的有符号十进制定点数。

  • 构造器

    • public BigDecimal(double val)
    • public BigDecimal(String val)
  • 常用方法

    • public BigDecimal add(BigDecimal augend)
    • public BigDecimal subtract(BigDecimal subtrahend)
    • public BigDecimal multiply(BigDecimal multiplicand)
    • public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void testBigInteger() {
    BigInteger bi = new BigInteger("12433241123");
    BigDecimal bd = new BigDecimal("12435.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));
    System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
    }