1.String字符串概述
-
字符串的使用非常频繁,Java中为了提高字符串的访问效率,给字符串专门准备了一个字符串常量池。
-
为什么有了字符串常量池效率就提高了呢?省去了创建字符串对象的过程,提高了效率。主要还是在类加载阶段,就将当前类中所有双引号括起来的字符串字面量全部创建,并将其存放到"字符串常量池"中。
-
字符串常量池在JDK8及之后,存储在堆内存当中。
-
字符串常量池本身是不存储字符串对象的,存储的是字符串对象的内存地址。(字符串常量池底层是个哈希表的结构)
-
String类本身是不能被继承的。因为他是被final修饰的。
-
字符串字面量本身是不可变的。怎么理解?"abc"只要类加载的时候创建好,并存储到字符串常量池中,这一辈子就不能变了。"abc"不能变成"abcd"。
-
字符串为什么是不可变的?因为String类中有一个:
private final byte[] value; -
为什么字符串对象要设计成不可变的?字符串常量池本身是共享的,因此字符串常量池肯定是多线程共享的。为了保证多线程并发的环境下,字符串在共享时仍然是安全的,因此字符串被设计成不可变的。
2.String的内存结构图
String s1="abc";
//s1是可变的,因为它毕竟是一个变量
s1="abcd";
//底层实际上创建了两个对象
// 字符串常量池一个,堆中一个
String s2=new String("abc");

