Java
基础之字符串操作—— String
什么是 字符串? 如果直接按照字面意思来理解就是 多个字符连接起来组合成的字符序列 。为了更好的理解以上的理论,我们先来解释下字符序列, 字符序列:把多个字符按照一定的顺序排列起来 ;而字符序列就是作为字符串的内容而存在的。所以可以把字符串理解为:把多个字符按照一定的顺序排列起来而构成的排列组合。
如果还是不好理解,没有关系,我还有法宝。我们可以用烤串来比喻说明,可以把字符串看作是烤串,烤串上的每一块肉都相当于是一个字符。把一块块肉按照肥瘦相间的顺序排列并串起来便成了我们吃的烤串,同理, 把多个字符按照一定的顺序“串”起来就构成了字符串。
字符串的分类,字符串分为 可变的字符串 和 不可变的字符串两种 ;这里的不可变与可变指的是字符串的对象还是不是同一个,会不会因为字符串对象内容的改变而创建新的对象。
Java
中的 String
类的对象就是不可变的。 StringBuilder
类和 StringBuffer
类的对象就是可变的;当对象创建完毕之后,该对象的内容发生改变时不会创建新的对象,也就是说对象的内容可以发生改变,当对象的内容发生改变时,对象保持不变,还是同一个。 String类表示不可变的字符串,当前 String
类对象创建完毕之后,该对象的内容(字符序列)是不变的,因为内容一旦改变就会创建一个一个新的对象。
String s1 = "laofu";
需要注意这里是双引号: ""
,区别与字符char类型的单引号: ''
; String s2 = new String("laofu");
;
上图中的 常量池 :用于存储常量的地方内存区域,位于方法区中。常量池又分为 编译常量池 和 运行常量池 两种:
JVM
String s1 = "laofu";
有可能只创建一个 String
对象,也有可能创建不创建 String
对象;如果在常量池中已经存在"laofu",那么对象 s1
会直接引用,不会创建新的 String
对象;否则,会先在常量池先创建常量 "laofu"
的内存空间,然后再引用。 String s2 = new String("laofu");
最多会创建两个 String
对象,最少创建一个 String
对象。可使用 new
关键字创建对象是会在堆空间创建内存区域,这是第一个对象;然后对象中的字符串字面量可能会创建第二个对象,而第二个对象如方式一中所描述的那样,是有可能会不被创建的,所以至少创建一个 String
对象。 字符串的本质,字符串在底层其实就是 char[]
, char
表示一个字符,比如:
String str = "laofu"; 等价于 char[] cs = new char[]{'l','a','o','f','u'};
String s1 = null;
此时 s1
没有初始化,也在 JVM
中没有分配内存空间。 String s2 = "";
此时对象s2已经初始化,值为 ""
, JVM
已经为其分配内存空间。 字符串的比较:使用 "=="
和 "equals"
会有不同效果,详情在之前的文章中分享过: [JAVA] 只知对象属性,不知类属性?就算类答应,static都不答应
==
号:用于比较对象引用的内存地址是否相同。 equals
方法:在 Object
类中和 ==
号相同,但在自定义类中,建议覆盖 equals
方法去实现比较自己内容的细节;由于 String
类覆盖已经覆盖了 equals
方法,所以其比较的是字符内容。
所以可以这样来判断字符串非空:
s1 != null;
; ""
): "".equals(s1);
; 如果上述两个条件都满足,说明字符串确实为空!
字符串拼接: Java
中的字符串可以通过是 "+"
实现拼接,那么代码中字符串拼接在 JVM
中又是如何处理的呢?我们通过一个例子说明:通过比较拼接字符串代码编译前后的代码来查看 JVM
对字符串拼接的处理。
通过上述例子不难发现, JVM
会对字符串拼接做一些优化操作,如果字符串字面量之间的拼接,无论有多少个字符串, JVM
都会一样的处理;如果是对象之间拼接,或者是对象和字面量之间的拼接,亦或是方法执行结果参与拼接, String
内部会使用 StringBuilder
先来获取对象的值,然后使用 append
方法来执行拼接。由此可以总结得出:
""
引号创建的字符串都是直接量,在编译期就会将其存储到常量池中; new String("")
创建的对象会存储到堆内存中,在运行期才创建; "aa" + "bb"
创建的也是直接量,这样的字符串在 编译期 就能确定,所以也会存储到常量池中; String
直接量的字符串表达式(如 "aa" + s1
)创建的对象是运行期才创建的,对象存储在堆中,因为其底层是创新了 StringBuilder
对象来实现拼接的; 5. 无论是使用变量,还是调用方法来连接字符串,都只能在 运行期 才能确定变量的值和方法的返回值,不存在编译优化操作。
这里列举了一些常用 String API
,更多的可以查阅 jdk使用手册
,做 Java
一定得学会查阅 jdk手册
。
byte[] getBytes()
:把字符串转换为 byte
数组。 char[] toCharArray()
:把字符串转换为 char
数组。 String(byte[] bytes)
:把 byte
数组转换为字符串。 String(char[] value)
:把 char
数组转换为字符串。 int length()
:返回此字符串的长度。 char charAt(int index)
:返回 指定索引 处的 char
值。 int indexOf(String str)
:返回指定字符串在此字符串中首次(从最左边算起)出现处的索引。 int lastIndexOf(String str)
:返回指定字符串在此字符串中最后(最右边算起)出现处的索引。 boolean equals(Object anObject)
: 将此字符串与指定的对象比较。 boolean equalsIgnoreCase(String anotherString)
: 将此 String 与另一个 String 做忽略大小写的比较。 boolean contentEquals(CharSequence cs)
: 将此字符串与指定的 CharSequence
比较,比较的是内容;
字符串大小写转换:调用方法的字符串就是当前字符串
String toUpperCase() String toLowerCase()
先来分别使用 String/StringBuilder/StringBuffer
来拼接 30000次
字符串,对比各自损耗的时间,经过测试发现:
String
做字符串拼接的时候,耗时最高,性能极低,原因是 String
内容是不可变的,每次内容改变都会在内存中创建新的对象。
性能最好的是 StringBuilder
,其次是 StringBuffer
,最后是 String
。 StringBuilder
和 StringBuffer
区别并不是很大,也有可能是测试次数还不够吧。感兴趣的小伙伴可以增加拼接次数来看看。代码很简单,就不展示出来了。
所以在开发中拼接字符串时,优先使用 StringBuffer/StringBuilder
,不到万不得已,不要轻易使用 String
。
StringBuffer
和 StringBuilder
都表示可变的字符串,两种’的功能方法都是相同的。但唯一的区别:
StringBuffer
: StringBuffer
中的方法都使用了 synchronized
修饰符,表示同步操作,在多线程并发的时候可以保证线程安全,但在保证线程安全的时候,对其性能有一定影响,会降低其性能。 StringBuilder
: StringBuilder
中的方法都没有使用了 synchronized
修饰符,线程不安全,正因为如此,其性能较高。 对并发安全没有很高要求的情况下,建议使用 StringBuilder
,因为其性能很高。像这样的情况会较多些。使用 StringBuilder
无参数的构造器,在底层创建了一个长度为 16
的 char
数组:
此时该数组只能存储 16
个字符,如果超过了 16
个字符,会自动扩容(创建长度更大的数组,再把之前的数组拷贝到新数组),此时性能极低;如果事先知道大概需要存储多少字符,可以通过构造器来设置字符的初始值:
//创建一个长度为80的char数组. new StringBuilder(80);
append(Object val)
:追加任意类型数据到当前 StringBuilder
对象中。 StringBuilder deleteCharAt(int index)
:删除字符串中指定位置的字符。 完结。虽然老夫不正经,但老夫一身的才华