zoukankan      html  css  js  c++  java
  • Java字符串String

    Java字符串String

    我们知道Java的字符窜是Immutable(不可变)的,一旦创建就不能更改其内容了;平常我们对字符串的操作是最多的,其实对字符串的操作,返回的字符串都是新建的字符串对象,原来并没有被改动,这跟C#是一模一样的;

    既然字符串是不可变量,当我们对字符串进行各种操作时的效率肯定是有影响的,比如我们平时最常用的 + 运算符:

    public class ConcatString{
      	public static void main(String[] args) {
            var name = "marson";
            var s = "abc" + name + "shine" + 47+ "nancy" + "summer zhu";
            print(s);
        }
    }
    

    这段代码我相信在我们日常开发中很容易遇见,它这里还没开始相加,就开辟了6段字符串对象,然后+起来又形成新的String对象,所以可以想象,当我们遇到大量(长度未可知且预知高于一定值的)字符串拼接时,会产生多少新的对象,对内存,性能造成不小的影响。

    所以这时候就有了StringBuilder

    StringBuilder

    StringBuilder的目的就是为了解决String的不变量的问题的,StringBuilder在内部维护初始容量为16(可动态扩展)的对象,它是一个变量,所以它append字符串时返回的对象是同一个。所以存在大量的字符串拼接时,StringBuilder是可以明显优于String;

    在JAVA SE5前,StringBuffer充当StringBuilder的角色,但是StringBuffer是线程安全的,细扣源码就会发现,里面含有大量的关键字synchronized,所以性能开销也比较大。

    下面是StringBuilder的demo

    public void UsingStringBuilder(){
      	var sb = new StringBuilder();
      	sb.append("abc").append("marson").append("shine")
      		.append("summer").append("zhu");
      	System.out.println(sb);
    }
    

    StringBuilder隐藏的陷阱

    下面我们来学习《Java编程思想》一书中提到的一种StringBuilder场景,贴下代码:

    public class InfiniteRecursion {
        @Override
        public String toString() {
            return "InfiniteRecursion address: " + this + "
    ";
        }
    
        public static void main(String[] args) {
            var v = new ArrayList<InfiniteRecursion>();
            for (int i = 0; i < 10; i++) {
                v.add(new InfiniteRecursion());
            }
            System.out.println(v);
        }
    }
    

    这种情况稍微不注意,就会犯下上面这段代码一样的错误——StackOverflowError

    这是由于无限递归导致的堆栈内存溢出的错误,因为InfinitialRecursion类复写了toString,并且返回一个字符串+拼接操作符。尽管拼接的对象是this对象,但是由于是字符串的拼接,所以jvm会自动转型为String类型,从而再次调用toString,最后导致错误出现。

    关于字符串池——intern

    Java关于字符串对象,其实有一个装载字符串的容器——字符串池(pool of strings),新建的String对象,只要池中不存在,那么就可以存进去,并生成唯一个引用,当我们新建一个内容一样的字符串内容,我们可以直接引用池中的字符串对象,进而减小新建字符串带来的开销提高应用程序性能,而String的实例方法intern就是这个作用:

    public class StringIntern {
        public static void main(String[] args) {
            var s = "MarsonShine";
            var ss = new String("MarsonShine");
            var sss = ss.intern();
            System.out.println("s == ss: " + (s == ss));// false
            System.out.println("s == sss: "+(s == sss));//	true
            System.out.println("ss == sss: "+(ss == sss));// false
        }
    }
    

    String VS StringBuilder

    最后我们来比较一下String与StringBuilder拼接字符串的性能对比来结束我们这个话题

    public class StringVsStringBuilder {
        private static final String INIT_STRING = "abcdefghijklmn1234567890";
    
        public static void main(String[] args) {
            var sw = new Stopwatch();
            sw.start();
            var str = "";
            for (int i = 0; i < 100000; i++) {
                str += INIT_STRING;
            }
            sw.end();
            System.out.println("String + 运行时间:" + sw.ElapsedMilliseconds() + " ms");
    
            sw.restart();
            var sb = new StringBuilder();
            for (int i = 0; i < 100000; i++) {
                sb.append(INIT_STRING);
            }
            sw.end();
            System.out.println("StringBuilder append 运行时间:" + sw.ElapsedMilliseconds() + " ms");
        }
    }
    

    这个类里面分别用String,StringBuilder对定长的字符串对象INIT_STRING多次拼接

    测试结果肯定也如大家所料,后者时间要远远小于前者的。但是当拼接的字符串比较少时,其差别就微乎其微了,理论上在少量字符串的拼接过程中,StringBuilder的性能是要逊色于String的,但是在我电脑上经过大量的测试,发现StringBuilder的性能始终要强与String的,我都有些怀疑是不是我Stopwatch辅助类写错了 - -;

    最后我来把这个段代码附上吧

    package performance;
    
    public class Stopwatch {
        private long startTime;
        private long endTime;
        public void start(){
            startTime = System.currentTimeMillis();
        }
        public void end(){
            endTime = System.currentTimeMillis();
        }
        public void restart(){
            startTime = System.currentTimeMillis();
        }
        public long ElapsedMilliseconds(){
            return endTime - startTime;
        }
    }
    

    后记

    因为我想弄清楚Java中的+操作符实际上是怎么调用的,运行的过程是怎么样的,是不是跟C#一样调用的是concat方法?

    后来我通过反编译java代码发现string的+操作符在JVM变成了动态指令调用:

    invokedynamic 指令去调用java.lang.invoke.makeConcatWithConstants方法,然后根据MethodHandler以及MethodType信息生成CallSite信息去执行具体的函数,但就CallSite调用那个过程我没搞清楚,调试断点也摸不清楚(Idiea玩不转 - -)

    有了解的同学希望告诉我下_

  • 相关阅读:
    622 CircularQueue C#
    x盒子
    Cygwin、MinG、MSys区别与联系(转)
    Spring集成MyBatis完整示例
    mybatis学习 (五) POJO的映射文件
    mybatis学习(四)——config全局配置文件解析
    json字段为null时输出空字符串
    mybatis学习(一)不使用 XML 构建 SqlSessionFactory
    数据库 ----jdbc连接池的弊端
    Spring @Import注解 —— 导入资源
  • 原文地址:https://www.cnblogs.com/ms27946/p/String-In-Java.html
Copyright © 2011-2022 走看看