第10章_常用类和基础API

150 阅读31分钟

学习目标

  • 熟练掌握String类的API
  • 熟练掌握StringBuilder和StringBuffer类的API
  • 熟悉日期时间API
  • 掌握比较器的使用
  • 了解数学相关API
  • 了解系统类API
  • 掌握数组基础算法

1. 字符串相关类之不可变字符序列:String

1.1 String的特性

  • java.lang.String 类代表字符串。Java程序中所有的字符串文字(例如"abc" )都可以看作是实现此类的实例。

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

  • 字符串String类型本身是final声明的,意味着我们不能继承String。

  • String对象的字符内容是存储在一个字符数组value[]中的。"abc" 等效于 char[] data={'a','b','c'}

    //jdk8中的String源码:
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[]; //String对象的字符内容是存储在此数组中
     
        /** Cache the hash code for the string */
        private int hash; // Default to 0
    
    
    • private意味着外面无法直接获取字符数组,而且String没有提供value的get和set方法。

    • final意味着字符数组的引用不可改变,而且String也没有提供方法来修改value数组某个元素值

    • 因此字符串的字符数组内容也不可变的,即String代表着不可变的字符序列。即,一旦对字符串进行修改,就会产生新对象。

    • JDK9只有,底层使用byte[]数组。

      public final class String implements java.io.Serializable, Comparable<String>, CharSequence { 
          @Stable
          private final byte[] value;
      }
      //官方说明:... that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.
      //细节:... The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.
      
  • Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持(toString()方法)。

1.2 String的内存结构

1.2.1 概述

因为字符串对象设计为不可变,那么所以字符串有常量池来保存很多常量对象。

JDK6中,字符串常量池在方法区。JDK7开始,就移到堆空间,直到目前JDK17版本。举例内存结构分配:

image-20220405160036240

image-20220405160029300

1.2.2 练习类型1:拼接

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
// 内存中只有一个"hello"对象被创建,同时被s1和s2共享。

对应内存结构为:(以下内存结构以JDK6为例绘制):

进一步:

Person p1 = new Person();
p1.name = “Tom";

Person p2 = new Person();
p2.name = “Tom";

System.out.println(p1.name .equals( p2.name)); //
System.out.println(p1.name == p2.name); //
System.out.println(p1.name == “Tom"); //

1.2.3 练习类型2:new

String str1 = “abc”; 与 String str2 = new String(“abc”);的区别?

str2 首先指向堆中的一个字符串对象,然后堆中字符串的value数组指向常量池中常量对象的value数组。

  • 字符串常量存储在字符串常量池,目的是共享。

  • 字符串非常量对象存储在堆中。

练习:

String s1 = "javaEE";
String s2 = "javaEE";
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

练习:String str2 = new String("hello"); 在内存中创建了几个对象?

两个

1.2.4 练习类型3:intern()

  • 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。

练习:

String s1 = "hello";
String s2 = "world";
String s3 = "hello" + "world";
String s4 = s1 + "world";
String s5 = s1 + s2;
String s6 = (s1 + s2).intern();

System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s4 == s5);
System.out.println(s3 == s6);

结论:

(1)常量+常量:结果是常量池。且常量池中不会存在相同内容的常量。

(2)常量与变量 或 变量与变量:结果在堆中

(3)拼接后调用intern方法:返回值在常量池中

练习:

@Test
public void test01(){
	String s1 = "hello";
	String s2 = "world";
	String s3 = "helloworld";
		
	String s4 = s1 + "world";//s4字符串内容也helloworld,s1是变量,"world"常量,变量 + 常量的结果在堆中
	String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是变量,变量 + 变量的结果在堆中
	String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
		
	System.out.println(s3 == s4);//false
	System.out.println(s3 == s5);//false
	System.out.println(s3 == s6);//true
}

@Test
public void test02(){
	final String s1 = "hello";
	final String s2 = "world";
	String s3 = "helloworld";
	
	String s4 = s1 + "world";//s4字符串内容也helloworld,s1是常量,"world"常量,常量+常量结果在常量池中
	String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是常量,常量+ 常量 结果在常量池中
	String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
		
	System.out.println(s3 == s4);//true
	System.out.println(s3 == s5);//true
	System.out.println(s3 == s6);//true
}

@Test
public void test01(){
	String s1 = "hello";
	String s2 = "world";
	String s3 = "helloworld";
		
	String s4 = (s1 + "world").intern();//把拼接的结果放到常量池中
	String s5 = (s1 + s2).intern();
		
	System.out.println(s3 == s4);//true
	System.out.println(s3 == s5);//true
}

练习:下列程序运行的结果:

public class TestString {
	public static void main(String[] args) {
		String str = "hello";
		String str2 = "world";
		String str3 ="helloworld";
		
		String str4 = "hello".concat("world");
		String str5 = "hello"+"world";
		
		System.out.println(str3 == str4);//false
		System.out.println(str3 == str5);//true
	}
}

concat方法拼接,哪怕是两个常量对象拼接,结果也是在堆。

练习:下列程序运行的结果:

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.print(ex.str + " and ");//
        System.out.println(ex.ch);
    }
}

1.3 String的常用API-1

1.3.1 构造器

  • public String() :初始化新创建的 String对象,以使其表示空字符序列。
  • String(String original): 初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
  • public String(char[] value) :通过当前参数中的字符数组来构造新的String。
  • public String(char[] value,int offset, int count) :通过字符数组的一部分来构造新的String。
  • public String(byte[] bytes) :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。
  • public String(byte[] bytes,String charsetName) :通过使用指定的字符集解码当前参数中的字节数组来构造新的String。

举例:

//字符串常量对象
String str = "hello";

// 无参构造
String str1 = new String();

//创建"hello"字符串常量的副本
String str2 = new String("hello");

//通过字符数组构造
char chars[] = {'a', 'b', 'c','d','e'};     
String str3 = new String(chars);
String str4 = new String(chars,0,3);

// 通过字节数组构造
byte bytes[] = {97, 98, 99 };     
String str5 = new String(bytes);
String str6 = new String(bytes,"GBK");

1.3.2 静态方法实现实例化

  • static String copyValueOf(char[] data): 返回指定数组中表示该字符序列的 String
  • static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String
  • static String valueOf(char[] data) : 返回指定数组中表示该字符序列的 String
  • static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String
  • static String valueOf(xx value):xx支持各种数据类型,返回各种数据类型的value参数的字符串表示形式。
public static void main(String[] args) {
	char[] data = {'h','e','l','l','o','j','a','v','a'};
	String s1 = String.copyValueOf(data);
	String s2 = String.copyValueOf(data,0,5);
	int num = 123456;
	String s3 = String.valueOf(num);
	
    System.out.println(s1);
	System.out.println(s2);
	System.out.println(s3);
}

1.3.3 字符串对象的比较

1、==:比较是对象的地址

只有两个字符串变量都是指向字符串的常量对象时,才会返回true

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);//true
    
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str1 == str4); //false
System.out.println(str3 == str4); //false

2、equals:比较是对象的内容,因为String类型重写equals,区分大小写

只要两个字符串的字符内容相同,就会返回true

String str1 = "hello";
String str2 = "hello";
System.out.println(str1.equals(str2));//true
    
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str1.equals(str3));//true
System.out.println(str3.equals(str4));//true

3、equalsIgnoreCase:比较的是对象的内容,不区分大小写

String str1 = new String("hello");
String str2 = new String("HELLO");
System.out.println(str1.equalsIgnoreCase(strs)); //true

4、compareTo:String类型重写了Comparable接口的抽象方法,自然排序,按照字符的Unicode编码值进行比较大小的,严格区分大小写

String str1 = "hello";
String str2 = "world";
str1.compareTo(str2) //小于0的值

5、compareToIgnoreCase:不区分大小写,其他按照字符的Unicode编码值进行比较大小

String str1 = new String("hello");
String str2 = new String("HELLO");
str1.compareToIgnoreCase(str2)  //等于0

1.3.4 空字符的比较

1、哪些是空字符串

String str1 = "";
String str2 = new String();
String str3 = new String("");

空字符串:长度为0

2、如何判断某个字符串是否是空字符串

if("".equals(str))

if(str!=null  && str.isEmpty())

if(str!=null && str.equals(""))

if(str!=null && str.length()==0)

1.3.5 String与其他结构间的转换

字符串 --> 基本数据类型、包装类:

  • Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
  • 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。

基本数据类型、包装类 --> 字符串:

  • 调用String类的public String valueOf(int n)可将int型转换为字符串
  • 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类型到字符串的转换。

字符数组 --> 字符串:

  • String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。

字符串 --> 字符数组:

  • public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。

  • public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

字节数组 --> 字符串:(解码)

  • String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
  • String(byte[],int offset,int length) :用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。
  • String(byte[],字符编码方式 ) 或 new String(byte[], int, int,字符编码方式):解码,按照指定的编码方式进行解码。

