Day14-String字符串的使用

第14天 String字符串的使用

学习目标

  • 熟练掌握String类的API
  • 熟练掌握StringBuilder和StringBuffer类的API

1. 字符串

java.lang.String 类代表字符串。Java程序中所有的字符串文字(例如"abc" )都可以被看作是实现此类的实例。字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。

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

Java 语言提供对字符串串联符号(“+”)以及将其他对象转换为字符串的特殊支持(toString()方法)。

1.1字符串的特点

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

2、String对象内部是用字符数组进行保存的

JDK1.9之前有一个char[] value数组,JDK1.9之后byte[]数组。

"abc" 等效于 char[] data={ 'a' , 'b' , 'c' }

例如: 
String str = "abc";

相当于: 
char data[] = {'a', 'b', 'c'};     
String str = new String(data);
// String底层是靠字符数组实现的。

3、字符串的对象也是不可变对象,意味着一旦进行修改,就会产生新对象。

(1)为什么String对象不可变?或者说String类是如何设计为对象不可变的?(面试题)

  • String类中这个char[] value数组也是final修饰的,意味着这个数组地址不可变,即不能对同一个String对象的value数组进行扩容、缩容等。
  • 虽然final修饰的value数组元素值可以修改。但是由于它是private修饰,外部不能直接操作它,所以在String类外无法直接修改value数组的元素值(除非用反射)。
  • String类型中提供的所有方法实现涉及到value数组长度需要变化,或value元素值需要变化,都是用新对象来表示修改后内容的。

综上3点保证了String对象的不可变。

(2)既然String对象不可变,那么我们“修改”String对象要如何操作?

  • 我们修改了字符串后,如果想要获得新的内容,必须重新接收。
  • 如果程序中涉及到大量的字符串的修改操作,那么此时的时空消耗比较高。可能需要考虑使用StringBuilder或StringBuffer的可变字符序列。

4、就因为字符串对象设计为不可变,所以可以共享。Java中把需要共享的字符串常量对象放在字符串常量池中。

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

1.2 构造字符串对象

1.2.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.2.2 使用静态方法valueOf和copyValueOf
  • 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);  //hellojava
		System.out.println(s2);  //hello
		System.out.println(s3);  //123456
	}
1.2.3 任意类型与字符串 +

任意数据类型与"字符串"进行拼接,结果都是字符串类型

	public static void main(String[] args) {
		int num = 123456;
		String s = num + "";
		System.out.println(s);
		
		Student stu = new Student();
		String s2 = stu + "";//自动调用对象的toString(),然后与""进行拼接
		System.out.println(s2);
	}
1.2.4 任意对象.toString

Object类中声明了toString()方法,因此任意对象都可以调用toString方法,转为字符串类型。如果没有重写toString的话,返回的默认是“对象的运行时类型@对象的hashCode值的十六进制值”

public static void main(String[] args) {
    LocalDate today = LocalDate.now();
    String str = today.toString();
    System.out.println(str);
}

1.3 字符串对象的比较

1.3.1 ==运算符

==运算符:比较是两个字符串对象的地址

    @Test
    public void test1(){
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);//true,说明str1和str2指向同一个字符串对象

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

        System.out.println(str3 == str4); //false
    }
1.3.2 equals方法比较

boolean equals(Object obj)方法:比较是两个字符串对象的内容,因为String类型重写equals,equals方法比较字符串内容时严格区分大小写。

    @Test
    public void test2(){
        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
    }
1.3.3 equalsIgnoreCase方法

boolean equalsIgnoreCase(String str)方法:比较是两个字符串对象的内容,并且不区分大小写。

    @Test
    public void test3(){
        String str1 = new String("hello");
        String str2 = new String("HELLO");
        System.out.println(str1.equalsIgnoreCase(str2)); //true
    }

	@Test
	public void test04(){
        //随机生成验证码,验证码由0-9,A-Z,a-z的4位字符组成
		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);
		}
		char[] code = new char[4];
		Random rand = new Random();
		for (int i = 0; i < 4; i++) {
			code[i]= array[rand.nextInt(array.length)];
		}
        String codeString = new String(code);
		System.out.println("验证码:" + codeString);
        
		//将用户输入的单词全部转为小写,如果用户没有输入单词,重新输入
		Scanner input = new Scanner(System.in);
		System.out.print("请输入验证码:");
		String inputCode = input.nextLine();
		
		if(!codeString.equalsIgnoreCase(inputCode)){
			System.out.println("验证码输入不正确");
		}else{
            System.out.println("验证码输入正确");
        }
        
        input.close();
	}
