博客主页:KC老衲爱尼姑的博客主页
博主的github,平常所写代码皆在于此
刷题求职神器
共勉:talk is cheap, show me the code
作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
刷题求职神器
在下给诸位推荐一款巨好用的刷题求职神器,如果还有小伙伴没有注册该网站,可以点击下方链接直接注册,注册完后就可以立即刷题了。
传送门:牛客网
文章目录
- 1.String概述
-
- 2.String类常用的构造方法
- 3.字符串方法
- 4.什么是池?
-
- 4.1字符串常量池
-
- 4.2再谈String对象创建
- 4.3intern方法
- 5.字符串的不可变性
- 6字符串修改
- 7.StringBuilder和StringBuffffe
- 8.面试题
1.String概述
String是Java中的引用类型,位于java.lang下,该类所定义的变量可用于指向字符串对象,然后来操作该字符串。
String既然是一个类,那么可以从该类的属性以及构造方法出发,去认识该类。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
通过源码可知,String实现了三个接口,首先java.io.Serializable是一个空接口,作用就是标识该类,说明此类可以被序列化,Comparable接口是用于比较大小的接口,最后一个CharSequence接口,该接口是char值的可读序列, 该接口为其实现类提供统一的,只读访问许多不同类型的char序列。
2.String类常用的构造方法
String类提供了许多的构造方法,但是最常用有以下几种。
@Test
public void testString(){
//使用常量串构造
String s = "hello";
System.out.println(s);
//newString对象
String s2 = new String("world");
System.out.println(s2);
//使用字符数组进行构造
char [] arr ={'a','b','c'};
String s3 = new String(arr);
System.out.println(s3);
}
通过源码可以看到String的底层是一个被private以及final修饰的字符数组
private final char value[];
private int hash; // Default to 0
通过调试也能证明底层确实是一个数组,只是它的组成部分还有hash。
1.直接使用常量串构造详解
2.newString详解
3.使用字符数组进行构造详解
当传入字符数组时 ,底层会拷贝一份字符数组并将拷贝后数组的引用给字符串对象的value。
传入字符数组时String的构造方法
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
3.字符串方法
String对象的比较
字符串的对象的比较分为以下4中
1.== 比较是否引用同一个对象
@Test
public void testString2(){
String s1 = new String("hmr");
String s2 = new String("hmr");
String s3 = new String("yzq");
String s4 = s1;
System.out.println(s1==s3);//fasle
System.out.println(s1 == s2);//false
System.out.println(s4==s1);//true
}
2.boolean equals(Object anObject) 方法:按照字典序比较
String 重写了Object中的equals方法,因为Object中的equals方法默认按照==来比较,String类重写后会按照字典序来比较
String重写后equals
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
示例
@Test
public void testString3(){
String s1 = new String("hmr");
String s2 = new String("hmr");
String s3 = new String("yzq");
String s4 = s1;
System.out.println(s1.equals(s3));//false
System.out.println(s1 .equals(s2));//true
System.out.println(s4.equals(s1));//true
}
3.int compareTo(String s) 方法 按照字典序进行比较
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:
-
先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
-
如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
示例
@Test
public void testString4(){
String s1 = new String("hmr");
String s2 = new String("hmr");
String s3 = new String("yzq");
String s4 = s1;
System.out.println(s1.compareTo(s2));//0
System.out.println(s2 .compareTo(s3));//-17
System.out.println(s4.compareTo(s1));//0
}
4.int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较。
示例
@Test
public void testString5(){
String s1 = new String("HMR");
String s2 = new String("hmr");
String s3 = new String("YZQ");
String s5 = new String("yzq");
String s4 = s1;
System.out.println(s1.compareToIgnoreCase(s2));//0
System.out.println(s2 .compareToIgnoreCase(s3));//-17
System.out.println(s4.compareToIgnoreCase(s1));//0
System.out.println(s5.compareToIgnoreCase(s3));//0
}
4.什么是池?
由于我们经常对这些字符串常量(常用资源)进行操作,而每次使用时都会开辟相应的内存,为了是程序运行的速度加快,就以空间来换时间,即事先将要频繁使用的资源放入空间中,当我们需要操作时直接从空间来拿使用就行了,这个空间就是池。这就好比张三家里没有冰箱,那么想吃冰棒得去小卖部,张三每天都出去觉得太麻烦了,于是自己买了个冰箱,在向冰箱里屯了许多冰棒,以后向吃就可以随时吃,就节约了大量的时间。
4.1字符串常量池
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(数组+链表(val为字符串对象))。不同版本的jdk下的字符串常量池的位置和大小的是不同的再次讨论的是jdk8下的字符串常量池,jdk8中该池位于堆内存中,池的大小可以设置,其最小值是1009。
4.2再谈String对象创建
1.直接使用字符串常量进行赋值
@Test
public void testString6(){
String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2);//true
}
2.通过new创建String类对象
@Test
public void testString6(){
String s3 = new String("hello");
String s4 =new String ("hello");
System.out.println(s3==s4);//false
}
当直接使用字符串常量进行赋值时,在加载字节码文件时,“hello”在字符串中已经创建好并保存在字符串常量池中,当代码走到String s1 = “hello”;创建对象时,会优先在字符串常量池中查找是否有该字符串,当找到了该字符串则将该字符串的引用赋值给s1,如果没有则创建新的字符串对象并入池。通过new创建的字符串类对象,首先会在堆内存开辟一个String对象,然后向字符串常量池中查找该字符是否存在,如若存在则将字符数组的引用赋值给字符串对象的value,反之则直接创建新的字符串对象。
通过上述,可以得出new的对象是唯一,并且使用常量串创建的String类型对象的效率更高,更节约空间。
4.3intern方法
intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。
代码示例
@Test
public void testString7(){
char [] arr = new char[]{'a','b','c'};
String s1 = new String(arr);
String s2 = "abc";
System.out.println(s1==s2);//false
}
@Test
public void testString7(){
char [] arr = new char[]{'a','b','c'};
String s1 = new String(arr);
s1.intern();
String s2 = "abc";
System.out.println(s1==s2);//true
}
当s1调用ntern()方法后,会将s1对象放入到字符串常量池,故s1==s2。
5.字符串的不可变性
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
- String类在设计时就是不可变的,
- String类中的字符实际是在内部的value字符数组中,通过源码可知String是被final修饰,不能被继承,同时把value被final以及private修饰表明value本身的值是不能修改的,也是就是不能引用其它数组,但是对于一个数组是可以通过下标访问修改其数组对应的值,而此时在String类外压根拿不到value故字符串不可变。
- 所以涉及到可能修饰字符串内容的操作都是创建一个新的对象,改变的新的对象,源码如下。
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
6字符串修改
错误的使用String进行拼接
public class Test {
public static void main(String[] args) {
String s = "";
for (int i = 0;i<10_000;i++) {
s+= i;
}
System.out.println(s);
}
}
使用String进行字符串拼接效率极其低下,之所以速度慢,可以通过查看Test的汇编代码来究其本质
通过汇编得知每次进行字符串拼接时都会new一个StringBuilder对象,这也意味着会程序的运行速度是非常低下的,因此尽量不使用String直接拼接字符串,可以使用StringBuilder或者StringBuffer。
7.StringBuilder和StringBuffffe
由于String类型的字符串不可更改,为了可以高效的进行字符串修改,Java提供了StringBuffer和StringBuilder类,这两个类大同小异,这里就介绍几个常用的API更多的方法,更多需求自行查看 StringBuildre在线文档。
1.使用StringBuilder进行拼接字符串
@Test
public void testString9(){
long start = System.currentTimeMillis();
StringBuffer s = new StringBuffer("");
for (int i = 0;i<10_0000;i++) {
s.append(i);
}
long end = System.currentTimeMillis();
System.out.println(end-start);//18
}
@Test
public void testString8(){
long start = System.currentTimeMillis();
String s = "";
for (int i = 0;i<10_0000;i++) {
s+= i;
}
long end = System.currentTimeMillis();//32139
System.out.println(end-start);
}
从上述代码的运行时间上来看,StringBulder比String速度快了n倍。
2.反转一个字符串
@Test
public void testStringBuilder(){
StringBuilder s= new StringBuilder("hello world");
System.out.println(s.reverse().toString());
}
注意:String和StringBuilder类不能直接转换。如果需要转换可以采用一下方式:
- String转StringBuilder:利用StringBuilder的构造方法或者append()方法。
- StringBuilder变成Sring:调用toString()方法。
8.面试题
1,String、StringBuffffer、StringBuilder的区别
-
String的内容不可修改,StringBuffffer与StringBuilder的内容可以修改。
-
StringBuffffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操
作。
- 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】
1.String str = “hello”;
只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象(1个)
2.String str = new String(“hello”)
会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟
的String对象赋值。(2个)
3.String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})
先在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到
String对象中(三个)
最后的话
各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。如果你想变强那么点我点我 牛客网。