字符串 --> 字节数组:(编码)

  • public byte[] getBytes() :使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
  • public byte[] getBytes(String charsetName) :使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

代码示例:

	@Test
    public void test01() throws Exception {
        String str = "中国";
        System.out.println(str.getBytes("ISO8859-1").length);// 2
        // ISO8859-1把所有的字符都当做一个byte处理,处理不了多个字节
        System.out.println(str.getBytes("GBK").length);// 4 每一个中文都是对应2个字节
        System.out.println(str.getBytes("UTF-8").length);// 6 常规的中文都是3个字节

        /*
         * 不乱码:(1)保证编码与解码的字符集名称一样(2)不缺字节
         */
        System.out.println(new String(str.getBytes("ISO8859-1"), "ISO8859-1"));// 乱码
        System.out.println(new String(str.getBytes("GBK"), "GBK"));// 中国
        System.out.println(new String(str.getBytes("UTF-8"), "UTF-8"));// 中国
    }

	@Test
    public void test02()throws Exception{
        byte[] data = {(byte)0B11100101, (byte)0B10110000, (byte)0B10011010, (byte)0B11000111, (byte)0B10101011,(byte)0B01110110};
        System.out.println(new String(data,"ISO8859-1"));
        System.out.println(new String(data,"GBK"));
        System.out.println(new String(data,"UTF-8"));
    }

1.4 String的常用API-2

String 类包括的方法可用于检查序列的单个字符、比较字符串、搜索字符串、提取子字符串、创建字符串副本并将所有字符全部转换为大写或小写。

1.4.1 系列1:常用方法

(1)boolean isEmpty():字符串是否为空

(2)int length():返回字符串的长度

(3)String concat(xx):拼接,理解为 +

(4)boolean equals(Object obj):比较字符串是否相等,区分大小写

(5)boolean equalsIgnoreCase(Object obj):比较字符串是否相等,不区分大小写

(6)int compareTo(String other):比较字符串大小,区分大小写,按照Unicode编码值比较大小

(7)int compareToIgnoreCase(String other):比较字符串大小,不区分大小写

(8)String toLowerCase():将字符串中大写字母转为小写

(9)String toUpperCase():将字符串中小写字母转为大写

(10)String trim():去掉字符串前后空白符

(11)public String intern():结果在常量池中共享

	@Test
	public void test01(){
		//将用户输入的单词全部转为小写,如果用户没有输入单词,重新输入
		Scanner input = new Scanner(System.in);
		String word;
		while(true){
			System.out.print("请输入单词:");
			word = input.nextLine();
			if(word.trim().length()!=0){
				word = word.toLowerCase();
				break;
			}
		}
		System.out.println(word);
	}

	@Test
	public void test02(){
        //随机生成验证码,验证码由0-9,A-Z,a-z的字符组成
		char[] array = new char[26*2+10];
		for (int i = 0; i < 10; i++) {
			array[i] = (char)('0' + i);
		}
		for (int i = 10,j=0; i < 10+26; i++,j++) {
			array[i] = (char)('A' + j);
		}
		for (int i = 10+26,j=0; i < array.length; i++,j++) {
			array[i] = (char)('a' + j);
		}
		String code = "";
		Random rand = new Random();
		for (int i = 0; i < 4; i++) {
			code += array[rand.nextInt(array.length)];
		}
		System.out.println("验证码:" + code);
		//将用户输入的单词全部转为小写,如果用户没有输入单词,重新输入
		Scanner input = new Scanner(System.in);
		System.out.print("请输入验证码:");
		String inputCode = input.nextLine();
		
		if(!code.equalsIgnoreCase(inputCode)){
			System.out.println("验证码输入不正确");
		}
	}

1.4.2 系列2:查找

(11)boolean contains(xx):是否包含xx

(12)int indexOf(xx):从前往后找当前字符串中xx,即如果有返回第一次出现的下标,要是没有返回-1

(13)int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始

(14)int lastIndexOf(xx):从后往前找当前字符串中xx,即如果有返回最后一次出现的下标,要是没有返回-1

(15)int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。

	@Test
	public void test01(){
		String str = "尚硅谷是一家靠谱的培训机构,尚硅谷可以说是IT培训的小清华,JavaEE是尚硅谷的当家学科,尚硅谷的大数据培训是行业独角兽。尚硅谷的前端和运维专业一样独领风骚。";
		System.out.println("是否包含清华:" + str.contains("清华"));
		System.out.println("培训出现的第一次下标:" + str.indexOf("培训"));
		System.out.println("培训出现的最后一次下标:" + str.lastIndexOf("培训"));
	}

1.4.3 系列3:字符串截取

(16)String substring(int beginIndex) :返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。

(17)String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

	@Test
	public void test01(){
		String str = "helloworldjavaatguigu";
		String sub1 = str.substring(5);
		String sub2 = str.substring(5,10);
		System.out.println(sub1);
		System.out.println(sub2);
	}

	@Test
	public void test02(){
		String fileName = "快速学习Java的秘诀.dat";
		//截取文件名
		System.out.println("文件名:" + fileName.substring(0,fileName.lastIndexOf(".")));
		//截取后缀名
		System.out.println("后缀名:" + fileName.substring(fileName.lastIndexOf(".")));
	}

1.4.4 系列4:和字符相关

(18)char charAt(index):返回[index]位置的字符

(19)char[] toCharArray(): 将此字符串转换为一个新的字符数组返回

(20)String(char[] value):返回指定数组中表示该字符序列的 String。

(21)String(char[] value, int offset, int count):返回指定数组中表示该字符序列的 String。

(22)static String copyValueOf(char[] data): 返回指定数组中表示该字符序列的 String

(23)static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String

(24)static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String

(25)static String valueOf(char[] data) :返回指定数组中表示该字符序列的 String

	@Test
	public void test01(){
		//将字符串中的字符按照大小顺序排列
		String str = "helloworldjavaatguigu";
		char[] array = str.toCharArray();
		Arrays.sort(array);
		str = new String(array);
		System.out.println(str);
	}
	
	@Test
	public void test02(){
		//将首字母转为大写
		String str = "jack";
		str = Character.toUpperCase(str.charAt(0))+str.substring(1);
		System.out.println(str);
	}

1.4.5 系列5:开头与结尾

(26)boolean startsWith(xx):测试此字符串是否以指定的前缀开始

(27)boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

(28)boolean endsWith(xx):测试此字符串是否以指定的后缀结束

	@Test
	public void test2(){
		String name = "张三";
		System.out.println(name.startsWith("张"));
	}
	
	@Test
	public void test(){
		String file = "Hello.txt";
		if(file.endsWith(".java")){
			System.out.println("Java源文件");
		}else if(file.endsWith(".class")){
			System.out.println("Java字节码文件");
		}else{
			System.out.println("其他文件");
		}
	}

1.4.6 系列6:正则匹配

(29)boolean matchs(正则表达式):判断当前字符串是否匹配某个正则表达式。(正则表达式见附录8.2)

	@Test
	public void test1(){
		//简单判断是否全部是数字,这个数字可以是1~n位
		String str = "12a345";
		
		//正则不是Java的语法,它是独立与Java的规则
		//在正则中\是表示转义,
		//同时在Java中\也是转义
		boolean flag = str.matches("\\d+");
		System.out.println(flag);
	}
	
	@Test
	public void test2(){
		String str = "123456789";
		
		//判断它是否全部由数字组成,并且第1位不能是0,长度为9位
		//第一位不能是0,那么数字[1-9]
		//接下来8位的数字,那么[0-9]{8}+
		boolean flag = str.matches("[1-9][0-9]{8}+");
		System.out.println(flag);
	}

	@Test
    public void test03(){
        //密码要求:必须有大写字母,小写字母,数字组成,6位
        System.out.println("Cly892".matches("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[A-Za-z0-9]{6}$"));//true
        System.out.println("1A2c45".matches("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[A-Za-z0-9]{6}$"));//true
        System.out.println("Clyyyy".matches("^(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{6}$"));//false
        /*
        (1)密码的长度为6,且只能有[A-Za-z0-9]组成。
        (2)另外,三个非捕获组都能匹配到自己的值。
        (?=.*[A-Z]):匹配值  C
        (?=.*[a-z]):匹配值  Clyya
        (?=.*[0-9]):匹配值  Clyya1
        三个非捕获组都有值。
         */
    }

1.4.7 系列7:替换

(30)String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 不支持正则。

(31)String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

(32)String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

(33)String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

	@Test
	public void test4(){
		String str1 = "hello244world.java;887";
		//把其中的非字母去掉
		str1 = str1.replaceAll("[^a-zA-Z]", "");
		System.out.println(str1);
        
        String str2 = "12hello34world5java7891mysql456";
		//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
		String string = str2.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
		System.out.println(string);

	}

1.4.8 系列8:拆分

(34)String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

