文章目录
1. String
1.1 String的特性
-
String是final类型对象,不可被继承。且代表不可变的字符序列。当字符串变量值发生改变,会在方法区的字符串常量池中重新开辟空间进行赋值
字符串是常量,存储在方法区的字符串常量池。同一字符串在常量池中独一份。且常量池中已经存在的字符串不容许任何修改(不可变性)。
所以常常出现String类型变量由于内容相同,指向常量池的同一空间。
-
String实现了Serializable接口:表示字符串是支持序列化的。
-
String实现了Comparable接口:表示String可以比较大小
-
String底层使用value数组存储字符串,并且是final类型数组(不可变性)
1.2 String对象创建
String str = "hello";
//本质上this.value = new char[0];
String s1 = new String();
//this.value = original.value;
String s2 = new String(String original);
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);//以数组a下标为startIndex字符开始,长度为count的串构造新的String
String对象的创建有两种方式,第一种是直接赋值,第二种是通过构造器的方式,这两种创建的String对象指向的空间是不同的。第一种是指向(方法区的)字符串常量池,第二种是指向堆空间。但字符串最终实际都是存储在字符串常量池中的。(不同的版本字符串常量池位置不同)
下面来看一段代码说说区别在哪?
//通过字面量定义的方式:此时的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
直接赋值的方式,s1和s2指向字符串常量池。
而构造器创建的方式,s3和s4指向堆空间,堆空间开辟String对象,String对象的value属性存储地址值指向字符串常量池的字符串。也就是说用构造器的方式创建String类在内存中一共创建两个对象图形化来说就是:
总结:字符串实际存储位置是方法区的字符串常量池,但不同方式创建的String对象,可能导致对象值不同(类类型对象村的是地址)
加强理解:
@Test
public void test4(){
String s1="woaibiancheng";
String s2="woai";
String s3=s2+"biancheng";
System.out.println(s1==s3);//false
final String s4="woai";
String s5=s4+"biancheng";
System.out.println(s1==s5);//true
}
为什么?final 修饰的String 变量与未修饰的String变量的区别
在class文件中:
直接看下一节,也有总结!
下面思考一个问题
//Person类有name和age两个属性
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name == p2.name);//false还是true?
关键得看Person类构造器是怎么对name构造的,
第一种name通过String构造器创建的方式—false
public Person(String name,int age){
this.name=new String(name);
this.age=age;
}
第二种name通过直接赋值—true
public Person(String name, int age) {
this.name = name;
this.age = age;
}
总的一句话就是new就是要在String中重新开辟空间。
1.2.1 字符串的特性
常量和常量拼接的返回的是拼接后字符串在方法区的字符串常量池中的地址。如果字符串拼接涉及String类型的变量,那么就相当于new,在堆区开辟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);//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
}
@Test
public void test4(){
String s1="woaibiancheng";
String s2="woai";
String s3=s2+"biancheng";
System.out.println(s1==s3);//false
final String s4="woai";
String s5=s4+"biancheng";
System.out.println(s1==s5);//true
}
1.2.2 intern()
如果常量池中有对应字符串,则直接返回该字符串的引用。如果
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
String s = new String("1") + new String("1");
String s2 = s.intern();
String s1 = "11";
System.out.println(s == s1);
System.out.println(s2 == s1);
System.out.println(s2 == s);
// true,true,true
1.2.3 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。
1.3 String常用方法
-
int length()
:返回字符串的长度 -
char charAt(int index)
: 返回某索引处的字符 -
boolean isEmpty()
:判断是否是空字符串 -
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)
:比较两个字符串的大小A.compareTo(anotherString)
:定义下标k,从前向后逐个比较字符大小- 如果有字母差异,返回
this.charAt(k)-anotherString.charAt(k)
。 - 当k到达较短字符串末尾,字母均相同,返回
this.length()-anotherString.length()
总结:逐个按照字母比较,字母小的放前面。如果无法出结果,字符串短的放前面
如果返回负数,则当前字符串小。如果返回正数,则当前字符串大。(用于字符串排序)
- 如果有字母差异,返回
-
String substring(int beginIndex)
:返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个字符的子字符串。 -
String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。(左闭右开) -
boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束(指定的后缀可以是多个字符) -
boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始(指定的后缀可以是多个字符) -
boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的子字符串是否以指定前缀开始 -
boolean contains(CharSequence s)
:当且仅当此字符串包含指定的 char 值序列时,返回 true -
int indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引,没有则返回-1 -
int indexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始搜查 -
int lastIndexOf(String str)
:返回指定子字符串在此字符串中最右边出现处的索引 -
int lastIndexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索注:indexOf和lastIndexOf方法如果未找到都是返回-1
-
String replace(char oldChar, char newChar)
:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 -
String replace(CharSequence target, CharSequence replacement)
:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。 -
String replaceAll(String regex, String replacement)
: 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。String str = "12hello34world5java7891mysql456"; //把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉 String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", ""); System.out.println(string);
-
String replaceFirst(String regex, String replacement)
: 使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 -
boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式。常用于匹配输入是否符合预定格式
String str = "12345"; //判断str字符串中是否全部有数字组成,即有1-n个数字组成 boolean matches = str.matches("\\d+"); System.out.println(matches); String tel = "0571-4534289"; //判断这是否是一个杭州的固定电话 boolean result = tel.matches("0571-\\d{7,8}"); System.out.println(result);
-
String[] split(String regex)
:根据给定正则表达式的匹配拆分此字符串。String str = "hello|world|java"; String[] strs = str.split("\\|"); for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); } System.out.println(); String str2 = "hello.world.java"; String[] strs2 = str2.split("\\."); for (int i = 0; i < strs2.length; i++) { System.out.println(strs2[i]); }
-
String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
1.4 String类型转换
1.4.1 String与包装类
我建议基本数据类型直接当包装类处理,这样只需要记住包装类和String之间的转换了,也就是toString和包装类的构造器,就能完成三者之间的转换了
1.4.2 String与char[]数组
-
字符数组–>字符串
调用String 类的构造器:
String(char[])和String(char[],int offset,int length)
分别用字符数组中的全部字符和部分字符创建字符串对象。 -
字符串–>字符数组
public char[] toCharArray()
:将字符串每个字符转化为字符数组中的元素public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
:提供了将指定索引范围内的字符串存放到数组中的方法。
1.4.3 String与Byte[]数组
涉及字符集----
-
Byte[]数组–>字符串
String(byte[])
:通过使用平台的默认字符集解码指定的 byte 数组,构造 String。String(byte[],int offset,int length)
:从字节数组起始位置offset开始取length个字节构造一个字符串对象。 -
字符串–>Byte[]数组
public byte[] getBytes()
:使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。public byte[] getBytes(String charsetName)
:使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码,将字符串转化为Bytes数组。
System.out.println("Arrays.toString(bytes) = " + Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码,将字符串转化为Bytes数组。
System.out.println("Arrays.toString(gbks) = " + Arrays.toString(gbks));
String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println("str2 = " + str2);
String str3 = new String(gbks);
System.out.println("str3 = " + str3);//出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, "gbk");
System.out.println("str4 = " + str4);//没有出现乱码。原因:编码集和解码集一致!
}
输出结果:
Arrays.toString(bytes) = [97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
Arrays.toString(gbks) = [97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
str2 = abc123中国
str3 = abc123�й�
str4 = abc123中国
2. StringBuffer与StringBuilder
String表示的是不可变字符序列,而StringBuffer和StringBuilder代表可变的字符序列。
StringBuffer和StringBuilder几乎一致,唯一区别就是线程是否安全
先总体了解String、StringBuffer、StringBuilder三者的导同(字符序列是否可变+线程是否安全)
- String:不可变的字符序列。效率最差
- StringBuffer: 可变的字符序列。线程安全教率低(内部方法有synchronized修饰)
- StringBuilder:可变的字符疗列。线程不安全效率高(JDK5.0新增)
三者都使用底层使用value字符数组存储。三者的value数组是否有final修饰决定对应字符串是否可变。String的value字符数组为不可变数组,有final修饰。StringBuffer和StringBuilder的value字符数组没有final修饰。不可变字符序列就是每次堆字符串修改都需要重新开辟空间,反之就是可变字符序列(有时候要重新开辟,有时候不用,下面源码分析有讲哦)
下面用代码来比较效率–
/*
对比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));
}
2.1 字符串构造器源码
创建不同的字符序列的底层–
//String按需开辟
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
//StringBuffer和StringBuilder后面具体分析
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//0 相当于输出有效长度
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
字符串的构造,String是按需开辟空间。StringBuffer和StringBuilder构造器创建字符串会预留16个字符空间。(因为他们可以指定开辟多少空间)
下面我们分析StringBuffer源码
对于无参构造:
以String为参构造:
StringBuffer和StringBuilder构造器首次创建会开辟当前所需空间+16的空间
当空间不够了咋整–看append源码
append功能是尾插字符串,每次插入检查空间是否足够:
ensurecapacityInternal
, 空间不够则开辟更大的空间复制数组,然后将尾插的字符串尾插上去。
这里看看新开辟的空间的大小的讲究:newCapacity 一般来说扩容一倍再加2,如果还不够就直接按照新的字符串长度来开辟(第一个if)接着return语句,newCapacity可能通过移位运算超出范围,成为负数等等在对新容量进行考虑
总结扩容问题:如果要添加的数据底层数组容量不足,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
由于append每次扩容开销大所以:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
回答StringBuffer和Stringbuilder内存解析主要从构造器开辟空间和append函数两个方面来谈
2.2 构造器
二者构造器类似
StringBuffer() StringBuilder()
:初始容量为16的字符串缓冲区StringBuffer(int size) StringBuilder(int size)
:构造指定容量的字符串缓冲区StringBuffer(String str) StringBuilder(String str)
:将内容初始化为指定字符串内容
为了避免频繁扩容,我们常使用第二个指定空间创造可变字符序列
2.3 常用方法
这里只列举比String类多出来的方法(当然个别string特有的方法StringBuffer和StringBuilder没有)
-
StringBuffer append(xxx)
:提供了很多重载的append()方法,用于进行字符串拼接,任何类型都按照字符面值尾插,甚至Boolean类型append中返回值是return this,所以支持方法链的使用,
-
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方法
2.4 StringBuffer Stringbuilder String 之间转化
-
StringBuffer,StringBuilder转化为String:调用String的构造器或调用StringBuffer,Stringbuilder的toString()函数返回String值
-
String转化为Stringbuffer,StringBuilder:调用StringBuffer,Stringbuilder的构造器
-
可见StringBuffer和StringBuilder之间的转化完全可以以String之间为桥梁
2.4 面试题
append源码分析
程序输出
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);
结果:
第一个append(str)。源码中如果插入的字符串为空就尾插null,null四个字符,所以长度为4,相应的第二个输出null
第三个空指针异常,看源码:
2.6 算法题
- 将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”–分析方法的性能内存
public class StringDemo {
/*
将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”
*/
//方式一:转换为char[]
public String reverse(String str,int startIndex,int endIndex){
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的拼接
public String reverse1(String str,int startIndex,int endIndex){
if(str != null){
//第1部分
String reverseStr = str.substring(0,startIndex);
//第2部分
for(int i = endIndex;i >= startIndex;i--){
reverseStr += str.charAt(i);
}
//第3部分
reverseStr += str.substring(endIndex + 1);
return reverseStr;
}
return null;
}
//方式三:使用StringBuffer/StringBuilder替换String
public String reverse2(String str,int startIndex,int endIndex){
if(str != null){
StringBuilder builder = new StringBuilder(str.length());
//第1部分
builder.append(str.substring(0,startIndex));
//第2部分
for(int i = endIndex;i >= startIndex;i--){
builder.append(str.charAt(i));
}
//第3部分
builder.append(str.substring(endIndex + 1));
return builder.toString();
}
return null;
}
@Test
public void testReverse(){
String str = "abcdefg";
String reverse = reverse2(str, 2, 5);
System.out.println(reverse);
}
}
3. JDK8之前日期时间API
这里只简述用的多的
3.1 java.lang.System类
System类提供的public static long currentTimeMillis()
用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
计算世界时间的主要标准有:
- UTC(Coordinated Universal Time)
- GMT(Greenwich Mean Time)
- CST(Central Standard Time)
3.2 java.util.Date类
表示特定的瞬间,精确到毫秒(类似currentTimeMillis()获取毫秒数)
构造器:
-
Date()
:使用无参构造器创建的对象可以获取本地当前时间。 -
Date(long date)
:创建指定毫秒时间的Date对象
常用方法
getTime()
:返回自1970年1月1日00:00:00 GMT 以来此 Date 对象表示的毫秒数。类似currentTimeMillis()获取毫秒数toString()
:把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。- 其它很多方法都过时deprecated了。
3.3 java.text.SimpleDateFormat类
Date类和指定日期字符串之间的转化,由SimpleDateFormat类提供方法实现
主要功能:
format格式化:日期(Date)–>文本(字符串)
parse解析:文本(字符串)–> 日期(Date)
格式化**:**
-
SimpleDateFormat()
:默认的模式和语言环境创建对象 -
SimpleDateFormat(String pattern)
:用参数pattern指定的格式创建一个对象(后续对象实例就能识别此格式日期)。具体含义:
该对象调用:public String format(Date date):方法格式化时间对象date
解析:
- public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期(由于可能存在字符串source非法,所以方法会抛出异常)
@Test
public void test1(){
Date date= new Date(); // 产生一个Date实例
//产生一个formater格式化的实例
SimpleDateFormat formater= new SimpleDateFormat();
System.out.println(formater.format(date));// 打印输出默认的格式
SimpleDateFormat formater2= new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z");
System.out.println(formater2.format(date));
try{
// 实例化一个指定的格式对象
Date date2= formater2.parse("2023.01.17 公元 at 17:57:06 CST");
// 将指定的日期解析后格式化按指定的格式输出
System.out.println(date2.toString());
} catch(ParseException e) {
e.printStackTrace();
}
}
3.4 java.util.Calendar(日历)类
Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
获取Calendar实例的方法(抽象类不能直接new)
- 调用静态方法Calendar.getInstance()方法(多态,获得子类的对象实例)
- 调用它的子类GregorianCalendar的构造器。
常用的函数:
-
public int get(int field)
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想
要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、
MINUTE、SECOND
-
public void set(int field, int value):将field属性修改成value
-
abstract public void add(int field, int amount):将field属性修改成amount
-
public final Date getTime()
-
public final void setTime(Date date)
里面的field是抽象类Calendar类里定义的属性,具体看文档
获取月份时:一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2,以此类推,周六是7
/*
Calendar日历类(抽象类)的使用
*/
@Test
public void testCalendar(){
//1.实例化
//方式一:创建其子类(GregorianCalendar)的对象
//方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
//System.out.println(calendar.getClass());
//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);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
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);
}
4. JDK8之后日期时间API
如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。(弃用的构造器构造器)
格式化:格式化只对Date有用,Calendar则不行。(SimpleDateFormat类)
此外,它们也不是线程安全的;不能处理闰秒等。第三次引入的API是成功的,并且Java 8中引入的java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。
Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理
新的日期API主要有
- java.time - 包含值对象的基础包
- java.time.chrono – 提供对不同的日历系统的访问
- java.time.format – 格式化和解析时间和日期
- java.time.temporal – 包括底层框架和扩展特性
- java.time.zone – 包含时区支持的类
说明:大多数开发者只会用到基础包和 format 包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
4.1 LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。
它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
- LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。
- LocalTime表示一个时间,而不是日期。
- LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历
常用的方法:
构造相应的事件类,用now(),of(),当然每个类有相应的构造器。获取相应时间类用getXxx,修改实践类用withXxx,加plusXxx,减用minusXxx,具体用法很细,使用时看API文档即可
@Test
public void test1(){
//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);
//of():设置指定的年、月、日、时、分、秒。没有偏移量。LocalTime,LocalDate的of用法类似
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1);
//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
System.out.println(localDateTime.getMonth());//月份
System.out.println(localDateTime.getMonthValue());//月份值
System.out.println(localDateTime.getMinute());
//体现不可变性,类比Calendar可变性
//withXxx():设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate);
System.out.println(localDate1);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime);
System.out.println(localDateTime2);
//不可变性
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
System.out.println(localDateTime);
System.out.println(localDateTime3);
LocalDateTime localDateTime4 = localDateTime.minusDays(6);
System.out.println(localDateTime);
System.out.println(localDateTime4);
}
4.2 Instant
Instant为时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。
- 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。
java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位(年月日)。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。
概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。
常用方法:
/*
Instant的使用
类似于 java.util.Date类
*/
@Test
public void test2(){
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);//2019-02-18T07:29:41.719Z
//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00
//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);
//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
System.out.println(instant1);
}
4.3 DateTimeFormatter
类比JDK1.8 以前的SimpleDateFormat
该类定义在java.time.format,提供了格式化与解析日期或时间的方法,实例化方式有:
- 预定义的标准格式有:
ISO_LOCAL_DATE_TIME; ISO_LOCAL_DATE; ISO_LOCAL_TIME
(创建预定义格式的DateTimeFormater,实际上都是DateTimeFormatter类定义好的实例对象)- 本地化相关的格式。如:
ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如:
ofPattern(“yyyy-MM-dd hh:mm:ss”)
/*
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);
System.out.println(str1);//2019-02-18T15:42:18.797---类型变了,输出没变
//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);
//方式二:
//本地化相关的格式。如:ofLocalizedDateTime()
//预定义的格式:FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2);//2019年2月18日 下午03时47分16秒00
//本地化相关的格式。如:ofLocalizedDate()
//预定义的格式:FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
//重点:方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
}
4.4 其他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 的实现。
@Test
public void test4(){
//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);
}
@Test
public void test4(){
//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());
}
@Test
public void test4(){
//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);
}
@Test
public void test4(){
// 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);
}
4.5 与传统API日期处理的转换
5. Java比较器
Java给对象的排序提供两种方式(接口)
java.lang.Comparable
:一劳永逸,规定类的比较规则java.util.Comparator
:临时定义的比较规则,往往作为参数传递
5.1 java.lang.Comparable
Comparable 的典型实现:(默认都是从小到大排列的)
-
String:按照字符串中字符的Unicode值进行比较(从左到右逐个比较)
-
Character:按照字符的Unicode值来进行比较
-
数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
-
Boolean:true 对应的包装类实例大于 false 对应的包装类实例
-
Date、Time等:后面的日期时间比前面的日期时间大
对于没有实现Comparable接口的类咋不能进行比较
class Goods implements Comparable {
private String name;
private double price;
//按照价格,比较商品的大小
@Override
public int compareTo(Object o) {
if(o instanceof Goods) {
Goods other = (Goods) o;
if (this.price > other.price) {
return 1;
} else if (this.price < other.price) {
return -1;
}
return 0;
}
throw new RuntimeException("输入的数据类型不一致");
}
}
5.2 java.util.Comparator
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
Comparator是临时定制的一种排序规则,不同于Comparable的一劳永逸的排序定义方式,Comparator往往作为参数将比较的规则信息传递。
Comparator中很多方法,我们重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
-
可以将 Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺上实现精确控制。
-
还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
@Test
public void test4(){
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 g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if(g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
6. 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的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
@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 userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);
}
7. Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。
7.1 求最大值最小值和绝对值
7.2 求整运算
7.3 三角函数运算
7.4 指数运算
8. BigInteger
Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的,最大为263-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)
:返回其值为 (thisexponent) 的 BigInteger。
9. BigDecimal
一般的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)
@Test
public void test2(){
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));
}