3.字符串的经典面试题
java
public class StringTest02 {
public static void main(String[] args) {
//"abc"在字符串常量池当中
String x="abc";
String y="abc";
System.out.println(x==y);//true
//比较两个字符串是否相等,不能使用 == ,必须调用equals方法。
String s3=new String("test");
String s4=new String("test");
System.out.println(s3==s4);//false
System.out.println(s3.equals(s4));//true
//运算符两边都是字面量的话,编译器会进行优化,在编译阶段完成拼接。
String s1="abc"+"def";//编译阶段对这个代码进行了优化,优化之后的代码 String s1="abcdef";
String s2="abcdef";
System.out.println(s1==s2);//true
}
}
4.字符串的构造方法
java
public class StringTest03 {
public static void main(String[] args) {
String s1="hello";
//给定一个byte数组,可以将其转换成字符串
byte[] bytes={97,98,99,100};
String s2=new String(bytes);
System.out.println(s2);//abcd
String s3=new String(bytes,1,2);
System.out.println(s3);
//给定一个字符串,将字符串转换成byte数组
byte[] bytes1="abcdef".getBytes();//编码的过程中默认采用UTF-8的方式进行编码
for (byte b:bytes1){
System.out.println(b);
}
//采用指定字符编码方式,进行编码
String s4="你好,世界";
byte[] gbks = s4.getBytes(Charset.forName("GBK"));
// byte[] gbks1 = s4.getBytes("GBK");
//将以上的byte[] 数组在转换成字符串(解码)
String s5=new String(gbks,Charset.forName("GBK"));
// String s5=new String(gbks,Charset.forName("UTF-8"));
System.out.println(s5);
char[] chars={'中','国','人'};
String s6=new String(chars);
String s7=new String(chars,1,2);
System.out.println(s7);
//了解
StringBuilder stringBuilder=new StringBuilder("ajfafa");
String s8=new String(stringBuilder);
System.out.println(s8);
}
}
5.关于正则表达式
正则表达式(regular expression),简称为regex或regexp,是一种用于描述特定模式的表达式。它可以匹配、查找、替换文本中与该模式匹配的内容,被广泛应用于各种文本处理和匹配相关的应用中。正则表达式不属于Java,属于所有编程语言,通用的.
5.1正则表达式的应用
-
验证输入内容的格式是否正确。例如,邮箱,手机号,密码等
-
在文本编辑器中进行搜索和替换。例如,在代码编辑器中查找指定字符串或替换错误的代码成为正确的代码块
-
数据挖掘和信息提取。正则表达式可以从HTML、XML、JSON等格式的数据中提取所需的信息
-
用于编写脚本语言,如awk,grep和sed
-
服务器端编程。正则表达式在处理数据和字符串时具有高效的性能,可以在开发Web应用程序时被广泛应用
5.2常见的正则表达式符号
元字符
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
字符转义
\. 表示一个普通的.字符。 \* 表示一个普通*字符。
重复次数
* 重复零次或更多次(0 - n)
- 重复一次或更多次(1 - n)
? 重复零次或一次 (0 或 1)
{n} 重复n次 (n)
{n,} 重复n次或更多次 ( >= n)
{n,m} 重复n到m次 (n - m)
字符类
abcdef 匹配abcdef这几个字符中的任意一个字符
0-9 匹配0-9中的任意一个数字
a-zA-Z0-9 匹配a-z,A-Z,0-9的任意一个字符
.?! 匹配标点符号(.或?或!)
abc- 匹配abc-四个字符中的任意一个字符(注意-只能出现在末尾。如果-在中间则表示区间)
分支条件
0\d{2}-\d{8}|0\d{3}-\d{7}
这个表达式能匹配两种以连字号分隔的电话号码:
一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)
分组
(\d{1,3}.){3}\d{1,3}
是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,
(\d{1,3}.){3}
匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})
反义
\W 匹配任意不是字母,数字,下划线的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
\^x 匹配除了x以外的任意字符
\^aeiou 匹配除了aeiou这几个字母以外的任意字符
5.3String的正则表达式相关的方法
java
package com.bit.String;
import java.util.Scanner;
public class StringTest08 {
public static void main(String[] args) {
//String类中的replace方法不支持正则表达式
String s1="ajdljfakljffhajkhakj";
String s2 = s1.replace("ajd", "");
System.out.println(s2);
//String 类中的replaceAll 方法支持正则表达式
//请将一个字符串当中的所有的数字清楚除
String s3="123sdhksfh456ahkfh";
// \\d代表正则表达式中的\d
String s4 = s3.replaceAll("\\d", "");
System.out.println(s4);
//String中的split也支持正则表达式(拆分字符串)
String s5="2000-4-12";
String[] ymd = s5.split("-");
for (String s : ymd) {
System.out.println(s);
}
String s6="abc123def456xyz";
//将字符串以数字为分割线进行拆分(abc def xyz)
String[] strs = s6.split("\\d{3}");
for (String str : strs) {
System.out.println(str);
}
//String 类中的matchs方法也支持正则表达式
//做字符串匹配的
String emailRexExp="/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/";
Scanner scanner=new Scanner(System.in);
System.out.print("请输入邮箱地址:");
String userInput = scanner.next();
//判断是否合法
boolean flag = userInput.matches(emailRexExp);
if (flag){
System.out.println("合法");
}else{
System.out.println("不合法");
}
}
}
6.String的经典面试题
java
public class StringTest09 {
public static void main(String[] args) {
// String s1 = new String("abc");
// String s2 = "abc";
// System.out.println(s1 == s2); // false
// System.out.println(s1.equals(s2)); // true
// 创建了几个对象(5个)
// "a"、"b"、new String("b")导致堆中1个、s1+s2拼接的时候底层有一个StringBuilder对象、拼接之后生成一个新String对象。
// String s1 = "a";
// String s2 = new String("b");
// String s3 = s1 + s2;
// 输出结果是什么?还是出现空指针异常?
// String s1 = null;
// String s2 = s1 + null;
// String s2 = s1.concat(null);//空指针异常
// System.out.println(s2); // nullnull
// 执行结果是什么?
// String s1 = "a1";
// String s2 = "a" + 1;
// System.out.println(s1 == s2); // true
// String s1 = "a" + "b" + "c";
// String s2 = "abc";
// System.out.println(s1 == s2); // true
// System.out.println(s1.equals(s2)); // true
// 创建了几个对象(6个)
String str = new String("a") + new String("b");
// 程序执行结果是?
// String s1 = "ab";
// final String s2 = "b";//编译器优化了
// String s3 = "a" + s2;
// System.out.println(s1 == s3); // true
//
// String s1 = "abc";
// StringBuilder s2 = new StringBuilder(s1);
// System.out.println(s1.equals(s2)); // false(类型不一致,false)
// String s1 = "ab";
// String s2 = "abc";
// String s3 = s1 + "c";
// System.out.println(s2 == s3); // false
// System.out.println(s2.equals(s3)); // true
// 创建了几个对象?(1个)
String s = "a" + "b";
String s1 = "ab";
final String s2 = getB();
String s3 = "a" + s2;
System.out.println(s1 == s3); // false(结果为false的主要原因:getB()方法只能在编译阶段执行,编译阶段无法执行,因此编译器无法优化)
}
public static String getB(){
return "b";
}
}
7.String、StringBuffer、StringBuilder区别
第一个,可变性,String内部的value值是final修饰的,所以它是不可变类。所以每次修改String的值,都会产生一个新的对象。StringBuffer和StringBuilder是可变类,字符串的变更不会产生新的对象。
第二个,线程安全性,String是不可变类,所以它是线程安全的。
StringBuffer是线程安全的,因为它每个操作方法都加了synchronized同步关键字。
StringBuilder不是线程安全的。所以在多线程环境下对字符串进行操作,应该使用StringBuffer,否则使用StringBuilder
第三个,性能方面。
String的性能是最的低的,因为不可变意味着在做字符串拼接和修改的时候,需要重新创建新的对象以及分配内存。其次是StringBuffer要比String性能高,因为它的可变性使得字符串可以直接被修改。最后是StringBuilder,它比StringBuffer的性能高,因为StringBuffer加了同步锁。
第四个,存储方面。
String存储在字符串常量池里面
StringBuffer和StringBuilder存储在堆内存空间。
最后再补充一下, StringBuilder和StringBuffer都是派生自AbstractStringBuilder这个抽象类。
8.new String("abc")到底创建了几个对象?
- 如果
abc这个字符串常量不存在,则创建两个对象,分别是abc这个字符串常量,以及new String这个实例对象。 - 如果
abc这字符串常量存在,则只会创建一个对象
9.StringBuider/StringBuffer
1.java中提供了可变长度的字符串类型,包括两个:
java.lang.StringBuilder
java.lang.StringBuffer
2.两个可变长的字符串的区别:
StringBuilder是非线程安全的.
StringBuffer是线程安全的。所有方法带synchronized。(效率低,因为它要兼顾线程安全间题)
3.String不够用吗,为什么要用StringBuilder/StringBuffer?
St♪ig在进行字符串拼接的时候,效率极低,空间复杂度极高。只要拼接就会创建新的字符串对象。
StringBuilder/StringBuffer专/门为字符串拼接的场景设计的。
实际开发中拼接字符串的场景,千万不要使用St♪1ng然后+=进行拼接,
一定要手s动调用StringBuffer,/StringBuilder的append方法进行追加。
底层不会创建新对象,空间不够的时候,最多就是扩容数组。
4.StringBuilder,/StringBuffer原理是什么?怎么拼接的?
StringBuilder/StringBuffer的共同父类:AbstractstringBuilder
AbstractstringBuilder类中有一个属性:byte\[\]value;
这个alve属性String中的alve属性最大的区别是:没有final
StringBuilder/StringBuffer中walve引用是可以指向新的数组对象的。达到可变。
5.StringBuffer/StringBuilder的默认初始化容量是16
6.可以给定初始化容量:通过构造方法也可以指定初始化容量。
7.StringBuffer/StringBuilder都有一个append方法,来完成拼接。
append.方法支持重载的,很好用。
8.StringBuffer/StringBuilder满了之后会自动扩容。
9.怎么优化StringBuffer/StringBuilder?
在创建StringBuffer/StringBuilder对象时,预估字符串的长度,尽量给定初始化容量,
减少底层数组扩容的次数,提高程序的执行效率。
10.StringBuilder/StringBuffer的扩容策略?
(原容量+1)*2
为什么+1?因为初始化容量可能是0,如果是0,不加1会导致扩容失败。