(35)String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

	@Test
	public void test1(){
		String str = "Hello World java atguigu";
		String[] all = str.split(" ");
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i]);
		}
	}	

	@Test
	public void test2(){
		String str = "1Hello2World3java4atguigu";
		str = str.replaceFirst("\\d", "");
		System.out.println(str);
		String[] all = str.split("\\d");
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i]);
		}
	}

	@Test
	public void test3(){
		String str = "1Hello2World3java4atguigu5";
		str = str.replaceAll("^\\d|\\d$", "");
		String[] all = str.split("\\d");
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i]);
		}
	}

	@Test
	public void test4(){
		String str = "张三.23|李四.24|王五.25";
		//|在正则中是有特殊意义,我这里要把它当做普通的|
		String[] all = str.split("\\|");
		
		//转成一个一个学生对象
		Student[] students = new Student[all.length];
		for (int i = 0; i < students.length; i++) {
			//.在正则中是特殊意义,我这里想要表示普通的.
			String[] strings = all[i].split("\\.");//张三,  23
			String name = strings[0];
			int age = Integer.parseInt(strings[1]);
			students[i] = new Student(name,age);
		}
		
		for (int i = 0; i < students.length; i++) {
			System.out.println(students[i]);
		}	
	}

1.5 常见算法题目

**题目1:**模拟一个trim方法,去除字符串两端的空格。

	public String myTrim(String str) {
		if (str != null) {
			int start = 0;// 用于记录从前往后首次索引位置不是空格的位置的索引
			int end = str.length() - 1;// 用于记录从后往前首次索引位置不是空格的位置的索引

			while (start < end && str.charAt(start) == ' ') {
				start++;
			}

			while (start < end && str.charAt(end) == ' ') {
				end--;
			}
			if (str.charAt(start) == ' ') {
				return "";
			}

			return str.substring(start, end + 1);
		}
		return null;
	}

	@Test
	public void testMyTrim() {
		String str = "   a   ";
		// str = " ";
		String newStr = myTrim(str);
		System.out.println("---" + newStr + "---");
	}

**题目2:**将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”

	// 方式一:
	public String reverse1(String str, int start, int end) {// start:2,end:5
		if (str != null) {
			// 1.
			char[] charArray = str.toCharArray();
			// 2.
			for (int i = start, j = end; i < j; i++, j--) {
				char temp = charArray[i];
				charArray[i] = charArray[j];
				charArray[j] = temp;
			}
			// 3.
			return new String(charArray);

		}
		return null;

	}

	// 方式二:
	public String reverse2(String str, int start, int end) {
		// 1.
		String newStr = str.substring(0, start);// ab
		// 2.
		for (int i = end; i >= start; i--) {
			newStr += str.charAt(i);
		} // abfedc
			// 3.
		newStr += str.substring(end + 1);
		return newStr;
	}

	// 方式三:推荐 (相较于方式二做的改进)
	public String reverse3(String str, int start, int end) {// ArrayList list = new ArrayList(80);
		// 1.
		StringBuffer s = new StringBuffer(str.length());
		// 2.
		s.append(str.substring(0, start));// ab
		// 3.
		for (int i = end; i >= start; i--) {
			s.append(str.charAt(i));
		}

		// 4.
		s.append(str.substring(end + 1));

		// 5.
		return s.toString();

	}

	@Test
	public void testReverse() {
		String str = "abcdefg";
		String str1 = reverse3(str, 2, 5);
		System.out.println(str1);// abfedcg

	}

**题目3:**获取一个字符串在另一个字符串中出现的次数。 比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数

	// 第3题
	// 判断str2在str1中出现的次数
	public int getCount(String mainStr, String subStr) {
		if (mainStr.length() >= subStr.length()) {
			int count = 0;
			int index = 0;
			// while((index = mainStr.indexOf(subStr)) != -1){
			// count++;
			// mainStr = mainStr.substring(index + subStr.length());
			// }
			// 改进:
			while ((index = mainStr.indexOf(subStr, index)) != -1) {
				index += subStr.length();
				count++;
			}

			return count;
		} else {
			return 0;
		}

	}

	@Test
	public void testGetCount() {
		String str1 = "cdabkkcadkabkebfkabkskab";
		String str2 = "ab";
		int count = getCount(str1, str2);
		System.out.println(count);
	}

**题目4:**获取两个字符串中最大相同子串。比如: str1 = "abcwerthelloyuiodef“;str2 = "cvhellobnm" 提示:将短的那个串进行长度依次递减的子串与较长的串比较。

	// 第4题
	// 如果只存在一个最大长度的相同子串
	public String getMaxSameSubString(String str1, String str2) {
		if (str1 != null && str2 != null) {
			String maxStr = (str1.length() > str2.length()) ? str1 : str2;
			String minStr = (str1.length() > str2.length()) ? str2 : str1;

			int len = minStr.length();

			for (int i = 0; i < len; i++) {// 0 1 2 3 4 此层循环决定要去几个字符

				for (int x = 0, y = len - i; y <= len; x++, y++) {

					if (maxStr.contains(minStr.substring(x, y))) {

						return minStr.substring(x, y);
					}

				}

			}
		}
		return null;
	}

	// 如果存在多个长度相同的最大相同子串
	// 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
	public String[] getMaxSameSubString1(String str1, String str2) {
		if (str1 != null && str2 != null) {
			StringBuffer sBuffer = new StringBuffer();
			String maxString = (str1.length() > str2.length()) ? str1 : str2;
			String minString = (str1.length() > str2.length()) ? str2 : str1;

			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)) {
						sBuffer.append(subString + ",");
					}
				}
				System.out.println(sBuffer);
				if (sBuffer.length() != 0) {
					break;
				}
			}
			String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
			return split;
		}

		return null;
	}
	// 如果存在多个长度相同的最大相同子串:使用ArrayList
//	public List<String> getMaxSameSubString1(String str1, String str2) {
//		if (str1 != null && str2 != null) {
//			List<String> list = new ArrayList<String>();
//			String maxString = (str1.length() > str2.length()) ? str1 : str2;
//			String minString = (str1.length() > str2.length()) ? str2 : str1;
//
//			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)) {
//						list.add(subString);
//					}
//				}
//				if (list.size() != 0) {
//					break;
//				}
//			}
//			return list;
//		}
//
//		return null;
//	}

	@Test
	public void testGetMaxSameSubString() {
		String str1 = "abcwerthelloyuiodef";
		String str2 = "cvhellobnmiodef";
		String[] strs = getMaxSameSubString1(str1, str2);
		System.out.println(Arrays.toString(strs));
	}

**题目5:**对字符串中字符进行自然顺序排序。 提示: 1)字符串变成字符数组。 2)对数组排序,选择,冒泡,Arrays.sort(); 3)将排序后的数组变成字符串。

	// 第5题
	@Test
	public void testSort() {
		String str = "abcwerthelloyuiodef";
		char[] arr = str.toCharArray();
		Arrays.sort(arr);

		String newStr = new String(arr);
		System.out.println(newStr);
	}

2. 字符串相关类之可变字符序列:StringBuffer、StringBuilder

因为String对象是不可变对象,虽然可以共享常量对象,但是对于频繁字符串的修改和拼接操作,效率极低,空间消耗也比较高。因此,JDK又在java.lang包提供了可变字符序列StringBuilder和StringBuffer类型。

2.1 StringBuffer与StringBuilder的理解

  • java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。比如:

    //情况1:
    String s = new String("我喜欢学习"); 
    //情况2:
    StringBuffer buffer = new StringBuffer("我喜欢学习"); 
    buffer.append("数学"); 
    

    image-20220405221714261

  • 作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。

  • 继承结构:

image-20220405174233055

  • StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样。
  • 区分String、StringBuffer、StringBuilder
    • String:不可变的字符序列; 底层使用char[]数组存储
    • StringBuffer:可变的字符序列;线程安全(方法有synchronized修饰),效率低;底层使用char[]数组存储
    • StringBuilder:可变的字符序列; jdk1.5引入,线程不安全的,效率高;底层使用char[]数组存储

2.2 StringBuilder、StringBuffer的API

StringBuilder、StringBuffer的API是完全一致的,并且很多方法与String相同。

1、常用API

(1)StringBuffer append(xx):提供了很多的append()方法,用于进行字符串追加的方式拼接

(2)StringBuffer delete(int start, int end):删除[start,end)之间字符

(3)StringBuffer deleteCharAt(int index):删除[index]位置字符

(4)StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列为str

(5)void setCharAt(int index, xx):替换[index]位置字符

(6)char charAt(int index):查找指定index位置上的字符

(7)StringBuffer insert(int index, xx):在[index]位置插入xx

(8)int length():返回存储的字符数据的长度

(9)StringBuffer reverse():反转

  • 当append和insert时,如果原来value数组长度不够,可扩容。

  • 如上(1)(2)(3)(4)(9)这些方法支持方法链操作。原理:

    image-20220405223542750

