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的时候 , 其余字 ...
随机推荐
- Vs编译时RazorTagHelper - DOTNET_HOST_PATH is not set
今天听朋友说遇到一个问题,打开一个aspnetcore2.2的项目工程,发现挺有意思,缺少环境变量DOTNET_HOST_PATH 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 MSB401 ...
- matlab 向量操作作业
写出下列语句的计算结果及作用 clear 清除所有变量 clc 清屏 A = [2 5 7 1 3 4]; 创建行向量并赋值 odds = 1:2:length(A); 冒号操 ...
- ssh 方面问题总结
ssh 远程执行命令: https://www.cnblogs.com/youngerger/p/9104144.html ssh免密登录: https://blog.csdn.net/jeikerx ...
- Linux提权(持续更新)
利用/etc/passwd提权 个人认为,这种提权方式在现实场景中难以实现,条件太过苛刻,但是建立Linux下的隐藏账户是个不错的选择,灵感来自:https://www.hackingarticles ...
- div可以滚动但不显示滚动条
首先有3个div, 第1个,固定大小是200*200(单位为px,下同) 第2个,不固定大小,其大小要用第3个div把个撑开,但是这个div必需要有滚动条, 第3个,固定大小与第1个div保持一致20 ...
- java后端开发三年!你还不了解Spring 依赖注入,凭什么给你涨薪
前言 前两天和一个同学吃饭的时候同学跟我说了一件事,说他公司有个做了两年的人向他提出要涨薪资,他就顺口问了一个问题关于spring依赖注入的,那个要求涨薪的同学居然被问懵了...事后回家想了想这一块确 ...
- Markdown的应知应会
Markdown介绍 什么是Markdown Markdown是一种纯文本.轻量级的标记语言,常用作文本编辑器使用.和记事本.notepad++相比,Markdown可以进行排版:和Word相比,Ma ...
- dsu on tree (树上启发式合并) 详解
一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...
- MathType总结编辑括号的类型(下)
在数学中,所涉及到的公式总是会有各种各样的情况,对于括号这些都是最常见的了.在最开始的四则基本运算中我们学会了使用括号,而随着学习的不断深入,所涉及到的符号与公式都越来越多,对于括号的类型也是使用得非 ...
- 几分钟看懂EasyRecovery数据恢复原理,比我想象的简单易懂得多
可能很多人知道使用数据恢复软件EasyRecovery可以恢复丢失的数据,但是却不知道它是什么原理.现在我们就以硬盘数据恢复为例,一起来了解下EasyRecovery数据恢复原理. 当硬盘数据丢失后, ...