1.3.4 compareTo方法

int compareTo(String str)方法:String类型实现了java.lang.Comparable接口,重写了Comparable接口的抽象方法,即String对象支持自然排序,该方法按照字符的Unicode编码值进行比较大小的,严格区分大小写。

    @Test
    public void test5(){
        String str1 = "hello";
        String str2 = "world";
        String str3 = "HELLO";
        System.out.println(str1.compareTo(str2));//-15
        System.out.println(str1.compareTo(str3));//32
    }

    @Test
    public void test6(){
        String[] arr = {"hello","java","chai","Jack","hi"};
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
1.3.5 compareToIgnoreCase方法

int compareToIgnoreCase(String str):String类型支持不区分大小写比较字符串大小。具体原理是先统一大小写再比较大小。

    @Test
    public void test7(){
        String str1 = "hello";
        String str2 = "world";
        String str3 = "HELLO";
        System.out.println(str1.compareToIgnoreCase(str2));//-15
        System.out.println(str1.compareToIgnoreCase(str3));//0
    }

	@Test
    public void test8(){
        String[] arr = {"hello","java","chai","Jack","Hi"};
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String s1 = (String) o1;
                String s2 = (String) o2;
                return s1.compareToIgnoreCase(s2);
            }
        });
        System.out.println(Arrays.toString(arr));
    }
1.3.6 区分语言环境的 String比较

java.text.Collator 类执行区分语言环境的 String 比较。

    @Test
    public void test9(){
        String[] arr = {"张三","李四","王五","尚硅谷"};
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String s1 = (String) o1;
                String s2 = (String) o2;
                Collator collator = Collator.getInstance(Locale.CHINA);
                return collator.compare(s1,s2);
            }
        });
        System.out.println(Arrays.toString(arr));
    }

1.4 空字符

1.4.1 什么是空字符串

Java语法层面:长度为0的字符串

业务层面:

  • 长度为0的字符串,例如:String str = “”;
  • null值,例如:String str = null;
  • 只包含空白字符的字符串,例如:" "等
1.4.2 Java语法层面如何判断空字符串?
package com.atguigu.string;

public class TestEmptyString {
    public static void main(String[] args) {
        char[] chars = new char[0];
        byte[] bytes = new byte[0];
        String[] strings = new String[10];
        strings[0] = "";
        strings[1] = new String();
        strings[2] = new String("");
        strings[3] = new String(chars);
        strings[4] = new String(bytes);
        strings[5] = String.valueOf(chars);
        strings[6] = String.copyValueOf(chars);
        strings[7] = "  ";
        strings[8] = null;
        strings[9] = "null";

        for(int i=0; i<strings.length; i++){
            String str = strings[i];
            System.out.println("str=[" + str +"]");
            System.out.println("".equals(str));
            System.out.println(str!=null && str.isEmpty());
            System.out.println(str!=null && str.equals(""));
            System.out.println(str!=null && str.length()==0);
            System.out.println();
        }
    }
}

1.5 字符串拼接方法

1.5.1 两种方式
两种方式:   
	字符串1.concat(字符串2)
	字符串1 + 字符串2
package com.atguigu.string;

import org.junit.Test;

