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的字符串操作一些简单的思考的更多相关文章

  1. JAVA作业—字符串操作

    ------------恢复内容开始------------ ------------恢复内容开始------------ ------------恢复内容开始------------ ------- ...

  2. Java的字符串操作

    目录 Java的字符串操作 一.不同字符串操作的对比 1.1 C++中const修饰指针 const在星号的左边,是被指向的常量不可变 const在星号的右边,是指针的指向不可变 二. Java字符串 ...

  3. java类库字符串操作

    在java类库中,java给我们提供了字符串几个特别的操作,分别是String,Stringbuffer,Stringbuilder等.下面就对这几个类做一个简单的介绍.首先,我们先了解一下Strin ...

  4. Java Script 字符串操作

    JS中常用几种字符串操作: big() small() bold() fontcolor() fontsize() italics() strike() link() charAt() charCod ...

  5. Java String 字符串操作小结

    // 转载加编辑 -- 21 Apr 2014 1. Java字符串中子串的查找 Java中字符串中子串的查找共有四种方法,如下: 1.int indexOf(String str) :返回第一次出现 ...

  6. 使用javap分析Java的字符串操作

    我们看这样一行简单的字符串赋值操作的Java代码. String a = "i042416"; 使用命令行将包含了这行代码的Java类反编译查看其字节码: javap -v con ...

  7. 四:Java之字符串操作String、StringBuffer和StringBuilder

    string是我们经经常使用到的一个类型,事实上有时候认为敲代码就是在重复的操作字符串,这是C的特点,在java中.jdk非常好的封装了关于字符串的操作.三个类String .StringBuffer ...

  8. Java基础(一)-- Java对字符串操作大全

    一.Java字符串类基本概念 在JAVA语言中,字符串数据实际上由String类所实现的.Java字符串类分为两类:一类是在程序中不会被改变长度的不变字符串:二类是在程序中会被改变长度的可变字符串.J ...

  9. android TextView字体设置最少占多少行. 及其 Java String 字符串操作 . .

    ①  字体设置: 修改代码 :  GridViewActivity.java priceTv为 TextView priceTv.setMaxLines(3); //当多与7个字fu的时候 , 其余字 ...

随机推荐

  1. 包装类和基本数据类型(以int和Integer为例)

    Java的包装类就是可以直接将简单类型的变量表示为一个类.java共有六个包装类:Integer,Long,Float,Double,Character,Boolean,对应六种基本数据类型. 包装类 ...

  2. Linux配置邮件发送信息

    背景 一般情况下,我们的IT系统都会有相关的告警的处理,有的是邮件,有的是短信,这些都能很方便的获得一些有用的信息 在某些时候我们没有这样的系统,而自己又需要定期的获取一些信息的时候,配置一个邮件发送 ...

  3. 转载 Web前端开发 HTML设计 经验与技巧总结

    文章目录1.限制input 输入框只能输入纯数字.限制长度.默认显示文字2.input输入框自动获取焦点3.用CSS让背景有透明度文字不变4.a标签禁止点击5.文字两种居中对齐6.设置一个元素一直在页 ...

  4. 玩转 Comparator 和 Comparable 两接口

    最近项目中有排序的需求,就查看了一下Java文档,发现有两个接口都可以进行排序,Comparable 和 Comparator 两接口到底有啥区别?何时用?怎么用?使用场景我都在底下一一研究分享出来: ...

  5. FL Studio进行侧链的三种方式(上)

    在本系列教程中,我们将学习如何在FL Studio中进行侧链.侧链是一种信号处理技术,通过它我们可以使用一个信号波形的振幅(音量)来控制另一个信号的某些参数.在电子音乐中,例如trance,house ...

  6. Vegas视频的音频叠加效果怎么实现,可以用其他软件吗

    有时我们会用Vegas为某段影片配音,我们要怎么把配音和背景声融合在一起呢?想必马上会有人反应过来:让配音和背景声分别置于两条轨道上就好了.这当然是一个相当好的方式. 可是,如果我想要把两段音频合成一 ...

  7. python升级版本

    前言 目前大部分使用的3.6或者3.7以及更低版本存在不少问题,随着python的更新很多问题得到修复并且具有更多新的功能. 更新 3.y.x版本升级到3.y.z 下载需要升级的exe安装包点击upg ...

  8. Robot Framework安装和入门

    1:安装 python 安装python并且配置好环境变量 2:安装 Robot Framework pip install robotframework 3:安装GUI界面 pip install ...

  9. C语言讲义——链表完整代码

    #include <stdio.h> #include <stdlib.h> #include <string.h> struct Node { int _id; ...

  10. Mybatis【2.3】-- Mybatis一定要使用commit才能成功修改数据么?

    代码直接放在Github仓库[https://github.com/Damaer/Mybatis-Learning],mybatis-02可直接运行,就不占篇幅了. 为什么我们有时候不使用commit ...