Android中字符串拼接那些事

一.概述

字符串是个什么?

字符串是由字符组成的,而字符由字节根据编码规则而来。这里我们的问题停留在字符以及字符串上,不涉及字符和字节的转换,想了解字符和字节转换以及编码规则请看字符编码(ASCII,Unicode和UTF-8,GBK)

问题

平时开发中涉及到字符串拼接到底什么情况使用什么方法最合适呢?以及为什么选择这种方法呢?(或者说一下String,StringBuilder,StringBuffer异同等)相信大家面试的时候都遇到过。

结论

下面先给出结论,再将结论的得出过程仔细分析一下。(jdk版本为1.8.0-51)
先给出拼接方式如下:
方法一: 使用“+”;
方法二: String.concat方法
方法三: StringBuilder.append方法
方法四: StringBuffer.append方法
下面是选择原则:

  1. 简单拼接使用方法一“+”
  2. 百分百确定不涉及多线程,大量拼接使用方法三StringBuilder.append
  3. 不确定会涉及多线程,大量拼接使用方法四StringBuffer.append

下面看看如何得出的这些结论。

二.拼接实际过程

2.1先看各个类的定义

String:是一个常量,一旦赋值就不可以修改了。(内部实现为字符数组,其为final类型)
StringBuilder:字符串变量(非线程安全)。(内部实现也是字符数组)
StringBuffer:字符串变量(Synchronized,即线程安全)。(内部实现也是字符数组)

2.2各种方法拼接的实质

2.2.1 方法一: 使用“+”

String既然是final的,那么哪里来的拼接呢?其实,所有的所谓字符串拼接,都是重新生成了一个新的字符串。如下:

String s2 = "Hello";
String s3 = "Word";
String s4 = s2 + s3;

如果我们反编译会发现其内部在编译的时候其实是使用了StringBuilder。上面的拼接过程是:

String s4 = new StringBuilder().append(s2).append(s3).toString();

但是注意

如果如下

String s1 = "Hello" + "Word";

其内部的实现和上面的原理就不同了,上面的这种拼接方法就相当于:

String s1 = "HelloWord";

因为在编译时候Hello和Word两个字符串被保存入常量池中,编译的时候就已经将两个字符串拼接为一个。关于类加载和编译相关的知识这里不展开说明。

2.2.2 方法二: String.concat方法

下面是源码:

	@FastNative
    public native String concat(String str);

在jdk版本为1.8.0-51中已经成为了native方法,从注解@FastNative来看它是将拼接过程放入了native层加快了拼接速度。

2.2.3 方法三: StringBuilder.append方法

实例代码如下,将两个字符串进行拼接

StringBuilder sb = new StringBuilder();
String s  = sb.append("Hello").append("Word").toString();

StringBuilder类的基类是AbstractStringBuilder,并且append也是使用的AbstractStringBuilder中的,下面我们来看AbstractStringBuilder。其中重要的几点:

  1. 存储实现是字符数组
  2. 字符数组中不一定所有位置都被占用了
  3. “数组扩容”,构造新String

下面是AbstractStringBuilder源码,只取相关部分

-------省略代码-------
/**
    * 字符数组作为存储容器
    */
   char[] value;

   /**
    * 数组中被使用的数量
    */
   int count;
-------省略代码-------
//拼接
public AbstractStringBuilder append(String str) {
       if (str == null)
           return appendNull();
       int len = str.length();
       ensureCapacityInternal(count + len);
       str.getChars(0, len, value, count);
       count += len;
       return this;
   }

//确认是否要扩容
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

//扩容操作
private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

StringBuilder源码,只取相关部分

@Override
public StringBuilder append(String str) {
     super.append(str);
     return this;
 }
@Override
public String toString() {
    if (count == 0) {
        return "";
    }
    return StringFactory.newStringFromChars(0, count, value);
}

2.2.4 方法四: StringBuffer.append方法

StringBuffer也是继承的AbstractStringBuilder,并且相应的append,ensureCapacityInternal,newCapacity也是来自于其基类。不同的是StringBuffer中append方法是synchronized,于是保证了线程安全。

@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

三 效率问题

这里通过拼接代码来验证,如下
方法一: 使用“+”;

	private void concatStr1() {
        long t1 = System.currentTimeMillis();
        String s = "hello";
        for(int i=0;i<60000;i++){
            String str = String.valueOf(i);
            s+=str;
        }
        long t2 = System.currentTimeMillis();
        long dur = t2-t1;
        Log.e("cost_time=",dur+"");
    }

方法二: String.concat方法

	private void concatStr2() {
        long t1 = System.currentTimeMillis();
        String s = "hello";
        for(int i=0;i<60000;i++){
            String str = String.valueOf(i);
            s.concat(str);
        }
        long t2 = System.currentTimeMillis();
        long dur = t2-t1;
        Log.e("cost_time=",dur+"");
    }

方法三: StringBuilder.append方法

	private void concatStr3() {
        long t1 = System.currentTimeMillis();
        String s = "hello";
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<60000;i++){
            sb.append(i);
        }
        long t2 = System.currentTimeMillis();
        long dur = t2-t1;
        Log.e("cost_time=",dur+"");
    }

方法四: StringBuffer.append方法

	private void concatStr4() {
        long t1 = System.currentTimeMillis();
        String s = "hello";
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<60000;i++){
            sb.append(i);
        }
        long t2 = System.currentTimeMillis();
        long dur = t2-t1;
        Log.e("cost_time=",dur+"");
    }

下面是依次的耗时时间

06-14 15:42:13.554 25934-26300/com.gong.io.demo E/cost_time=: 79443
06-14 16:04:48.623 31326-31505/com.gong.io.demo E/cost_time=: 37
06-14 15:47:45.329 27786-27911/com.gong.io.demo E/cost_time=: 5
06-14 16:06:26.622 31965-32103/com.gong.io.demo E/cost_time=: 6

从中我们了解到耗时排名如下:

StringBuilder < StringBuffer < concat < + 

得出结论:

  1. 大量拼接操作时选用StringBuffer(耗时和StringBuilder几乎相同,且你不可能保证不会用在多线程的情况下,针对Android开发)。若后台开发并且这里明确可能成为速度瓶颈,那另当别论(百分百确定单线程就用StringBuilder);
  2. concat方法现改为native方法了,效率有了非常大的提升(相对之前使用字符数组);
  3. 方法一“+”:不光速度很低,由于内部创建大量StringBuilder实例(参看2.2.1),造成内存资源浪费。
发布了82 篇原创文章 · 获赞 31 · 访问量 5万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览