2、其它API

(1)int indexOf(String str):在当前字符序列中查询str的第一次出现下标

(2)int indexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str的第一次出现下标

(3)int lastIndexOf(String str):在当前字符序列中查询str的最后一次出现下标

(4)int lastIndexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str的最后一次出现下标

(5)String substring(int start):截取当前字符序列[start,最后]

(6)String substring(int start, int end):截取当前字符序列[start,end)

(7)String toString():返回此序列中数据的字符串表示形式

(8)void setLength(int newLength) :设置当前字符序列长度为newLength

	@Test
	public void test1(){
		StringBuilder s = new StringBuilder();
		s.append("hello").append(true).append('a').append(12).append("atguigu");
		System.out.println(s);
		System.out.println(s.length());
	}
	
	@Test
	public void test2(){
		StringBuilder s = new StringBuilder("helloworld");
		s.insert(5, "java");
		s.insert(5, "chailinyan");
		System.out.println(s);
	}

	@Test
	public void test3(){
		StringBuilder s = new StringBuilder("helloworld");
		s.delete(1, 3);
		s.deleteCharAt(4);
		System.out.println(s);
	}
	@Test
	public void test4(){
		StringBuilder s = new StringBuilder("helloworld");
		s.reverse();
		System.out.println(s);
	}

	@Test
	public void test5(){
		StringBuilder s = new StringBuilder("helloworld");
		s.setCharAt(2, 'a');
		System.out.println(s);
	}

	@Test
	public void test6(){
		StringBuilder s = new StringBuilder("helloworld");
		s.setLength(30);
		System.out.println(s);
	}

2.3 效率测试

//初始设置
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.4 练习

笔试题:程序输出:

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);//

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

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

  • 构造器:
    • Date():使用无参构造器创建的对象可以获取本地当前时间。
    • Date(long 毫秒数):把该毫秒值换算成日期时间对象
  • 常用方法
    • getTime(): 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
    • toString(): 把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。
    • 其它很多方法都过时了。
  • 举例:
	@Test
	public void test1(){
		Date d = new Date();
		System.out.println(d);
	}

	@Test
	public void test2(){
		long time = System.currentTimeMillis();
		System.out.println(time);//1559806982971
		//当前系统时间距离1970-1-1 0:0:0 0毫秒的时间差,毫秒为单位
	}

	@Test
	public void test3(){
		Date d = new Date();
		long time = d.getTime();
		System.out.println(time);//1559807047979
	}

	@Test
	public void test4(){
		long time = 1559807047979L;
		Date d = new Date(time);
		System.out.println(d);
	}

	@Test
	public void test5(){
		long time = Long.MAX_VALUE;
		Date d = new Date(time);
		System.out.println(d);
	}

3.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):从给定字符串的开始解析文本,以生成一个日期。

1572599023197

	//格式化
	@Test
	public void test9(){
		Date d = new Date();

		SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒  E Z");
		//把Date日期转成字符串,按照指定的格式转
		String str = sf.format(d);
		System.out.println(str);
	}
	//解析
	@Test
	public void test10() throws ParseException{
		String str = "2019年06月06日 16时03分14秒 545毫秒  星期四 +0800";
		SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒  E Z");
		Date d = sf.parse(str);
		System.out.println(d);
	}
	

3.4 java.util.Calendar(日历)

  • Calendar 类是一个抽象类,主用用于完成日期字段之间相互操作的功能。

  • 获取Calendar实例的方法

    • 使用Calendar.getInstance()方法

      image-20220123184906903

    • 调用它的子类GregorianCalendar的构造器。

      image-20220405225828816

  • 一个Calendar的实例是系统时间的抽象表示,可以修改或获取 YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND等 日历字段对应的时间值。

    • public void set(int field,int value)
    • public int get(int field)
    • public void add(int field,int amount)
    • public final Date getTime()
    • public final void setTime(Date date)
  • 注意:

    • 获取月份时:一月是0,二月是1,以此类推,12月是11
    • 获取星期时:周日是1,周二是2 , 。。。。周六是7
  • 示例代码:

import org.junit.Test;

import java.util.Calendar;
import java.util.TimeZone;

public class TestCalendar {
    @Test
    public void test1(){
        Calendar c = Calendar.getInstance();
        System.out.println(c);

        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH)+1;
        int day = c.get(Calendar.DATE);
        int hour = c.get(Calendar.HOUR_OF_DAY);
        int minute = c.get(Calendar.MINUTE);

        System.out.println(year + "-" + month + "-" + day + " " + hour + ":" + minute);
    }

    @Test
    public void test2(){
        TimeZone t = TimeZone.getTimeZone("America/Los_Angeles");
        Calendar c = Calendar.getInstance(t);
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH)+1;
        int day = c.get(Calendar.DATE);
        int hour = c.get(Calendar.HOUR_OF_DAY);
        int minute = c.get(Calendar.MINUTE);

        System.out.println(year + "-" + month + "-" + day + " " + hour + ":" + minute);
    }
    
    @Test
    public void test3(){
        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());  
    }
}

4. JDK8:新的日期时间API

如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:

  • 可变性:像日期和时间这样的类应该是不可变的。

  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始。

  • 格式化:格式化只对Date有用,Calendar则不行。

  • 此外,它们也不是线程安全的;不能处理闰秒等。

    闰秒,是指为保持协调世界时接近于世界时时刻,由国际计量局统一规定在年底或年中(也可能在季末)对协调世界时增加或减少1秒的调整。由于地球自转的不均匀性和长期变慢性(主要由潮汐摩擦引起的),会使世界时(民用时)和原子时之间相差超过到±0.9秒时,就把协调世界时向前拨1秒(负闰秒,最后一分钟为59秒)或向后拨1秒(正闰秒,最后一分钟为61秒); 闰秒一般加在公历年末或公历六月末。

    目前,全球已经进行了27次闰秒,均为正闰秒。

总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一。

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

Java 8 以一个新的开始为 Java 创建优秀的 API。新的日期时间API包含:

  • java.time – 包含值对象的基础包
  • java.time.chrono – 提供对不同的日历系统的访问。
  • java.time.format – 格式化和解析时间和日期
  • java.time.temporal – 包括底层框架和扩展特性
  • java.time.zone – 包含时区支持的类

说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

比如,新的 java.time 中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。

4.1 本地日期时间:LocalDate、LocalTime、LocalDateTime

方法描述
now() / now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear()获得月份(1-12) /获得年份
getHours()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
with(TemporalAdjuster t)将当前日期时间设置为校对器指定的日期时间
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时
plus(TemporalAmount t)/minus(TemporalAmount t)添加或减少一个 Duration 或 Period
isBefore()/isAfter()比较两个 LocalDate
isLeapYear()判断是否是闰年(在LocalDate类中声明)
format(DateTimeFormatter t)格式化本地日期、时间,返回一个字符串
parse(Charsequence text)将指定格式的字符串解析为日期、时间
import org.junit.Test;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class TestLocalDateTime {
    @Test
    public void test7(){
        LocalDate now = LocalDate.now();
        LocalDate before = now.minusDays(100);
        System.out.println(before);//2019-02-26
    }

    @Test
    public void test06(){
        LocalDate lai = LocalDate.of(2019, 5, 13);
        LocalDate go = lai.plusDays(160);
        System.out.println(go);//2019-10-20
    }

    @Test
    public void test05(){
        LocalDate lai = LocalDate.of(2019, 5, 13);
        System.out.println(lai.getDayOfYear());
    }


    @Test
    public void test04(){
        LocalDate lai = LocalDate.of(2019, 5, 13);
        System.out.println(lai);
    }

    @Test
    public void test03(){
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
    }

    @Test
    public void test02(){
        LocalTime now = LocalTime.now();
        System.out.println(now);
    }

    @Test
    public void test01(){
        LocalDate now = LocalDate.now();
        System.out.println(now);
    }
}

4.2 瞬时:Instant

  • 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纳秒

方法描述
now()静态方法,返回默认UTC时区的Instant类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个 OffsetDateTime
toEpochMilli()返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

中国大陆、中国香港、中国澳门、中国台湾、蒙古国、新加坡、马来西亚、菲律宾、西澳大利亚州的时间与UTC的时差均为+8,也就是UTC+8。

instant.atOffset(ZoneOffset.ofHours(8));

image-20220406000442908

整个地球分为二十四时区,每个时区都有自己的本地时间。在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated)。UTC与格林尼治平均时(GMT, Greenwich Mean Time)一样,都与英国伦敦的本地时相同。这里,UTC与GMT含义完全相同。

北京时区是东八区,领先UTC八个小时,在电子邮件信头的Date域记为+0800。如果在电子邮件的信头中有这么一行:

Date: Fri, 08 Nov 2002 09:42:22 +0800

