文章目录
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类型。具体比较方式:
1.先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2.如果前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是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
1.String类在设计时就是不可变的
2.String类中的字符实际是在内部的value字符数组中,通过源码可知String是被final修饰,不能被继承,同时把value被final以及private修饰表明value本身的值是不能修改的,也是就是不能引用其它数组,但是对于一个数组是可以通过下标访问修改其数组对应的值,而此时在String类外压根拿不到value故字符串不可变。
3.所以涉及到可能修饰字符串内容的操作都是创建一个新的对象,改变的新的对象,源码如下。
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类不能直接转换。如果需要转换可以采用一下方式:
- 1.String转StringBuilder:利用StringBuilder的构造方法或者append()方法。
- 2.StringBuilder变成Sring:调用toString()方法。
8.面试题
1,String、StringBuffffer、StringBuilder的区别
String的内容不可修改,StringBuffffer与StringBuilder的内容可以修改。
StringBuffffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操
作。
2.以下总共创建了多少个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对象中(三个)
最后的话
各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。如果你想变强那么点我点我 牛客网。