public class TestStringConcat {
    @Test
    public void test1(){
        String s1 = "hello";
        String s2 = "world";
        String s3 = s1 + s2;
        String s4 = s1.concat(s2);
        System.out.println("s3 = " + s3);//helloworld
        System.out.println("s4 = " + s4);//helloworld
    }

}
1.5.2 两种方式的区别
区别:    
    字符串1.concat(字符串2):只要拼接的不是空字符串,每次都new一个String
    字符串1 + 字符串2""常量拼接,编译器直接优化为拼接后的字符串常量值
        非""常量拼接,编译器优化为StringBuilder的append,然后再把结果toString。
    @Test
    public void test2(){
        String s1 = "hello";
        String s2 = "world";
        String s3 = "helloworld";
        String s4 = s1.concat(s2);
        System.out.println(s3 == s4);//false
        System.out.println(s3.equals(s4));//true
    }

    @Test
    public void test3(){
        String s1 = "hello";
        String s2 = "world";
        String s3 = "helloworld";
        String s4 = s1 + s2;
        String s5 = s1 + "world";
        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 test4(){
        final String s1 = "hello";//此时s1完全等价于"hello"
        final String s2 = "world";//此时s2完全等价于"world"
        String s3 = "helloworld";
        String s4 = s1 + s2;
        String s5 = s1 + "world";
        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 test5(){
        final String s1 = new String("hello");
        final String s2 = "world";
        String s3 = "helloworld";
        String s4 = s1 + s2;
        String s5 = s1 + "world";
        String s6 = new String("hello") + "world";
        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
    }
class Test{
	public void test1() {
        String s1 = "hello" + "world";
    }
	public void test2() {
        String s1 = "hello";
        String s2 = s1 + "world";
    }
	public void test3() {
        String s1 = "hello";
        String s2 = "world";
        String s3 = s1 + s2;
    }
}

image-20221102140637953

image-20221102140830458

1.6 字符串常量池

1.6.1 字符串常量对象共享的好处

字符串常量对象共享的好处:节省内存

String s1 = "atguigu";
String s2 = "atguigu";
System.out.println(s1 == s2);
这里只创建了一个字符串对象"atguigu"
1.6.2 字符串常量对象可以共享的原因

字符串常量对象可以共享的原因:字符串对象不可变

String s1 = "atguigu";
String s2 = "atguigu";
System.out.println(s1 == s2);

s2 = s2.replace("a","o");
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);

这里s1指向的"atguigu"和s2指向的"atguigu"是同一个。
如果无法保证"atguigu"对象不可变,那么当s2将"a"替换为"o"之后,那么s1就会受到影响,这样是不安全的。
但是,现在我们发现s1并未受到影响,也就是说,s1指向的"atguigu"对象并未被修改,而是基于"atguigu"重新复制了一个新对象"atguigu",然后替换成"otguigu"

image-20221102104053826

1.6.3 字符串常量池

字符串常量池是一个哈希表,它里面记录了可以共享使用的字符串常量对象的地址。采用哈希表结构的目的是为了提高性能,用空间换时间。字符串对象的地址散列存储在哈希表中,虽然是散列存储,但是因为可以使用字符串对象的hashCode值快速的计算存储位置的下标,所以效率还是很高的。

image-20220803165846201

String s1 = "hello";
String s2 = "hello";

当给s1赋值"hello"时,根据"hello"hashCode()值,计算出来index=[2],如果table[index]=table[2]=null,那就把"hello"对象的字符串的地址放到table[2]中。
    
当给s2赋值"hello"时,根据"hello"hashCode()值,计算出来index=[2],此时table[index]=table[2]!=null,那就直接把"hello"的内存地址赋值给s2。
    
String s3 = "Aa";
String s4 = "BB";
当给s3赋值"Aa"时,根据"Aa"hashCode()值,计算出来index=[6],如果table[index]=table[6]=null,那就把"Aa"对象的字符串的地址放到table[6]中。
    
当给s4赋值"BB"时,根据"BB"hashCode()值,计算出来index=[6],此时table[index]=table[6]!=null,但是"BB""Aa"不一样,那就直接把"BB"的内存地址也放到table[6]中,相当于table[6]中记录了两个字符串对象的地址,它们使用链表连接起来。    
1.6.4 哪些字符串对象地址放入字符串常量池?
需要共享的字符串地址记录到字符串常量池的table表中,不需要共享的字符串对象其地址值不需要记录到字符串常量池的table表中。除了以下2种,其他的都不放入字符串常量池:
(1""直接的字符串
(2)字符串对象.intern()结果
 
其他:
(1)直接new2)valueOf,copyValueOf等
(3)字符串对象拼接:concat拼接 以及 +左右两边出现  非直接""的字符串拼接
(4)toUpperCase,toLowerCase,substring,repalce等各种String方法得到的字符串
其实下面这些方式,本质都是新new的。
package com.atguigu.string;

import org.junit.Test;

public class TestStringTable {
    @Test
    public void test1(){
        String s1 = "hello";
        String s2 = "hello";
        System.out.println(s1 == s2);
    }

    @Test
    public void test2(){
        String s1 = new String("hello");
        String s2 = new String("hello");
        String s3 = s1.intern();
        String s4 = s2.intern();
        System.out.println(s1 == s2);//false
        System.out.println(s3 == s4);//true
    }

    @Test
    public void test3(){
        String s1 = "hello";
        String s2 = "world";
        String s3 = "HELLO";

        String s4 = s1.concat(s2);
        String s5 = s1 + s2;
        String s6 = s3.toLowerCase();

        String s7 = "hello" + "world";
        String s8 = "helloworld";
        System.out.println(s4 == s8);//false
        System.out.println(s5 == s8);//false
        System.out.println(s6 == s8);//false
        System.out.println(s7 == s8);//true
    }
}
1.6.5 字符串对象和字符串常量池在哪里?

字符串常量池表:

  • JDK1.6:在方法区的永久代
  • JDK1.7:在堆
  • JDK1.8:元空间

字符串对象:

  • JDK1.7之前:需要共享的字符串对象存储在方法区的永久代,然后把对象地址记录到字符串常量池的table表中,不需要共享的字符串对象存储在堆中,其地址值不需要记录到字符串常量池的table表中。
  • JDK1.7之后:所有字符串对象都存储在堆中。同样需要共享的字符串地址记录到字符串常量池的table表中,不需要共享的字符串对象其地址值不需要记录到字符串常量池的table表中。

字符串的intern方法:

  • 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
package com.atguigu.string;

import org.junit.Test;

public class TestStringIntern {
    @Test
    public void test1(){
        String s1 = new String("hello");
        String s2 = s1.intern();
        System.out.println(s1 == s2);
        /*
        JDK8:false
        JDK6:false
         */
    }

    @Test
    public void test2(){
        String s1 = "he".concat("llo");
        String s2 = s1.intern();
        System.out.println(s1 == s2);
        /*
        JDK8:true
        JDK6:false
         */
    }
}

image-20221102150850396

1.6.6 字符串的对象的个数(面试题)
String str1 = "hello";
String str2 = new String("hello");

//上面的代码一共有几个字符串对象。
//2个
String s1 = new String("hello");
String s2 = s1.intern();

//JDK1.6,2个
//JDK1.8,2个
String s = "a" + "b" + "c" +"d";
//1个,abcd
String s1 = "he".concat("llo");
String s2 = s1.intern();
//JDK1.6 4个
//JDK1.8 3个
1.6.7 扩展:IDEA如何查看字符串对象个数?
package com.atguigu.string;

import org.junit.Test;

public class TestStringCount {
    @Test
    public void test1(){
        String s1 = "尚硅谷".concat("柴林燕");
        String s2 = s1.intern();
    }
    @Test
    public void test2(){
        String s1 = "he".concat("llo");//Debug演示坑,在底层初始化代码中已经有"he"字符串常量
                                        //上面代码 显示String个数增加2
        String s2 = s1.intern();
    }

    @Test
    public void test3(){
        String s1 = "he";
        String s2 = "llo";
        String s3 = "hello";
    }
}

image-20221102153156427

image-20221102153219522

1.7 字符串对象的内存分析

就算不共享同一个字符串对象,字符串对象之间也会**“尽量”**共享同一个value数组。

package com.atguigu.string;

import org.junit.Test;

public class TestStringMemory {
    @Test
    public void test1(){
        String str1 = new String("hello");
        System.out.println(str1.hashCode());

        char[] arr = {'h','e','l','l','o'};
        String str2 = new String(arr);
    }
}

image-20221102155247282

1.8 字符串的其他方法

1.8.1 求字符串的长度

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

    @Test
    public void test1(){
        System.out.println("hello".length());
    }
1.8.2 转大小写

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

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

    @Test
    public void test2(){
        System.out.println("Hello".toLowerCase());
        System.out.println("Hello".toUpperCase());
    }
1.8.3 去掉前后空白

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

    @Test
    public void test3(){
        System.out.println("[" + "    hello   world    ".trim() + "]");
    }
    @Test
    public void test04(){
        //将用户输入的单词转为小写,如果用户没有输入单词,重新输入
        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);
        input.close();
    }