说明信件的发送地的地方时间是二○○二年十一月八号,星期五,早上九点四十二分(二十二秒),这个地方的本地时领先UTC八个小时(+0800, 就是东八区时间)。电子邮件信头的Date域使用二十四小时的时钟,而不使用AM和PM来标记上下午。

以这个电子邮件的发送时间为例,如果要把这个时间转化为UTC,可以使用一下公式:

UTC + 时区差 = 本地时间

时区差东为正,西为负。在此,把东八区时区差记为 +0800,

UTC + (+0800) = 本地(北京)时间 (1)

那么,UTC = 本地时间(北京时间)- 0800 (2)

0942 - 0800 = 0142

即UTC是当天凌晨一点四十二分二十二秒。

4.3 日期时间格式化:DateTimeFormatter

该类提供了三种格式化方法:

  • 预定义的标准格式。如:ISO_LOCAL_DATE_TIME、ISO_LOCAL_DATE、ISO_LOCAL_TIME

  • 本地化相关的格式。如:ofLocalizedDate(FormatStyle.LONG)

    // 本地化相关的格式。如:ofLocalizedDateTime()
    // FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
    				
    // 本地化相关的格式。如:ofLocalizedDate()
    // FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
    
  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

ofPattern(String pattern)静态方法,返回一个指定字符串格式的DateTimeFormatter
format(TemporalAccessor t)格式化一个日期、时间,返回字符串
parse(CharSequence text)将指定格式的字符序列解析为一个日期、时间

举例:

import org.junit.Test;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;

public class TestDatetimeFormatter {
    @Test
    public void test1(){
        LocalDateTime now = LocalDateTime.now();
//		DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);//2019年6月6日 下午04时40分03秒
//        DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);//2019-6-6 16:40:37
//        DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);//19-6-6 下午4:40
        DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withZone(ZoneId.systemDefault());
        String str = df.format(now);
        System.out.println(str);
    }
    
    @Test
    public void test2(){
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;//2019-06-06T16:38:23.756
        String str = df.format(now);
        System.out.println(str);
    }

    @Test
    public void test3(){
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒  SSS毫秒  E 是这一年的D天");
        String str = df.format(now);
        System.out.println(str);
    }
}

4.4 其它API

1、指定时区日期时间:ZondId和ZonedDateTime

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

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

    • 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如:Asia/Shanghai等
  • 常见时区ID:

Asia/Shanghai
UTC
America/New_York
  • 可以通过ZondId获取所有可用的时区ID:
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Set;

public class TestZone {
    @Test
    public void test01() {
        //需要知道一些时区的id
        //Set<String>是一个集合,容器
        Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        //快捷模板iter
        for (String availableZoneId : availableZoneIds) {
            System.out.println(availableZoneId);
        }
    }

    @Test
    public void test02(){
        ZonedDateTime t1 = ZonedDateTime.now();
        System.out.println(t1);

        ZonedDateTime t2 = ZonedDateTime.now(ZoneId.of("America/New_York"));
        System.out.println(t2);
    }
}

2、持续日期/时间:Period和Duration

  • 持续时间:Duration,用于计算两个“时间”间隔
  • 日期间隔:Period,用于计算两个“日期”间隔
import org.junit.Test;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;

public class TestPeriodDuration {
    @Test
    public void test01(){
        LocalDate t1 = LocalDate.now();
        LocalDate t2 = LocalDate.of(2018, 12, 31);
        Period between = Period.between(t1, t2);
        System.out.println(between);

        System.out.println("相差的年数:"+between.getYears());
        System.out.println("相差的月数:"+between.getMonths());
        System.out.println("相差的天数:"+between.getDays());
        System.out.println("相差的总数:"+between.toTotalMonths());
    }

    @Test
    public void test02(){
        LocalDateTime t1 = LocalDateTime.now();
        LocalDateTime t2 = LocalDateTime.of(2017, 8, 29, 0, 0, 0, 0);
        Duration between = Duration.between(t1, t2);
        System.out.println(between);

        System.out.println("相差的总天数:"+between.toDays());
        System.out.println("相差的总小时数:"+between.toHours());
        System.out.println("相差的总分钟数:"+between.toMinutes());
        System.out.println("相差的总秒数:"+between.getSeconds());
        System.out.println("相差的总毫秒数:"+between.toMillis());
        System.out.println("相差的总纳秒数:"+between.toNanos());
        System.out.println("不够一秒的纳秒数:"+between.getNano());
    }
    @Test
    public void test03(){
        //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);

    }
}

3、Clock:使用时区提供对当前即时、日期和时间的访问的时钟。

4、

TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。 TemporalAdjusters : 该类通过静态方法(firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用 TemporalAdjuster 的实现。

@Test
public void test1(){
    // 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 与传统日期处理的转换

To 遗留类From 遗留类
java.time.Instant与java.util.DateDate.from(instant)date.toInstant()
java.time.Instant与java.sql.TimestampTimestamp.from(instant)timestamp.toInstant()
java.time.ZonedDateTime与java.util.GregorianCalendarGregorianCalendar.from(zonedDateTime)cal.toZonedDateTime()
java.time.LocalDate与java.sql.TimeDate.valueOf(localDate)date.toLocalDate()
java.time.LocalTime与java.sql.TimeDate.valueOf(localDate)date.toLocalTime()
java.time.LocalDateTime与java.sql.TimestampTimestamp.valueOf(localDateTime)timestamp.toLocalDateTime()
java.time.ZoneId与java.util.TimeZoneTimezone.getTimeZone(id)timeZone.toZoneId()
java.time.format.DateTimeFormatter与java.text.DateFormatformatter.toFormat()

5. Java比较器

我们知道基本数据类型的数据(除boolean类型外)需要比较大小的话,之间使用比较运算符即可,但是引用数据类型是不能直接使用比较运算符来比较大小的。那么,如何解决这个问题呢?

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

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

    • 自然排序:java.lang.Comparable
    • 定制排序:java.util.Comparator

5.1 自然排序:java.lang.Comparable

  • Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
  • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。
package java.lang;

public interface Comparable{
    int compareTo(Object 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等:后面的日期时间比前面的日期时间大
  • 代码示例:

package com.atguigu.api;

public class Student implements Comparable {
    private int id;
    private String name;
    private int score;
    private int age;

    public Student(int id, String name, int score, int age) {
        this.id = id;
        this.name = name;
        this.score = score;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", score=" + score +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        //这些需要强制,将o对象向下转型为Student类型的变量,才能调用Student类中的属性
        //默认按照学号比较大小
        Student stu = (Student) o;
        return this.id - stu.id;
    }
}

测试类

package com.atguigu.api;

public class TestStudent {
    public static void main(String[] args) {
        Student[] arr = new Student[5];
        arr[0] = new Student(3,"张三",90,23);
        arr[1] = new Student(1,"熊大",100,22);
        arr[2] = new Student(5,"王五",75,25);
        arr[3] = new Student(4,"李四",85,24);
        arr[4] = new Student(2,"熊二",85,18);

        //单独比较两个对象
        System.out.println(arr[0].compareTo(arr[1]));
        System.out.println(arr[1].compareTo(arr[2]));
        System.out.println(arr[2].compareTo(arr[2]));

        System.out.println("所有学生:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("按照学号排序:");
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].compareTo(arr[j+1])>0){
                    Student temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

再举例:


public class Student implements Comparable {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        return this.score - ((Student)o).score;
    }
}

测试:

@Test
public void test02() {
	Student[] students = new Student[3];
	students[0] = new Student("张三", 96);
	students[1] = new Student("李四", 85);
	students[2] = new Student("王五", 98);

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

再举例:

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("输入的数据类型不一致");
    }
    //构造器、getter、setter、toString()方法略
}


测试:

public class ComparableTest{
    public static void main(String[] args) {

        Goods[] all = new Goods[4];
        all[0] = new Goods("《红楼梦》", 100);
        all[1] = new Goods("《西游记》", 80);
        all[2] = new Goods("《三国演义》", 140);
        all[3] = new Goods("《水浒传》", 120);

        Arrays.sort(all);

        System.out.println(Arrays.toString(all));

    }

}

5.2 定制排序:java.util.Comparator

  • 思考?
    • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码(例如:一些第三方的类,你只有.class文件,没有源文件)
    • 如果一个类,实现了Comparable接口,也指定了两个对象的比较大小的规则,但是此时此刻我不想按照它预定义的方法比较大小,但是我又不能随意修改,因为会影响其他地方的使用,怎么办?
    • JDK在设计类库之初,也考虑到这种情况了,所以又增加了一个java.util.Comparator接口。强行对多个对象进行整体排序的比较。
  • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。
package java.util;

public interface Comparator{
    int compare(Object o1,Object o2);
}
  • 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。

举例:

package com.atguigu.api;

import java.util.Comparator;
//定义定制比较器类
public class StudentScoreComparator implements Comparator { 
    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        int result = s1.getScore() - s2.getScore();
        return result != 0 ? result : s1.getId() - s2.getId();
    }
}

测试类

package com.atguigu.api;

public class TestStudent {
    public static void main(String[] args) {
        Student[] arr = new Student[5];
        arr[0] = new Student(3,"张三",90,23);
        arr[1] = new Student(1,"熊大",100,22);
        arr[2] = new Student(5,"王五",75,25);
        arr[3] = new Student(4,"李四",85,24);
        arr[4] = new Student(2,"熊二",85,18);

        //单独比较两个对象
        System.out.println(arr[0].compareTo(arr[1]));
        System.out.println(arr[1].compareTo(arr[2]));
        System.out.println(arr[2].compareTo(arr[2]));

        System.out.println("所有学生:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("按照学号排序:");
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].compareTo(arr[j+1])>0){
                    Student temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        System.out.println("按照成绩排序");
        StudentScoreComparator sc = new StudentScoreComparator();
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(sc.compare(arr[j],arr[j+1])>0){
                    Student temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

再举例:

@Test
public void test02() {
	Student[] students = new Student[3];
	students[0] = new Student("张三", 96);
	students[1] = new Student("李四", 85);
	students[2] = new Student("王五", 98);

	System.out.println(Arrays.toString(students));
	//定制排序
	Arrays.sort(students, new Comparator() {
		@Override
		public int compare(Object o1, Object o2) {
			return Collator.getInstance(Locale.CHINA).compare(((Student) o1).getName(), ((Student) o2).getName());
		}
	});
	System.out.println(Arrays.toString(students));
}

再举例:

Goods[] all = new Goods[4];
all[0] = new Goods("War and Peace", 100);
all[1] = new Goods("Childhood", 80);
all[2] = new Goods("Scarlet and Black", 140);
all[3] = new Goods("Notre Dame de Paris", 120);

Arrays.sort(all, new Comparator() {

    @Override
    public int compare(Object o1, Object o2) {
        Goods g1 = (Goods) o1;
        Goods g2 = (Goods) o2;

        return g1.getName().compareTo(g2.getName());
    }
});

System.out.println(Arrays.toString(all));

6. 系统相关类

6.1 java.lang.System类

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

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

  • 成员变量 Scanner scan = new Scanner(System.in);

    • 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-20220406003340258

  • 举例

import org.junit.Test;

public class TestSystem {
    @Test
    public void test01(){
        long time = System.currentTimeMillis();
        System.out.println("现在的系统时间距离1970年1月1日凌晨:" + time + "毫秒");

        System.exit(0);

        System.out.println("over");//不会执行
    }

    @Test
    public void test02(){
        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);
    }

    @Test
    public void test03() throws InterruptedException {
        for (int i=1; i <=10; i++){
            MyDemo my = new MyDemo(i);
            //每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
        }

        //为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
        System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
        //调用这句代码,会让GC尽快来工作。
        Thread.sleep(5000);
    }
}

class MyDemo{
    private int value;

    public MyDemo(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "MyDemo{" + "value=" + value + '}';
    }

    //重写finalize方法,让大家看一下它的调用效果
    @Override
    protected void finalize() throws Throwable {
//        正常重写,这里是编写清理系统内存的代码
//        这里写输出语句是为了看到finalize()方法被调用的效果
        System.out.println(this+ "轻轻的走了,不带走一段代码....");
    }
}
  • static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):

    从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。常用于数组的插入和删除

import org.junit.Test;

import java.util.Arrays;

public class TestSystemArrayCopy {
    @Test
    public void test01(){
        int[] arr1 = {1,2,3,4,5};
        int[] arr2 = new int[10];
        System.arraycopy(arr1,0,arr2,3,arr1.length);
        System.out.println(Arrays.toString(arr1));
        System.out.println(Arrays.toString(arr2));
    }

    @Test
    public void test02(){
        int[] arr = {1,2,3,4,5};
        System.arraycopy(arr,0,arr,1,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

    @Test
    public void test03(){
        int[] arr = {1,2,3,4,5};
        System.arraycopy(arr,1,arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
}

6.2 java.lang.Runtime类

每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。 应用程序不能创建自己的 Runtime 类实例。

public static Runtime getRuntime(): 返回与当前 Java 应用程序相关的运行时对象。

public long totalMemory():返回 Java 虚拟机中的内存总量。此方法返回的值可能随时间的推移而变化,这取决于主机环境。

public long freeMemory():回 Java 虚拟机中的空闲内存量。调用 gc 方法可能导致 freeMemory 返回值的增加。

package com.atguigu.system;

public class TestRuntime {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        long totalMemory = runtime.totalMemory();
        String str = "";
        for (int i = 0; i < args.length; i++) {
            str += i;
        }
        long freeMemory = runtime.freeMemory();
        System.out.println("总内存:" + totalMemory);
        System.out.println("空闲内存:" + freeMemory);
        System.out.println("已用内存:" + (totalMemory-freeMemory));
    }
}

7. 和数学相关的类

7.1 java.lang.Math

java.lang.Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。类似这样的工具类,其所有方法均为静态方法,并且不会创建对象,调用起来非常简单。

  • public static double abs(double a) :返回 double 值的绝对值。
double d1 = Math.abs(-5); //d1的值为5
double d2 = Math.abs(5); //d2的值为5
  • public static double ceil(double a) :返回大于等于参数的最小的整数。
double d1 = Math.ceil(3.3); //d1的值为 4.0
double d2 = Math.ceil(-3.3); //d2的值为 -3.0
double d3 = Math.ceil(5.1); //d3的值为 6.0
  • public static double floor(double a) :返回小于等于参数最大的整数。
double d1 = Math.floor(3.3); //d1的值为3.0
double d2 = Math.floor(-3.3); //d2的值为-4.0
double d3 = Math.floor(5.1); //d3的值为 5.0
  • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)
long d1 = Math.round(5.5); //d1的值为6.0
long d2 = Math.round(5.4); //d2的值为5.0
  • public static double pow(double a,double b):返回a的b幂次方法
  • public static double sqrt(double a):返回a的平方根
  • public static double random():返回[0,1)的随机值
  • public static final double PI:返回圆周率
  • public static double max(double x, double y):返回x,y中的最大值
  • public static double min(double x, double y):返回x,y中的最小值
  • 其它:acos,asin,atan,cos,sin,tan 三角函数
double result = Math.pow(2,31);
double sqrt = Math.sqrt(256);
double rand = Math.random();
double pi = Math.PI;

7.2 java.math包

7.2.1 BigInteger

  • 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。
	@Test
	public void test01(){
//		long bigNum = 123456789123456789123456789L;
		
		BigInteger b1 = new BigInteger("123456789123456789123456789");
		BigInteger b2 = new BigInteger("78923456789123456789123456789");
		
//		System.out.println("和:" + (b1+b2));//错误的,无法直接使用+进行求和
		
		System.out.println("和:" + b1.add(b2));
		System.out.println("减:" + b1.subtract(b2));
		System.out.println("乘:" + b1.multiply(b2));
		System.out.println("除:" + b2.divide(b1));
		System.out.println("余:" + b2.remainder(b1));
	}

7.2.2 RoundingMode枚举类

CEILING :向正无限大方向舍入的舍入模式。

DOWN :向零方向舍入的舍入模式。

FLOOR:向负无限大方向舍入的舍入模式。

HALF_DOWN :向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。

HALF_EVEN:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。

HALF_UP:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。

UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。 UP:远离零方向舍入的舍入模式。

7.2.3 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 test02(){
		/*double big = 12.123456789123456789123456789;
		System.out.println("big = " + big);*/
		
		BigDecimal b1 = new BigDecimal("123.45678912345678912345678912345678");
		BigDecimal b2 = new BigDecimal("7.8923456789123456789123456789998898888");
		
//		System.out.println("和:" + (b1+b2));//错误的,无法直接使用+进行求和
		
		System.out.println("和:" + b1.add(b2));
		System.out.println("减:" + b1.subtract(b2));
		System.out.println("乘:" + b1.multiply(b2));
		System.out.println("除:" + b1.divide(b2,20,RoundingMode.UP));//divide(BigDecimal divisor, int scale, int roundingMode)
		System.out.println("除:" + b1.divide(b2,20,RoundingMode.DOWN));//divide(BigDecimal divisor, int scale, int roundingMode)
		System.out.println("余:" + b1.remainder(b2));
	}
  • 举例
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));
}

7.3 java.util.Random

用于产生随机数

  • boolean nextBoolean():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 boolean 值。

  • void nextBytes(byte[] bytes):生成随机字节并将其置于用户提供的 byte 数组中。

  • double nextDouble():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的 double 值。

  • float nextFloat():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的 float 值。

  • double nextGaussian():返回下一个伪随机数,它是取自此随机数生成器序列的、呈高斯(“正态”)分布的 double 值,其平均值是 0.0,标准差是 1.0。

  • int nextInt():返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。

  • int nextInt(int n):返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值。

  • long nextLong():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 long 值。

	@Test
	public void test03(){
		Random r = new Random();
		System.out.println("随机整数:" + r.nextInt());
		System.out.println("随机小数:" + r.nextDouble());
		System.out.println("随机布尔值:" + r.nextBoolean());
	}

8. 附录(了解)

8.1 字符编码的发展

8.1.1 ASCII码

计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还可以做更多的事,例如文本处理。但由于计算机只识“数”,因此人们必须告诉计算机哪个数字来代表哪个特定字符,例如65代表字母‘A’,66代表字母‘B’,以此类推。但是计算机之间字符-数字的对应关系必须得一致,否则就会造成同一段数字在不同计算机上显示出来的字符不一样。因此美国国家标准协会ANSI制定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是ASCII字符集(Character Set),也称ASCII码。

那时候的字符编解码系统非常简单,就是简单的查表过程。其中:

  • 0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)
  • 32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

8.1.2 OEM字符集的衍生

当计算机开始发展起来的时候,人们逐渐发现,ASCII字符集里那可怜的128个字符已经不能再满足他们的需求了。人们就在想,一个字节能够表示的数字(编号)有256个,而ASCII字符只用到了0x00~0x7F,也就是占用了前128个,后面128个数字不用白不用,因此很多人打起了后面这128个数字的主意。可是问题在于,很多人同时有这样的想法,但是大家对于0x80-0xFF这后面的128个数字分别对应什么样的字符,却有各自的想法。这就导致了当时销往世界各地的机器上出现了大量各式各样的OEM字符集。不同的OEM字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历résumés给职员乙,结果职员乙看到的却是r?sum?s,因为é字符在职员甲机器上的OEM字符集中对应的字节是0x82,而在职员乙的机器上,由于使用的OEM字符集不同,对0x82字节解码后得到的字符却是?。

8.1.3 多字节字符集(MBCS)和中文字符集

上面我们提到的字符集都是基于单字节编码,也就是说,一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题,因为他们通过扩展第8个比特,就可以得到256个字符了,足够用了。但是对于亚洲国家来说,256个字符是远远不够用的。因此这些国家的人为了用上电脑,又要保持和ASCII字符集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集(Muilti-Bytes Charecter Set)。例如中国使用的就是双字节字符集编码。

例如目前最常用的中文字符集GB2312,涵盖了所有简体字符以及一部分其他字符;GBK(K代表扩展的意思)则在GB2312的基础上加入了对繁体字符等其他非简体字符。这两个字符集的字符都是使用1-2个字节来表示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候,如果遇到字节的最高位是0的话,那么就使用936代码页中的第1张码表进行解码,这就和单字节字符集的编解码方式一致了。如果遇到字节的最高位是1的话,那么就表示需要两个字节值才能对应一个字符。

1563199557136

8.1.4 ANSI标准、国家标准、ISO标准

不同ASCII衍生字符集的出现,让文档交流变得非常困难,因此各种组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码(注意,我们现在通常说到ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK),ISO组织制定的各种ISO标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的GBK,GB2312和GB18030。

操作系统在发布的时候,通常会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例如你用GB2312字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,我们也可以在一台机器上阅读多个国家不同语言的文档了,前提是本机必须安装该文档使用的字符集。

8.1.5 Unicode的出现

虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:如果一份文档中含有不同国家的不同语言的字符,那么无法在一份文档中显示所有字符。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是Unicode字符集。

Unicode字符集涵盖了目前人类使用的所有字符,并为每个字符进行统一编号,分配唯一的字符码(Code Point)。Unicode字符集将所有字符按照使用上的频繁度划分为17个层面(Plane),每个层面上有216=65536个字符码空间。其中第0个层面BMP,基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字,要么是留作扩展。我们平常用到的Unicode字符,一般都是位于BMP层面上的。目前Unicode字符集中尚有大量字符空间未使用。

==在内存中每一个字符使用它在Unicode字符集中的唯一编码值表示==,这是没有问题的。因为Unicode字符集中字符编码值的范围是[0, 65535],在Java的JVM内存中无论这个字符的编码值是多少,都分配2个字节。

但是==在其他环境中==,例如文件中、IO流中等,Unicode就不完美了,这里有三个的问题,一个是,在文件或IO流中英文字母等ASCII码表中的字符只用一个字节表示,第二个问题是如何才能区别这是Unicode和ASCII,即计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第三个,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode在很长一段时间内无法推广,直到互联网的出现,为解决Unicode如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8就是在互联网上使用最广的一种Unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号。从unicode到uft-8并不是直接的对应,而是要过一些算法和规则来转换(即Uncidoe字符集≠UTF-8编码方式)。

Unicode符号范围 | UTF-8编码方式

(十六进制) | (二进制)

—————————————————————–

0000 0000-0000 007F | 0xxxxxxx(兼容原来的ASCII)

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

1563199860263

因此,Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。

早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码,936代码页,实际上说的是同个东西。

但是对于Unicode则不同,Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,你可以写gb2312,codepage936,utf-8,utf-16,但请不要写Unicode。

8.2 正则表达式

正则表达式,又称规则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE)。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。通常被用来检索、替换那些符合某个模式(规则)的文本。

8.2.1 正则表达式构造摘要

(1)字符类

[abc]abc(简单类)

[^abc]:任何字符,除了 abc(否定)

[a-zA-Z]azAZ,两头的字母包括在内(范围)

(2)预定义字符类

.:任何字符(与行结束符可能匹配也可能不匹配)

\d:数字:[0-9]

\D:非数字: [^0-9]

\s:空白字符:[ \t\n\x0B\f\r]

\S:非空白字符:[^\s]

\w:单词字符:[a-zA-Z_0-9]

\W:非单词字符:[^\w]

(3)POSIX 字符类(仅 US-ASCII)

\p{Lower} 小写字母字符:[a-z]

\p{Upper} 大写字母字符:[A-Z]

\p{ASCII} 所有 ASCII:[\x00-\x7F]

\p{Alpha} 字母字符:[\p{Lower}\p{Upper}]

\p{Digit} 十进制数字:[0-9]

\p{Alnum} 字母数字字符:[\p{Alpha}\p{Digit}]

\p{Punct} 标点符号:!"#$%&'()*+,-./:;<=>?@[]^_`{|}~

\p{Blank} 空格或制表符:[ \t]

(4)边界匹配器

^:行的开头

$:行的结尾

(5)Greedy 数量词

X?X,一次或一次也没有

X*X,零次或多次

X+X,一次或多次

X{n}X,恰好 n

X{n,}X,至少 n

X{n,m}X,至少 n 次,但是不超过 m

(6)Logical 运算符

XYX 后跟 Y

X|YXY

(X):X,作为捕获组

(7)特殊构造(非捕获)

(?:X) :X,作为非捕获组

(?>X): X,作为独立的非捕获组

(?=X) :X,通过零宽度的正 lookahead

(?<=X) :X,通过零宽度的正 lookbehind

(?!X) :X,通过零宽度的负 lookahead

(?<!X) :X,通过零宽度的负 lookbehind

8.2.2 常见的正则表达式示例

  • 验证用户名和密码,要求第一个字必须为字母,一共6~16位字母数字下划线组成:(^[a-zA-Z]\w{5,15}$)

  • 验证电话号码:xxx/xxxx-xxxxxxx/xxxxxxxx:(^(\d{3,4}-)\d{7,8}$)

  • 验证手机号码:( ^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$ )

  • 验证身份证号: (^\d{15})(\d18)|(^\d{18})|(^\d{17}(\d|X|x)$)

  • 验证Email地址:(^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$)

  • 只能输入由数字和26个英文字母组成的字符串:(^[A-Za-z0-9]+$)

  • 整数或者小数:(^[0-9]+(.[0-9]+){0,1}$)

  • 中文字符的正则表达式:([\u4e00-\u9fa5])

  • 金额校验(非零开头的最多带两位小数的数字):(^([1-9][0-9]*)+(.[0-9]{1,2})?$)

  • IPV4地址:(((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))

8.2.3 捕获组

捕获组分为:

  • 普通捕获组:从正则表达式左侧开始,每出现一个左括号"("记做一个分组,分组编号从 1 开始。0 代表整个表达式。
  • 命名捕获组:每个以左括号开始的捕获组,都紧跟着 ?,而后才是正则表达式。

(1)普通捕获组:

(\\d{4})-((\\d{2})-(\\d{2}))

以“2017-04-25”时间字符串为例,有4个捕获组,0是整个表达式。

编号捕获组匹配
0(\d{4})-((\d{2})-(\d{2}))2017-04-25
1(\d{4})2017
2((\d{2})-(\d{2}))04-25
3(\d{2})04
4(\d{2})25
package com.atguigu.pattern;

import org.junit.Test;

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

public class TestCaptureGroup{
    @Test
    public void test01(){
        Pattern pattern = Pattern.compile("(\\d{4})-((\\d{2})-(\\d{2}))");
        Matcher matcher = pattern.matcher("2017-04-25");
        matcher.find();//必须要有这句
        System.out.printf("\nmatcher.group(0) value:%s", matcher.group(0));
        System.out.printf("\nmatcher.group(1) value:%s", matcher.group(1));
        System.out.printf("\nmatcher.group(2) value:%s", matcher.group(2));
        System.out.printf("\nmatcher.group(3) value:%s", matcher.group(3));
        System.out.printf("\nmatcher.group(4) value:%s", matcher.group(4));
    }
}

(2)命名捕获组:

(\\d{4})-((\\d{2})-(\\d{2})) 非命名捕获组,普通捕获组
(?<year>\\d{4})-(?<md>(?<month>\\d{2})-(?<date>\\d{2}))

以“2017-04-25”时间字符串为例,有4个捕获组,0是整个表达式。

编号名称捕获组匹配
00(?\d{4})-(?(?\d{2})-(?\d{2}))2017-04-25
1year(?\d{4})-2017
2md(?(?\d{2})-(?\d{2}))04-25
3month(?\d{2})04
4date(?\d{2})25
package com.atguigu.pattern;

import org.junit.Test;

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

public class TestCaptureGroup{
    @Test
    public void test02(){
        Pattern pattern = Pattern.compile("(?<year>\\d{4})-(?<md>(?<month>\\d{2})-(?<date>\\d{2}))");
        Matcher matcher = pattern.matcher("2017-04-25");
        matcher.find();//必须要有这句
        System.out.printf("\n===========使用名称获取=============");
        System.out.printf("\nmatcher.group(0) value:%s", matcher.group(0));
        System.out.printf("\n matcher.group('year') value:%s", matcher.group("year"));
        System.out.printf("\nmatcher.group('md') value:%s", matcher.group("md"));
        System.out.printf("\nmatcher.group('month') value:%s", matcher.group("month"));
        System.out.printf("\nmatcher.group('date') value:%s", matcher.group("date"));
        matcher.reset();
        System.out.printf("\n===========使用编号获取=============");
        matcher.find();
        System.out.printf("\nmatcher.group(0) value:%s", matcher.group(0));
        System.out.printf("\nmatcher.group(1) value:%s", matcher.group(1));
        System.out.printf("\nmatcher.group(2) value:%s", matcher.group(2));
        System.out.printf("\nmatcher.group(3) value:%s", matcher.group(3));
        System.out.printf("\nmatcher.group(4) value:%s", matcher.group(4));
    }
}

8.2.4 非捕获组

在左括号后紧跟 ?:,而后再加上正则表达式,构成非捕获组。:还可以换成>、=、<=、!、<!等

  • (?:X) :X,作为非捕获组

  • (?>X): X,作为独立的非捕获组

  • (?=X) :X,通过零宽度的正 lookahead

  • (?<=X) :X,通过零宽度的正 lookbehind

  • (?!X) :X,通过零宽度的负 lookahead

  • (?<!X) :X,通过零宽度的负 lookbehind

(1)(?:X) 和(?>X)
  • (?:X) :X,作为非捕获组
  • (?>X): X,作为独立的非捕获组

现有字符串:

String str = "12332aa438aaf";

需要找出这样的两位字符(数字,或小写字母),它后面有两个a。找出这样的字符连同aa一起。

有如下两个正则:

[0-9a-z]{2}(?:aa)
[0-9a-z]{2}(?>aa)
package com.atguigu.pattern;

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

public class TestNotCaptureGroup1 {
    public static void main(String[] args) {
        String str = "12332aa438aaf";

        Pattern p1 = Pattern.compile("[0-9a-z]{2}(?:aa)");
        //匹配两位字符(数字,或字母),且后面有两个a
        Matcher m1 = p1.matcher(str);
        while(m1.find()){
            System.out.println(m1.group(0));
        }

        System.out.println("----------------------");
        Pattern p2 = Pattern.compile("[0-9a-z]{2}(?>aa)");
        //匹配两位字符(数字,或字母),且后面有两个a
        Matcher m2 = p2.matcher(str);
        while(m2.find()){
            System.out.println(m2.group(0));
        }
    }
}

运行结果:

32aa
38aa
----------------------
32aa
38aa

(2)(?=X)和(?<=X)
  • (?=X) :X,通过零宽度的正 lookahead,肯定式向前查找

  • (?<=X):X,通过零宽度的正 lookbehind,肯定式向后查找

现有字符串:

String str = "12332aa438aaf";
  • 找出这样的两位字符(数字,或字母),它后面有两个a(不包含aa)。
  • 找出这样的两位字符(数字,或字母),它前面有两个a(不包含aa)。

有如下两个正则:

[0-9a-z]{2}(?=aa)
(?<=aa)[0-9a-z]{2}
package com.atguigu.pattern;

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

public class TestNotCaptureGroup2 {
    public static void main(String[] args) {
        String str = "12332aa438aaf";

        Pattern p1 = Pattern.compile("[0-9a-z]{2}(?=aa)");
        //匹配两位字符(数字,或字母),且后面有两个a
        Matcher m1 = p1.matcher(str);
        while(m1.find()){
            System.out.println(m1.group(0));
        }

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

        Pattern p2 = Pattern.compile("(?<=aa)[0-9a-z]{2}");
        //匹配两个字符(数字,或字母),且前面有两个a
        Matcher m2 = p2.matcher(str);
        while(m2.find()){
            System.out.println(m2.group(0));
        }
    }
}

运行结果:

32
38
---------------------
43

什么是零宽度?

根据"[0-9a-z]{2}(?=aa)"正则,"12332aa438aaf"第一次匹配了32,继续查找下一次是从32后面的aa开始找,而不是数字4开始找。

上面的代码修改str的值:

package com.atguigu.pattern;

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

public class TestNotCaptureGroup3 {
    public static void main(String[] args) {
        String str = "aaaaaaaa";
        Pattern p = Pattern.compile("[0-9a-z]{2}(?=aa)");
        Matcher m = p.matcher(str);
        while(m.find()){
            System.out.println(m.group(0));
        }
    }
}

运行结果是:

aa
aa
aa

解析:

第一次匹配比较容易找到,那就是前四个:aaaa ,当然第三和第四个 a 是不捕获的,所以输出是第一和第二个a; 接着继续查找,这时是从第三个a开始,三到六,这4个a区配到了,所以输出第三和第四个a; 接着继续查找,这时是从第五个a开始,五到八,这4个a区配到了,所以输出第五和第六个a; 接着往后查找,这时是从第七个a开始,显然,第七和第八个a,不满足正则的匹配条件,查找结束。

(3)(?!X) 和(?<!X)

把'=' 换成了'!',意思也正好相反。

package com.atguigu.pattern;

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

public class TestNotCaptureGroup4 {
    public static void main(String[] args) {
        String str = "12332aa438aaf";

        Pattern p1 = Pattern.compile("[0-9a-z]{2}(?!aa)");
        //匹配两位字符(数字,或字母),且后面没有两个a
        Matcher m1 = p1.matcher(str);
        while(m1.find()){
            System.out.println(m1.group(0));
        }

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

        Pattern p2 = Pattern.compile("(?<!aa)[0-9a-z]{2}");
        //匹配两个字符(数字,或字母),且前面没有两个a
        Matcher m2 = p2.matcher(str);
        while(m2.find()){
            System.out.println(m2.group(0));
        }
    }
}

运行结果:

12
33
2a
a4
8a
af
---------------------
12
33
2a
a4
38
aa

8.2.5 捕获组和非捕获组一起使用

现有如下需求:有金额:8899¥、8899.56¥ 和 6688$等,要求提炼出它们的货币金额和货币种类。现在少于一元钱基本上买不到东西了,所以希望忽略小数部分。

现需要两个捕获组:一个组匹配货币金额,一个组匹配货币种类:

(\\d+)([¥$])

而小数部分的匹配是非捕获组:

(?:\\.?)(?:\\d*)

完整的正则表达式:

(\\d+)(?:\\.?)(?:\\d*)([¥$])
package com.atguigu.pattern;

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

public class TestNotCaptureGroup5 {
    public static void main(String[] args) {

        Pattern p = Pattern.compile("(\\d+)(?:\\.?)(?:\\d*)([¥$])");
        String[] arr = {"8895¥","8899.56¥","6688$","8965"};
        for (String str : arr) {
            Matcher m = p.matcher(str);
            if(m.matches()){
                System.out.println("货币金额: " + m.group(0));
                System.out.println("货币金额: " + m.group(1));
                System.out.println("货币种类: " + m.group(2));
                //非捕获组(?:),它可以理解为只匹配而不捕获。所以无论是否匹配(?:\\.?)(?:\\d*),都不捕获,即无论是否有小数,这里都只有group(1)和group(2)
                //捕获组,可以理解为匹配且捕获。捕获整数部分金额值,和金额单位值。
            }
        }
    }
}