String类
1.Java中的字符串常量(如"abc"、“张三”)都可以看作是String类的数据体现。
字符串常量存在于方法区的常量池,程序中出现过的字符串常量都在常量池
创建,且同一个字符串常量只会创建一次。
String s1 = “abc”; //常量池中创建了 “abc”, s1指向它
s1 = “张三”; //常量池中创建了 “张三”, s1指向它
String s2 = “abc”; //常量池中已有 “abc”, s2直接指向它
2它很特殊,String类型的引用可以直接指向常量池,也可以先指向堆,堆再指向常量池
//字符串常量赋值
String s1 = “abc”; //s1指向常量池 “abc”
//new创建并初始化
String s2 = new String(“abc”); //s2指向堆 value属性地址, value属性地址指向常量池 “abc”
s1、s2的字符串内容都是"abc", 但是两者存储的地址却不相同!
3.String数据存储采用final修饰的一维字符数组
private final char[] value; //数组是引用类型,final修饰表示赋值后value的值不可变
public String(String str){
this.value = str.value; //将常量字符串所在常量池中的地址赋值给对象属性value
}
4.String类型不可变性说明
因String数据采用final修饰的一维字符数组存储,new创建后,实例中的字符数组value不可被改变
即实例中的value属性的值不可再变。
所以String类型变量进行 增、删、改 等操作后,其本身是不变的,一般要返回新值。
但是String类型的变量只要不用final修饰,它是可以改变的。
如:
String s2 = new String("abc"); //s2指向堆中实例,实例中属性value指向 "abc"
s2 = "def"; //s2改变指向"def"
String s3 = s2.replace('d', 'D'); //s2中字符a替换为A,由于s2不可变且s2未改变指向,所以s2还是"def",s3是"Def"
5.String类构造器
public String()
public String(String original)
public String(char value[])
public String(char value[], int offset, int count)
public String(int[] codePoints, int offset, int count)
public String(byte ascii[], int hibyte, int offset, int count)
public String(byte ascii[], int hibyte)
public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException
public String(byte bytes[], int offset, int length, Charset charset)
public String(byte bytes[], String charsetName) throws UnsupportedEncodingException
public String(byte bytes[], Charset charset)
public String(byte bytes[], int offset, int length)
public String(byte bytes[])
public String(StringBuffer buffer)
public String(StringBuilder builder)
6.String类常用方法
int length(): 返回字符串的长度(字符数量)
char charAt(int index): 返回某索引处的字符
boolean isEmpty(): 判断是否是空字符串(true-是|false-否)
String toLowerCase(): 使用默认语言环境,将字符串所有字符改成小写并返回
String toUpperCase(): 使用默认语言环境,将字符串所有字符改成大写并返回
String trim(): 返回字符串的副本,忽略前导空白、尾部空白
boolean equals(Object obj): 比较字符串的内容是否相同(true-是|false-否)
boolean equalsIgnoreCase(String str): 忽略字符大小写比较字符串内容是否相同(true-是|false-否)
String concat(String str): 将指定字符串连接到字符串的尾部,类似"+"
int compareTo(String str): 比较两个字符串的大小
从左往右逐个字符比较,当出现不相同则返回两个字符的差值(ascll差值)
负数:表示字符串比指定字符串小
等于0:表示字符串与指定字符串相同
正数:表示字符串比指定字符串大
String substring(int beginIndex): 截取字符串,从指定位置至最后一个字符。
String substring(int beginIndex, int endIndex): 截取字符串,从指定位置至指定位置。[beginIndex,endIndex)
boolean endsWith(String suffix): 判断字符串是否以指定的后缀结束(true-是|false-否)
boolean startsWith(String prefix): 判断字符串是否以指定的前缀开始(true-是|false-否)
boolean startsWith(String prefix, int toffset): 判断字符串从指定位置是否以指定的前缀开始(true-是|false-否)
boolean contains(CharSequence s): 当且仅当此字符串包含指定的char至序列时,返回true
int indexOf(String str): 返回指定字符串在字符串中第1次出现的位置(-1表示未找到)
int indexOf(String str, int fromIndex): 返回指定字符串在字符串中从指定的位置开始第1次出现的位置(-1表示未找到)
int lastIndexOf(String str): 返回指定字符串在字符串中最后1次出现的位置(-1表示未找到)
int lastIndexOf(String str, int fromIndex): 返回指定字符串在字符串中从指定的位置开始最后1次出现的位置(-1表示未找到)
String replace(char oldC, char newC): 字符替换,返回替换后的结果
String replace(CharSequence oldSeq, CharSequence newSeq): 字符序列替换,返回替换后的结果
String replaceFirst(String regex, String newStr): 字符串替换,替换第一个匹配的,返回替换后的结果,支持正则
String replaceAll(String regex, String newStr): 字符串替换,替换所有匹配的,返回替换后的结果,支持正则
boolean matches(String regex): 判断字符串是否符合某个正则表达式
String[] split(String regex): 字符串分割, 以符合正则表达式的子串作为分割符。
String[] split(String regex, int limit): 字符串分割, 以符合正则表达式的子串作为分割符。分割数量最多不超过limit个,超过时最后的所有为1个
7.String类型与其他类型的转换
[1-1]String -> 基本数据类型/包装类:调用包装类的静态方法 Xxx.parseXxx(字符串)
int n1 = Integer.parseInt("123");
float n2 = Float.parseFloat("123.2");
double n3 = Double.parseDouble("123.2");
boolean b = Boolean.parseBoolean("true");
char c = "123".charAt(0);
[1-2]基本数据类型/包装类 -> String:调用String类的静态方法 String.valueOf(值)
String str = String.valueOf(123);
[2-1]String -> char[] 调用String类的方法 字符串.toCharArray()
char[] cs = "abcs".toCharArray();
[2-2]char[] -> String 调用String类的构造器
String str = new String(new char[]{'a','b','c'});
[3-1]String -> byte[] 调用String类的方法 字符串.getBytes();
byte[] bs = "abcs".getBytes();//使用默认字符集编码
byte[] bs = "abcs".getBytes("gbk");//使用指定的字符集编码
utf-8字符集中,一个汉字占3个byte,一个字母占1个byte
gbk字符集中,一个汉字占2个byte,一个字母占1个byte
[3-2]byte[] -> String 调用String类的构造器
byte[] bs = "abcs".getBytes();//使用默认字符集编码
String str = new String(bs); //使用默认字符集解码
byte[] bs = "abcs".getBytes("gbk");//使用指定的字符集编码
String str = new String(bs,"gbk"); //使用指定的字符集解码
StringBuffer类、StringBuilder类
这两个类也都是可变字符序列的字符串类型,且这两个类中的属性、方法基本一致。
区别在于:StringBuffer类中的方法都是synchronized修饰的同步方法,线程安全但效率低。
StringBuilder类中的方法不是同步方法,未处理线程安全问题但效率高,效率一般是StringBuffer的2倍。
StringBuilder类为JDK5.0新增。
执行效率:StringBuilder > StringBuffer >> String
1.数据存储采用一维字符数组
private char[] value; //数组是引用类型,没有final修饰表示value可被改变
2.数据可变性说明
因数据存储采用的一维字符数组没有被final修饰,new创建后,实例中的字符数组value可被改变
即实例中的value属性的值可再变。
所以StringBuffer/StringBuilder类型变量进行 增、删、改 等操作后,其本身是可变的。
如:
StringBuffer s2 = new StringBuffer("def"); //s2指向堆中实例,实例中属性value指向 "abc"
StringBuffer s3 = s2.replace(0,1,"D"); //s2中字符a替换为A,由于s2所以s2是"Def",s3是"Def"
3.StringBuffer类、StringBuilder类常用方法
StringBuffer append(形参): 提供多个重载方法,用于添加内容到字符串
StringBuffer delete(int start, int end): 删除指定位置的内容
StringBuffer replace(int start, int end, String str): 把[start,end)位置的内容替换为str
StringBuffer insert(int offset, xxx): 在指定位置插入xxx
StringBuffer reverse(): 把字符序列逆转
int indexOf(String str): 指定字符串首次出现的位置
String substring(int start, int end): 截取[start,end)位置的内容
int length(): 返回字符串实际字符数量
void setCharAt(int n, char c): 将指定位置的字符修改为c
面试题:
1.对于String型变量,字符串常量赋值 与 new创建赋值的区别?
字符串常量赋值:String s1 = “abc”;
此方式赋值变量s1指向的是"abc"所在常量池的内存地址,内存中只会创建一个对象
new创建:String s2 = new String(“abc”);
此方式创建变量s2指向的是堆内存的实例对象,实例中属性value指向"abc"所在常量池的内存地址
内存中会创建2个对象,一个在堆内存中,一个在常量池中。
2.以下代码,在内存中创建了几个对象?
String s = new String(“abc”);
会创建2个对象,一个在堆内存,一个在常量池中。
3.以下代码输出的结果分别是什么?
String s1 = “javaEE”;
String s2 = “hadoop”;
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "hadoop" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
结论:常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
常量与变量的拼接结果在堆中。
字符串调用intern()方法表示取字符串所在常量池的地址。
4.以下代码输出结果是什么?
public class StringTest{
String str = new String(“good”);
char[] ch = {‘t’,‘e’,‘s’,‘t’};
public void change(String str,char[] ch){
str = “tesk ok”; //字符串虽然是引用类型,但是是不可变的,地址传进来后即时修改值也不改变原值
ch[0] = ‘b’;//数组是引用类型,地址传进来后进行操作会影响原值
}
public static void main(String[] args){
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.println(ex.str);//good
System.out.println(ex.ch);//best
}
}
5.常见算法题
模拟一个trim方法, 去除字符串两端的空格。
将一个字符串进行反转。将字符串中指定部分进行反转。
获取一个字符串在另一个字符串中出现的次数。
获取两个字符串中最大相同子串。比如 str1="abcwerthelloyuiodef";str2="cvhellobnm"
对字符串中字符进行自然顺序排序。
6.String、StringBuffer、StringBuilder的异同?(高频面试题)
相同点:
三者都是字符串类型,底层存储都是采用字符数组。
不同点:
String类型是不可变字符序列,StringBuffer/StringBuilder类型是可变字符序列。
String类型除了可以new创建对象,还可以字符串常量直接赋值,StringBuffer/StringBuilder则只能new创建
StringBuffer类中方法都是synchronized修饰的同步方法,线程安全但效率低。
StringBuilder类中的方法不是同步方法,未处理线程安全问题但效率高。不存在线程安全问题时优先用它。
执行效率不同:StringBuilder > StringBuffer >> String
源码层面:
String s1 = new String(); //final char[] value = new char[0];
String s2 = new String(“abc”); //final char[] value = new char[]{‘a’,‘b’,‘c’};
System.out.println(s2.length());//3
StringBuffer s3 = new StringBuffer(); //char[] value = new char[16];
StringBuffer s4 = new StringBuffer("abc"); //char[] value = new char["abc".length+16];
System.out.println(s4.length());//3
1.String通过new创建对象,调用空参构造器时,底层字符数组长度为0
调用有参构造器时,传入的常量是几个字符,底层字符数组长度就为多少
2.StringBuffer/StringBuilder创建对象调用空参构造器,底层字符数组长度为16
调用有参构造器时,传入的常量是几个字符,底层字符数组长度就为:传入的字符数量+16
3.StringBuffer/StringBuilder对象实例中的字符数组value,存储了实际字符及预留的容量
调用length()方法返回的是实际字符数量。
4.StringBuffer/StringBuilder扩容问题:比如当前对象实例中的属性value容量为16个字符
此时通过append方法添加的字符数量超过16,则添加前会进行扩容,扩容逻辑为:
if (当前已有字符数+将要添加的字符数)>value的总容量 {
新容量 = value的总容量*2+2
if (当前已有字符数+将要添加的字符数)>新容量 {
新容量 = (当前已有字符数+将要添加的字符数)
}
}
char[] tmp = new char[新容量];
将原有字符复制到新字符数组
将需要添加的字符追加到新字符数组
将属性value赋值为新字符数组(value指向新数组)
由于扩容过程涉及字符数组创建、数组复制等操作耗时,编写代码时尽量做到不用出现扩容
方法是,new创建对象时,提前指定一个足够的容量:new StringBuffer(长度);