目录
String类
什么是String类?
String类的定义方式
String的内部
如何求字符串长度呢?
String类对象的比较
字符串查找
总结charAt
字符串转化
数字与字符串之间的转化
总结:
字符串常量池
intern方法
一道面试题:
String的不可变性
字符串修改
StringBuffer和StringBuilder
经典面试题
String类 什么是String类?
我们无论是在做oj题还是在学习编程语言时都会遇到字符串,在c语言中我们用字符指针或者字符数组来定义字符串,而在java这门编程语言中专门为它定义了一种类型,我们叫做string类,利用双引号引起来的就是字符串。既然是String类那肯定通过这个字符串调用很多方法,没错在java标准库中,有很多方法,我们可以调用。
String类的定义方式string定义字符串有几种常见的定义方式:
String str1 = "hello";
第一种方式我们可以直接字符串"hello"赋给str。
String str2 = new String("world");
由于String类型是引用类型,在这里我们可以通过new关键字定义字符串
char[] chars = {'c','s','d','n'};
String str3 = new String(chars);//将字符数组组装成字符串
我们还可以讲一个字符数组组装成一个字符串。
String的内部在java中String是一种很特殊的类型,我们来看一下它的内部是怎么构造的。看一下源码是怎么实现的???
根据源码的实现可知:String是由value这个字符数组和hash值组成。
我们也可以根据调试看String类型的构造:
从这里能看出String类型是由字符数组和hash值组成,也能看出在Java中没有像c语言那样以'\0'结尾,在Java中是根据求字符串长度来知道字符串的结尾。
我们也可根据String在内存中的图来理解String类型:
这个str1指向value,value又指向这个字符数组。
如何求字符串长度呢?在Java中求字符串长度格式: 字符串对象的引用.length();
当然我们也可以这样求,利用双引号引起来的就是一个对象,在Java中一切皆对象。
String类对象的比较- 根据==比较
public static void main(String[] args) {
int a =1;
int b =1;
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(a==b);
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str3==str4);
}
这段代码都会打印出什么呢???
是不跟你心里的答案不太一样为啥会打印这样的结果??
首先第一个 是两个int类型进行比较,因为==两边都是基本数据类型也就是int类型,两个值相等答案就是true毋庸置疑。
我们先说第3个,发现str1和str3的内容都是hello,结果却是false,为啥呢??答案就是因为String是引用类型,当通过关键字new实例化的时候他就会产生一个新的对象,所以str1和str3存放的地址不同所以结果为false,可知==是比较左右两个对象的地址。
我们再说第4个,这个有了第3个基础上就更好回答了,因为两个对象都要new,都要产生新的对象,索引两个引用存放地址是不同的,所以结果为false。
我们来说第2个,这个是因为有个叫字符串常量池捣的鬼,第一次str1放的是hello,hello要存放在字符串常量池当中,当我们再一次对象里放的是hello时,它会检查字符串常量池里面有没有hello,如果有他就会将这个对象的引用指向str1,所以这就是String的特殊性,在字符串常量池中只能存在一份。所以str1和str2引用的是同一个对象,地址相同,答案就是true。
总结:
如果==两边是基本数据类型,比较的是值是否相同。
如果==两边是引用类型,比较的是地址是否相同。
- 根据equals比较字符串
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
String str4 = new String("hello");
String str5 = "world";
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str3.equals(str4));
System.out.println(str3.equals(str5));
}
我们可以发现只要引用所指向内容一样,答案就是true。引用所指向的内容不一样,答案就是false。
在这里还要注意一下,equals是依据类型进行比较,比如是比较两个String类型的字符串,那他就会重写String类型的equals方法。如果是object那就会重写object的equals方法根据地址比较。
总结:
equals比较字符串的时候比较的是字符串的内容,字符串内容相同,返回结果就是true,反之为false。
- 根据compareTo比较两个字符串
我们之前学过一个接口是comparable接口,这个接口就要重写compareTo方法,我们知道compareTo是比较两个字符串的大小,依据字典序比较。
public static void main(String[] args) {
String str1 = "hellb";
String str2 = "hella";
String str3 = "world";
String str4 = "hellb";
System.out.println(str1.compareTo(str2));
System.out.println(str1.compareTo(str3));
System.out.println(str1.compareTo(str4));
}
我们可以知道根据compareTo比较,比较的是两个字符串的大小,从第一个字符一个一个按照字典序进行比较,如果字符串1大于字符串二返回大于0的数组,这个数字就是两个字符间的间距,如果小于0,返回的是两个字符间的负间距,也就是返回小于0的数字,当这两个字符串相等的时候返回0.
此外还有一个compareToIgnoreCase:
这个compareToIgnoreCase是忽略大小写进行比较两个字符串
比如:
public static void main(String[] args) {
String str1 = "hello";
String str2 = "HELLO";
System.out.println(str1.compareToIgnoreCase(str2));
}
总结:
compareTo比较的是两个字符串的大小,从第一个字符按照字典序进行一一比较。
而compareToIgnoreCase是忽略两个字符串的大小写进行比较。
字符串查找
在java中标准库为了开发更方便,实现了很多有关字符串方法,我们接下来讲一讲字符串查找。
- charAt(int index)
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.charAt(0));
System.out.println(str1.charAt(1));
System.out.println(str1.charAt(2));
}
charAt(下标),是拿到某个下标的字符。
当你设置的下标超出范围就会报出异常;
总结charAtcharAt(int index)方法是拿到下标index的字符。当设置的下标超出范围就会报异常。
- indexof
int indexOf(int ch) | 返回ch第一次出现的位置,没有返回-1 |
int indexOf(int ch, int fromIndex) | 从fromIndex位置开始找ch第一次出现的位置,没有返回-1 |
int indexOf(String str) | 返回str第一次出现的位置,没有返回-1 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始找str第一次出现的位置,没有返回-1 |
- 第一个:indexOf(int ch) 返回第一次ch出现的位置,如果没有找到返回-1
- 第二个 :indexOf(int ch,int fromIndex)从fromIndex位置开始寻找字符ch第一次出现的位置
从fromIndex的位置开始寻找字符ch,如果找到了返回下标,如果没有找到返回-1,
- 第三个:indexOf(String str) 找到了str返回str的第一个字符的下标,没有找到返回-1;
- lastIndexOf
int lastIndexOf(int ch) | 从后往前找,返回ch第一次出现的位置,没有返回-1 |
int lastIndexOf(int ch, int fromIndex) | 从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返 回-1 |
int lastIndexOf(String str) | 从后往前找,返回str第一次出现的位置,没有返回-1 |
int lastIndexOf(String str, int fromIndex) | 从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返 回-1 |
public static void main(String[] args) {
String str = "Hello,programmer";
System.out.println(str.lastIndexOf('p'));//从字符串最后位置开始找字符p
System.out.println(str.lastIndexOf("pro"));//从字符串最后位置开始找字符串pro
System.out.println(str.lastIndexOf('p',14));//从14的位置往前找字符p
System.out.println(str.lastIndexOf("pro",4));//从4位置往前找字符串pro
System.out.println(str.lastIndexOf("pr",10000));//超出范围就会从最后位置寻找
}
字符串转化 数字与字符串之间的转化
- 数字转字符串 ->利用Stirng.valueOf(int x)
- 字符串转整数 1.Integer.valueOf 2.Integer.parseint
- 大小写转换 大写转小写 -> toLowerCase() 小写转大写->toUpperCase()
- 字符串转数组->toCharArray()
- 数组转字符串 ->toStirng
- 字符串替换->replace
- 字符串拆分 ->split
- 分为多组拆分
- 分为多次拆分
- split如果有特殊字符需要转义
- 字符串截取 subString
- 去除字符串中的左右空格->trim
- 总结:
根据上面分析字符串String类的方法可以发现,只要我们对字符串进行 *** 作都不是在字符串本身 *** 作而是又创建了一个新的对象。
字符串常量池
我们先思考一下这段代码:
public static void main(String[] args) {
String str1 = "hello,world";
String str2 = new String("hello,world");
String str3 = "hello,world";
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str3);
}
让我们来分析一下为啥会出现这样的结果呢???
我们回忆一下==是判断什么来着???
没错,如果==左右两边是基础数据类型判断的就是值的大小,如果==左右两边是引用类型的他就判断的是地址是否相等。
1.str1==str2显然这里由于str2要new一个对象,所以会重写创建一块内存空间,地址会不一样,答案就为false。同理str2与str3判断相等也是与1类似的。
2.str1==str3,为啥这里答案为false呢??不是说String是引用类型么,都会new一个对象么??
答案为啥会是这样的呢??这就与我们的字符串常量池相关了。
什么是字符串常量池呢??
字符串常量池StringTable以前存放在方法区中,而现在java版本更新把字符串常量池放在了堆中,字符串常量池底层是由哈希表来实现的,用来存储字符串常量(也就是被双引号引起来的字符串)的地址,目的是为了避免频繁的创建和销毁对象而影响系统性能,它也实现了对象的共享,节省内存空间。
1. 在JVM中字符串常量池只有一份,是全局共享的
2. 刚开始字符串常量池是空的,随着程序不断运行,字符串常量池中元素会越来越多
3. 当类加载时,字节码文件中的常量池也被加载到JVM中,称为运行时常量池,同时会将其中的字符串常量保存在字符串常量池中
4. 字符创常量池中的内容:一部分来自运行时常量池,一部分来自程序动态添加
也就是说在字符串常量池中只保存相同字符串常量的一份地址,这份地址是全局共享的,每一次要放入字符串常量池之前都要检查一下,这个字符串常量的地址常量池里是否存在,如果字符串常量池中存在这份地址,就不要再将这个地址放入常量池,只需将这个地址存到这个对象变量即可。
画图理解一下:
intern方法
public static void main(String[] args) {
String str2 = new String("hello,world");
str2 = str2.intern();
String str1 = "hello,world";
String str3 = "hello,world";
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str3);
}
这时当我们使用了intern方法,它会检查字符串常量是否在常量池当中,如果没有就把它放在字符串常量池中,如果有就返回它的引用。
一道面试题:
请解释String类中两种对象实例化的区别:(每一个题都不在上一个基础上)
1. String str = "hello"
将hello放入常量池当中,这个str引用,引用的就是这个放在常量池当中hello的这个对象,只会在堆中开辟一块空间。
2. String str = new String("hello");
如果面试官问这个题,我们应该这么回答,这行代码创建一个或者两个对象,一是当检查字符串常量池中有没有hello,如果发现常量池没有hello,他就会在字符串常量池中创建,然后在通过new创建一个对象,这个String对象的value数组就存放着常量池中的hello,而str就引用这个新在堆内存创建的对象。
3. String str = new String(new char[]{'h', 'e', 'l', 'l', 'o'})
这里首先new了一个字符数组,然后根据:
这里会拷贝一个char类型的字符数组然后String对象中的value数组就指向这个拷贝的char类型的字符数组。这里就会创建三个对象
String的不可变性
为啥String不可变呢???
我们可以看一下String的源码:
从这里可以看到:String包含两个成员,一个是char 类型的value数组,一个是hash值。
那为啥String会不可变呢,第一这个String类是被final所修饰的,证明这个类是不能被继承的,第二这个String的成员的char类型的value数组是被final+private所修饰,他也没有提供get或set方法我们并不能访问到这个数组所以不能被修改,注意不是因为她被final所修饰他就不可变,这个成员final修饰只是代表这个value数组的指向不可改变。还有一点就是因为对String类型的 *** 作总是要返回一个新的对象而不是在本身改变。
- String这个类被final所修饰,不能被继承。
- 这个String成员的value数组被private+final所修饰,并且没有提供get或者set方法,并不能够访问到,所以不能修改
- 对String类型的 *** 作总是返回一个新的对象,而不是在本身 *** 作。
字符串修改
我们如果要对字符串类型进行修改,怎么修改呢???那就只能使用 + 号来进行拼接
public static void main(String[] args) {
String str = "";
for(int i =0;i<10;++i) {
str+="a";
}
System.out.println(str);
}
这样写虽然能得出拼接出来的结果,但是你并不知道它究竟会做什么???
看一下汇编代码:
根据汇编代码,有个new,我们还记得对String类型 *** 作的时候,需要返回给一个全新的对象,熟不知这里在拼接的时候会创建大量的临时对象,这样需要频繁的创建和销毁。
从这个汇编代码我们还可以知道,这个String类型会被优化成Stringbuilder,也就是先创建一个StirngBuilder这个对象,然后调用两次append的方法进行拼接,在调用toString方法把拼接的之后的调用toString方法返回到这个对象中。
StringBuffer和StringBuilder
对于String类型,当我们进行拼接的时候会产生大量的临时对象,这就会造成效率非常的低下。
所以,如果要进行修改的话,我们就可以shiyongStringBuffer或者Stringbuilder,它们是可变的,通过调用它们的append方法进行拼接,并且他们不会创建临时对象,而是把拼接好的返回当前对象。
我们怎么使用StringBuffer和StringBulider呢???
public static void main(String[] args) {
//使用StringBuffer
StringBuffer stringBuffer = new StringBuffer();
for(int i =0;i<10;++i) {
stringBuffer.append("a");
}
System.out.println(stringBuffer);
//使用StringBuilder
StringBuilder stringBuilder = new StringBuilder();
for(int i =0;i<10;++i) {
stringBuilder.append("a");
}
System.out.println(stringBuilder);
}
- 为什么说StringBuffer或者StringBuilder拼接不会产生临时对象呢???
原因是StringBuffer或者StringBuilder拼接是利用append。我们看一看append的原码
他返回的是this,this代表当前对象的引用,所以就返回到当前stringbuilder对象里,不会产生大量临时对象。
注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
StringBuilder变为String: 调用toString()方法
StringBuffer StringBuilder的区别??
两个对比可知,他两差了一个关键字synchronized,这个关键字代表线程安全。
经典面试题
面试官:String StringBuffer StringBuilder的区别???
- 首先String与这两个的区别:string的拼接会被优化成Stringbuilder的append的拼接,String不可变,而StringBuffer StringBuilder可变。同时String拼接会产生大量的临时对象,而StringBuffer StringBuilder不会产生,而是将拼接好的返回到当前对象。
- StringBuffer StringBuilder有String没有的方法,同时StringBuffer StringBuilder大部分功能类似
- StringBuffer是线程安全的 StringBuilder不是线程安全的
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)