Java的字符串操作一些简单的思考
Java的字符串操作
1 .1不可变的String
String对象事不可变的,String类中的每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初修改的String对象则丝毫未动。
public class text {
public static String upcase(String s){
return s.toUpperCase();
}
public static void main(String[] args) {
String s = "Hello";
System.out.println(s);
String ss = upcase(s);
System.out.println(ss);
System.out.println(s);
}
}
/*
运行输出:
Hello
HELLO
Hello
*/
当s传给upcase()方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用的所指的对象其实一直待在单一的物理位置上,从未动过。因此,字符串操作不改变原字符串内容,而是返回新字符串。
早期JDK版本的String
总是以char[]
存储,它的定义如下:
public final class String {
private final char[] value;
private final int offset;
private final int count;
}
而较新的JDK版本的String
则以byte[]
存储:如果String
仅包含ASCII字符,则每个byte
存储一个字符,否则,每两个byte
存储一个字符,这样做的目的是为了节省内存,因为大量的长度较短的String
通常仅包含ASCII字符:
public final class String {
private final byte[] value;
private final byte coder; // 0 = LATIN1, 1 = UTF16
1.2提升效率的StringBuilder
Java工程师规定了一样好东西,为String对象重载的“+”操作符,使得我们可以直接用“+”拼接字符串,但随之而来也给String带来了一定的效率问题。而为了高效拼接字符串,Java提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,往StringBuilder中新增字符时,不会创建临时对象。
参考以下用操作符“+”连接String:
public class text {
public static void main(String[] args) {
String hello = "Hello";
String s = "abc" + hello + "DEF";
System.out.println(s);
}
}
/*
运行输出:
abcHelloDEF
*/
之后反编译以上代码
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String Hello
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String abc
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String DEF
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 100
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2
33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
}
基于JDK8版本去反编译(JDK9版本反编译结果非常简单,据说是为了更加统一字符串的优化,提供了StringConcatFactory,作为统一入口,虽然更简单但不利于我理解,争取之后能更加看懂吧!)中可以看出,编译器创建了一个StringBuilder对象,用以构造最终的String,并为每个字符调用一个StringBuilder的append()方法,总计4次,最后调用toString()生成结果,并存为s。虽然我们在源代码中并没用使用StringBuilder类,但是编译器却自作主张地使用了它,因为他更加高效。
虽然有点马后炮,但是不妨想一想编译器要是不为我们自动优化会产生什么结果:String可能有一个append()方法,它会生成一个新的String对象,以包含“abc”与hello连接后的字符串。然后该对象再与“DEF”相连,生成一个新的String对象。假如之后还有需要相连的,便以此类推。而正因String类对象的不可变性,造成字符串连接需要多个中间对象,造成程序执行时的内存浪费并且需要处理垃圾回收,增加工作量。
1.3 线程安全StringBuffer
查阅一些资料,发现最初是先有StringBuffer的,后来的版本增加了StringBuilder,而这两者为了实现修改字符序列的目的,StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组,二者都继承了 AbstractStringBuilder,里面包含了基本操作,最大区别在于最终的方法是否加了 synchronized。
线程安全:
StringBuffer:线程安全,StringBuilder:线程不安全。因为 StringBuffer 的所有公开方法都是 synchronized 修饰的,而 StringBuilder 并没有 StringBuilder 修饰。
缓冲区:
StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。
性能:
StringBuffer 是线程安全的,它的所有公开方法都是同步的,StringBuilder 是没有对方法加锁同步的,StringBuilder 的性能要远大于 StringBuffer。
1.4简单的程序测试检测效率
/*
实现的东西很简单,就是使用三种不同的方式来拼接字符串,以此来观察效率和内存消耗
*/
public class WitherStringBuilder {
public String implicit(String[] fields){//编译器隐式创建StringBuilder
String result = "";
for (int i = 0; i < fields.length; i++){
result += fields[i];
}
return result;
}
public String explicitStringBuilder(String[] fields){
//直接用StringBuilder的append()方法拼接
StringBuilder result = new StringBuilder();
for (int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
}
public String explicitStringBuffer(String[] fields){
//直接用StringBuffer的append()方法拼接
StringBuffer result = new StringBuffer();
for (int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
}
}
我们采用这么一段代码,其实也就是对字符串的拼接分别采用3种不同的方式,与此同时查看各自反编译后运行的逻辑比对与实际运行时的效率比对。程序入口采用了Java内置的一些查看内存消耗和程序运行时间的方法,并在控制台输出方便我们查看。关键代码如下:
public class Main {
public static void main(String[] args) {
String[] fields = new String[10000];//这里设置需要监测的字符串数组大小
String result;
for (int i = 0; i < fields.length; i++) {
fields[i] = "abc";
}
Examination.start();//监测程序运行的时间和内存消耗,具体代码不再这累赘
//减少工作量,每次只运行以下其中一条,分别对应
//1.用String拼接字符串
String s = new WitherStringBuilder().implicit(fields);
//2.用StringBuilder拼接字符串
String s = new WitherStringBuilder().explicitStringBuilder(fields);
//3.用StringBuffer拼接字符串
String s = new WitherStringBuilder().explicitStringBuffer(fields);
Examination.end();
}
在10000个单元数量级的字符串数组进行拼接的情况下,出现以下结果:
采用String拼接:
---------------您的代码执行时间为:300.10 ms, 消耗内存:138.11 M
采用StringBuilder拼接:
---------------您的代码执行时间为:1.02 ms, 消耗内存:0 M
采用StringBuffer拼接:
---------------您的代码执行时间为:2.07 ms, 消耗内存:0.64 M
在100000个单元数量级的字符串数组拼接的情况下,出现以下结果:
采用String拼接:
---------------您的代码执行时间为:16943.61 ms, 消耗内存:250.40 M
采用StringBuilder拼接:
---------------您的代码执行时间为:7.42 ms, 消耗内存:2.68 M
采用StringBuffer拼接:
---------------您的代码执行时间为:7.40 ms, 消耗内存:2.68 M
结果分析:以上简单的两组结果,用到的数量级从一万到十万,不难看出String用于拼接字符串简直既消耗时间,又消耗内存!(属实慢!!)StringBuilder和StringBuffer相差无几,但是当数量级上去,涉及线程安全的StringBuffer还是会略显弟弟。
接下来我尝试反编译去查看它的处理逻辑,分析为何他们差别为何会如此之大。
implicit方法:
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39: areturn从第8行到第35行构成了一个循环体,重点是在这个循环体里,每循环一次就要创建一个StringBuilder对象。这应该可以解释,采用String直接用“+”拼接字符串,会产生过多冗余的中间对象,浪费很多内存,导致执行相同结果的代码却有天差地别的效率体现!
explicitStringBuilder方法:
public java.lang.String explicitStringBuilder(java.lang.String[]);
Code:
0: new #3 // class java/lang/StringBuilder
3: dup
4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: aload_2
31: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn显式地创建StringBuilder意味着源代码部分我就告诉编译器用StringBuilder,循环过程中可以看出来只生成了一个StringBuilder对象,操作效率高,内存浪费也少!
explicitStringBuffer方法:其实同上面StringBuffer,因为还没涉及到线程安全问题暂且不累赘分析
Java的字符串操作一些简单的思考的更多相关文章
- JAVA作业—字符串操作
------------恢复内容开始------------ ------------恢复内容开始------------ ------------恢复内容开始------------ ------- ...
- Java的字符串操作
目录 Java的字符串操作 一.不同字符串操作的对比 1.1 C++中const修饰指针 const在星号的左边,是被指向的常量不可变 const在星号的右边,是指针的指向不可变 二. Java字符串 ...
- java类库字符串操作
在java类库中,java给我们提供了字符串几个特别的操作,分别是String,Stringbuffer,Stringbuilder等.下面就对这几个类做一个简单的介绍.首先,我们先了解一下Strin ...
- Java Script 字符串操作
JS中常用几种字符串操作: big() small() bold() fontcolor() fontsize() italics() strike() link() charAt() charCod ...
- Java String 字符串操作小结
// 转载加编辑 -- 21 Apr 2014 1. Java字符串中子串的查找 Java中字符串中子串的查找共有四种方法,如下: 1.int indexOf(String str) :返回第一次出现 ...
- 使用javap分析Java的字符串操作
我们看这样一行简单的字符串赋值操作的Java代码. String a = "i042416"; 使用命令行将包含了这行代码的Java类反编译查看其字节码: javap -v con ...
- 四:Java之字符串操作String、StringBuffer和StringBuilder
string是我们经经常使用到的一个类型,事实上有时候认为敲代码就是在重复的操作字符串,这是C的特点,在java中.jdk非常好的封装了关于字符串的操作.三个类String .StringBuffer ...
- Java基础(一)-- Java对字符串操作大全
一.Java字符串类基本概念 在JAVA语言中,字符串数据实际上由String类所实现的.Java字符串类分为两类:一类是在程序中不会被改变长度的不变字符串:二类是在程序中会被改变长度的可变字符串.J ...
- android TextView字体设置最少占多少行. 及其 Java String 字符串操作 . .
① 字体设置: 修改代码 : GridViewActivity.java priceTv为 TextView priceTv.setMaxLines(3); //当多与7个字fu的时候 , 其余字 ...
随机推荐
- 关于Wrapper Class
public class RunTest{ public static void main(String[] args) { Integer ten=new Integer(10); Long nin ...
- innodb为什么需要doublewrite(转)
InnoDB的page size默认是16KB,而操作系统的一个block size是4KB,磁盘io block则更小.那么InnoDB的page刷到磁盘上要写4个操作系统block,在极端情况下( ...
- 02 Filter过滤器
Filter 一.Filter过滤器 Filter过滤器它是JavaWeb的三大组件之一.三大组件分别是:Servlet程序.Listener监听器.Filter过滤器 Filter过滤器是JavaE ...
- NOIP前一些题目的理解
ZYB和售货机(图论,环) 题目链接 个人感觉这道题与基环树没有任何关系,你会发现,每个点最多只有一个入度和出度,所以只能是链或环. 还有就是本题的突破点就在于正确建图,题目的限制保证每个点的入度不大 ...
- python菜鸟教程学习4:基本数据类型
变量:python中的变量不需要声明,但在使用前都必须要赋值,变量赋值之后才会被创建 在python中变量是没有类型的,所有的数据类型是对内存中对象的类型. 赋值:使用等号=来给变量赋值 python ...
- mysql之sql语句逻辑执行顺序
1. (1)from先执行,from执行后就会将所有表(多个表时和单表所有的表)数据加载到内存中了 (2)ON执行,得到连接表用的连接条件. (3)JOIN执行,根据ON的连接条件,将from加载的所 ...
- redis源码学习之slowlog
目录 背景 环境说明 redis执行命令流程 记录slowlog源码分析 制造一条slowlog slowlog分析 1.slowlog如何开启 2.slowlog数量限制 3.slowlog中的耗时 ...
- 攻防世界-PHP文件包含
<?php show_source(__FILE__); echo $_GET['hello']; $page=$_GET['page']; while (strstr($page, " ...
- Hash算法——加密解密说明
MD5 pmd5-md5加密解密 加密类型识别工具 hash-identifier
- Spring-Boot项目中配置redis注解缓存
Spring-Boot项目中配置redis注解缓存 在pom中添加redis缓存支持依赖 <dependency> <groupId>org.springframework.b ...