1.8.4 字符串内容查找

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

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

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

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

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

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

	@Test
	public void test06(){
		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 test07(){
		String fileName = "快速学习Java的秘诀.dat";
		//截取文件名
		System.out.println("文件名:" + fileName.substring(0,fileName.lastIndexOf(".")));
		//截取后缀名
		System.out.println("后缀名:" + fileName.substring(fileName.lastIndexOf(".")));
	}
1.8.6 获取char和char[]

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

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

将字符数组转为String对象,可以使用之前介绍过的构造器和静态方法valueOf或copyValueOf等

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

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

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

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

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

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

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

(12)byte[] getBytes():编码,把字符串变为字节数组,按照平台默认的字符编码方式进行编码

​ byte[] getBytes(字符编码方式):按照指定的编码方式进行编码

(13)new String(byte[] ) 或 new String(byte[], int, int):解码,按照平台默认的字符编码进行解码

​ new String(byte[],字符编码方式 ) 或 new String(byte[], int, int,字符编码方式):解码,按照指定的编码方式进行解码

(编码方式见附录10.7.1)

    @Test
    public void test10()throws Exception{
        byte[] data = {(byte)0B01100001, (byte)0B01100010, (byte)0B01100011, (byte)0B01100100};
        System.out.println(new String(data,"ISO8859-1"));
        System.out.println(new String(data,"GBK"));
        System.out.println(new String(data,"UTF-8"));
    }

    @Test
    public void test11()throws Exception{
        byte[] data = {(byte)0B01100001, (byte)0B01100010, (byte)0B01100011,(byte)0B11001001,(byte)0B11010000};
        System.out.println(new String(data,"ISO8859-1"));
        System.out.println(new String(data,"GBK"));
        System.out.println(new String(data,"UTF-8"));
    }

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

    @Test
    public void test13() 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"));// 中国
    }
