赞
踩
java.lang.String
类代表字符串。Java程序中所有的字符串文字(例如"abc"
)都可以被看作是实现此类的实例。字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。
String
类包括的方法可用于检查序列的单个字符、比较字符串、搜索字符串、提取子字符串、创建字符串副本并将所有字符全部转换为大写或小写。
Java 语言提供对字符串串联符号(“+”)以及将其他对象转换为字符串的特殊支持(toString()方法)。
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类是如何设计为对象不可变的?(面试题)
综上3点保证了String对象的不可变。
(2)既然String对象不可变,那么我们“修改”String对象要如何操作?
4、就因为字符串对象设计为不可变,所以可以共享。Java中把需要共享的字符串常量对象放在字符串常量池中。
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);
// 内存中只有一个"abc"对象被创建,同时被s1和s2共享。
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");
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);
}
任意数据类型与"字符串"进行拼接,结果都是字符串类型
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);
}
Object类中声明了toString()方法,因此任意对象都可以调用toString方法,转为字符串类型。如果没有重写toString的话,返回的默认是“对象的运行时类型@对象的hashCode值的十六进制值”
public static void main(String[] args) {
LocalDate today = LocalDate.now();
String str = today.toString();
System.out.println(str);
}
==运算符:比较是两个字符串对象的地址
@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
}
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
}
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();
}
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));
}
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));
}
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));
}
Java语法层面:长度为0的字符串
业务层面:
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.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.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;
}
}
字符串常量对象共享的好处:节省内存
String s1 = "atguigu";
String s2 = "atguigu";
System.out.println(s1 == s2);
这里只创建了一个字符串对象"atguigu"。
字符串常量对象可以共享的原因:字符串对象不可变
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"。
字符串常量池是一个哈希表,它里面记录了可以共享使用的字符串常量对象的地址。采用哈希表结构的目的是为了提高性能,用空间换时间。字符串对象的地址散列存储在哈希表中,虽然是散列存储,但是因为可以使用字符串对象的hashCode值快速的计算存储位置的下标,所以效率还是很高的。
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]中记录了两个字符串对象的地址,它们使用链表连接起来。
需要共享的字符串地址记录到字符串常量池的table表中,不需要共享的字符串对象其地址值不需要记录到字符串常量池的table表中。除了以下2种,其他的都不放入字符串常量池:
(1)""直接的字符串
(2)字符串对象.intern()结果
其他:
(1)直接new
(2)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
}
}
字符串常量池表:
字符串对象:
字符串的intern方法:
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
*/
}
}
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个
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";
}
}
就算不共享同一个字符串对象,字符串对象之间也会**“尽量”**共享同一个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);
}
}
(1)int length():返回字符串的长度
@Test
public void test1(){
System.out.println("hello".length());
}
(2)String toLowerCase():将字符串中大写字母转为小写
(3)String toUpperCase():将字符串中小写字母转为大写
@Test
public void test2(){
System.out.println("Hello".toLowerCase());
System.out.println("Hello".toUpperCase());
}
(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();
}
(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("培训"));
}
(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(".")));
}
(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);
}
(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"));// 中国
}
(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("其他文件");
}
}
(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
三个非捕获组都有值,即都匹配上了就行。
非捕获组是只匹配不捕获。
*/
}
(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);
}
(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 +
'}';
}
}
因为String对象是不可变对象,虽然可以共享常量对象,但是对于频繁字符串的修改和拼接操作,效率极低。因此,JDK又在java.lang包提供了可变字符序列StringBuilder和StringBuffer类型。
StringBuffer:老的,线程安全的(因为它的方法有synchronized修饰)
StringBuilder:线程不安全的
常用的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());
}
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
}
}
计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还可以做更多的事,例如文本处理。但由于计算机只识“数”,因此人们必须告诉计算机哪个数字来代表哪个特定字符,例如65代表字母‘A’,66代表字母‘B’,以此类推。但是计算机之间字符-数字的对应关系必须得一致,否则就会造成同一段数字在不同计算机上显示出来的字符不一样。因此美国国家标准协会ANSI制定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是ASCII字符集(Character Set),也称ASCII码。
那时候的字符编解码系统非常简单,就是简单的查表过程。其中:
当计算机开始发展起来的时候,人们逐渐发现,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个比特,就可以得到256个字符了,足够用了。但是对于亚洲国家来说,256个字符是远远不够用的。因此这些国家的人为了用上电脑,又要保持和ASCII字符集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集(Muilti-Bytes Charecter Set)。例如中国使用的就是双字节字符集编码。
例如目前最常用的中文字符集GB2312,涵盖了所有简体字符以及一部分其他字符;GBK(K代表扩展的意思)则在GB2312的基础上加入了对繁体字符等其他非简体字符。这两个字符集的字符都是使用1-2个字节来表示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候,如果遇到字节的最高位是0的话,那么就使用936代码页中的第1张码表进行解码,这就和单字节字符集的编解码方式一致了。如果遇到字节的最高位是1的话,那么就表示需要两个字节值才能对应一个字符。
不同ASCII衍生字符集的出现,让文档交流变得非常困难,因此各种组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码(注意,我们现在通常说到ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK),ISO组织制定的各种ISO标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的GBK,GB2312和GB18030。
操作系统在发布的时候,通常会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例如你用GB2312字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,我们也可以在一台机器上阅读多个国家不同语言的文档了,前提是本机必须安装该文档使用的字符集。
虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:如果一份文档中含有不同国家的不同语言的字符,那么无法在一份文档中显示所有字符。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是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
因此,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。
正则表达式,又称规则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE)。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。通常被用来检索、替换那些符合某个模式(规则)的文本。
[abc]
:a
、b
或 c
(简单类)
[^abc]
:任何字符,除了 a
、b
或 c
(否定)
[a-zA-Z]
:a
到 z
或 A
到 Z
,两头的字母包括在内(范围)
.
:任何字符(与行结束符可能匹配也可能不匹配)
\d
:数字:[0-9]
\D
:非数字: [^0-9]
\s
:空白字符:[ \t\n\x0B\f\r]
\S
:非空白字符:[^\s]
\w
:单词字符:[a-zA-Z_0-9]
\W
:非单词字符:[^\w]
\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]
^
:行的开头
$
:行的结尾
X?
:X,一次或一次也没有
X*
:X,零次或多次
X+
:X,一次或多次
X{
n}
:X,恰好 n 次
X{
n,}
:X,至少 n 次
X{
n,
m}
:X,至少 n 次,但是不超过 m 次
XY:X 后跟 Y
X|
Y:X 或 Y
(
X)
:X,作为捕获组
(?:X) :X,作为非捕获组
(?>X): X,作为独立的非捕获组
(?=X) :X,通过零宽度的正 lookahead
(?<=X) :X,通过零宽度的正 lookbehind
(?!X) :X,通过零宽度的负 lookahead
(?<!X) :X,通过零宽度的负 lookbehind
验证用户名和密码,要求第一个字必须为字母,一共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]))
捕获组分为:
(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是整个表达式。
编号 | 名称 | 捕获组 | 匹配 |
---|---|---|---|
0 | 0 | (?\d{4})-(?(?\d{2})-(?\d{2})) | 2017-04-25 |
1 | year | (?\d{4})- | 2017 |
2 | md | (?(?\d{2})-(?\d{2})) | 04-25 |
3 | month | (?\d{2}) | 04 |
4 | date | (?\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));
}
}
在左括号后紧跟 ?:,而后再加上正则表达式,构成非捕获组。:还可以换成>、=、<=、!、<!等
现有字符串:
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
(?=X) :X,通过零宽度的正 lookahead,肯定式向前查找
(?<=X):X,通过零宽度的正 lookbehind,肯定式向后查找
现有字符串:
String str = "12332aa438aaf";
有如下两个正则:
[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,不满足正则的匹配条件,查找结束。
把’=’ 换成了’!',意思也正好相反。
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
现有如下需求:有金额: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)
//捕获组,可以理解为匹配且捕获。捕获整数部分金额值,和金额单位值。
}
}
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。