1.8.8 字符串开头与结尾的判断

(14)boolean startsWith(xx):是否以xx开头

(15)boolean endsWith(xx):是否以xx结尾

    @Test
    public void test14(){
        String name = "张三";
        System.out.println(name.startsWith("张"));
    }

    @Test
    public void test15(){
        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.8.9字符串匹配正则表达式

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

	@Test
	public void test16(){
		//简单判断是否全部是数字,这个数字可以是1~n位
		String str = "12a345";
		
		//正则不是Java的语法,它是独立与Java的规则
		//在正则中\是表示转义,
		//同时在Java中\也是转义
		boolean flag = str.matches("\\d+");
		System.out.println(flag);
	}
	
	@Test
	public void test17(){
		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 test18(){
        //密码要求:必须有大写字母,小写字母,数字组成,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.8.10 字符串内容的替换

(17)String replace(xx,xx):不支持正则

(18)String replaceFirst(正则,value):替换第一个匹配部分

(19)String replaceAll(正则, value):替换所有匹配部分

    @Test
    public void test19(){
        String str = "hello244world.java;887";

        String s1 = str.replace("244","");
        System.out.println("s1 = " + s1);

        String s2 = str.replaceFirst("\\d+","");
        System.out.println("s2 = " + s2);


        //把其中的非字母去掉
        String s3 = str.replaceAll("[^a-zA-Z]", "");
        System.out.println("s3 = " + s3);
    }
1.8.11 字符串拆分

(20)String[] split(正则):按照某种规则进行拆分

	@Test
	public void test20(){
		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 test21(){
		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 test22(){
		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 test23(){
		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]);
		}
	}
class Student {
    private String name;
    private int age;

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

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

2 可变字符序列

2.1 String与可变字符序列的区别

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

StringBuffer:老的,线程安全的(因为它的方法有synchronized修饰)

StringBuilder:线程不安全的

2.2 StringBuilder、StringBuffer的API

常用的API,StringBuilder、StringBuffer的API是完全一致的

(1)StringBuffer append(xx):拼接,追加

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

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

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

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

(6)StringBuffer reverse():反转

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

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

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

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

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

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

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

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

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

(13)void trimToSize():尝试减少用于字符序列的存储空间。如果缓冲区大于保存当前字符序列所需的存储空间,则将重新调整其大小,以便更好地利用存储空间。

	@Test
	public void test6(){
		StringBuilder s = new StringBuilder("helloworld");
		s.setLength(30);
		System.out.println(s);
	}
	@Test
	public void test5(){
		StringBuilder s = new StringBuilder("helloworld");
		s.setCharAt(2, 'a');
		System.out.println(s);
	}
	
	
	@Test
	public void test4(){
		StringBuilder s = new StringBuilder("helloworld");
		s.reverse();
		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 test2(){
		StringBuilder s = new StringBuilder("helloworld");
		s.insert(5, "java");
		s.insert(5, "chailinyan");
		System.out.println(s);
	}
	
	@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());
	}

2.3 效率测试

package com.atguigu.stringbuffer;

import org.junit.Test;

public class TestTime {

    @Test
    public void testString(){
        long start = System.currentTimeMillis();
        String s = new String("0");
        for(int i=1;i<=10000;i++){
            s += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("String拼接+用时:"+(end-start));//367

        long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        System.out.println("String拼接+memory占用内存: " + memory);//473081920字节
    }

    @Test
    public void testStringBuilder(){
        long start = System.currentTimeMillis();
        StringBuilder s = new StringBuilder("0");
        for(int i=1;i<=10000;i++){
            s.append(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder拼接+用时:"+(end-start));//5
        long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        System.out.println("StringBuilder拼接+memory占用内存: " + memory);//13435032
    }

    @Test
    public void testStringBuffer(){
        long start = System.currentTimeMillis();
        StringBuffer s = new StringBuffer("0");
        for(int i=1;i<=10000;i++){
            s.append(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer拼接+用时:"+(end-start));//5
        long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        System.out.println("StringBuffer拼接+memory占用内存: " + memory);//13435032
    }
}

3 附录(自查)

3.1 字符编码的发展

3.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个小写英文字母,其余为一些标点符号、运算符号等。
3.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字节解码后得到的字符却是?。

3.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

3.1.4 ANSI标准、国家标准、ISO标准

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

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

3.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。

3.2 正则表达式

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

3.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

3.2. 2 常见的正则表达式示例
  • 验证用户名和密码,要求第一个字必须为字母,一共6~16位字母数字下划线组成:(1\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}KaTeX parse error: Undefined control sequence: \d at position 4: )|(\̲d̲{18})|(\d{17}(\d|X|x)$)

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

  • 只能输入由数字和26个英文字母组成的字符串:(2+$)

  • 整数或者小数:(3+(.[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]))

3.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));
    }
}

3.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
3.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)
                //捕获组,可以理解为匹配且捕获。捕获整数部分金额值,和金额单位值。
            }
        }
    }
}

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

}


运行结果:

```java
12
33
2a
a4
8a
af
---------------------
12
33
2a
a4
38
aa
3.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)
                //捕获组,可以理解为匹配且捕获。捕获整数部分金额值,和金额单位值。
            }
        }
    }
}

  1. a-zA-Z ↩︎

  2. A-Za-z0-9 ↩︎

  3. 0-9 ↩︎

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值