Java中的字符串

作者:禅楼望月(http://www.cnblogs.com/yaoyinglong/

1.字符串可以被GC回收了

我们之前在表达式的陷阱中就说到“对于Java程序中的字符直接量,JVM会使用一个字符串池来保护他们:当第一次使用某个字符串直接时,JVM会将它们放入字符串池进行缓存。”在jdk1.7之前HotSpot将该字符串常量池放在永久代中,所以当初我们还说“在一般情况下,字符串缓冲池中字符串对象不会被垃圾回收”,但是jdk1.7以后HotSpot就将字符串常量池从永久代中移出。因此我们看到如下程序,并不会导致内存溢出:

public class StringTest {
    public static void main(String[] args){
        List<String> list=new ArrayList<String>();
        int i=0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

这段代码在jdk1.7中会一直循环下去,但是在jdk1.6中会报“OutOfMemoryError:PermGen space”错误。这样java字符串常量池就回归到了堆内存中,接受垃圾回收器的垃圾回收。

2.String是不可变的

创建好一个String之后,它就不会再被改变了,查看JDK文档会发现,String类中看似修改了String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容,而最初的String对象纹丝未动。

3.String重载了“+”运算符

Java不允许Java程序员重载运算符。

public class StringTest {
    public static void main(String[] args){
        String mango="mango";
        String s="abc"+mango+"def"+47;
        System.out.println(s);
    }
}

通过javap来反编译以上代码:

E:\program\Thinking_in_Java\bin\string>javap -c StringTest.class
Compiled from "StringTest.java"
public class string.StringTest {
  public string.StringTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #16                 // String mango
       2: astore_1
       3: new           #18                 // class java/lang/StringBuilder
       6: dup
       7: ldc           #20                 // String abc
       9: invokespecial #22                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      12: aload_1
      13: invokevirtual #25                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      16: ldc           #29                 // String def
      18: invokevirtual #25                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: bipush        47
      23: invokevirtual #31                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      26: invokevirtual #34                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      29: astore_2
      30: getstatic     #38                 // Field java/lang/System.out:Ljava/io/PrintStream;
      33: aload_2
      34: invokevirtual #44                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      37: return
}

从上面的的反编译代码我们可以清楚的看到,编译器默认使用StringBuilder对我们的字符串做了优化。这样就避免了直接使用String和“+”运算符拼接所产生的中间垃圾。既然,JDK已经为我们做了字符串的优化,那我们是不是肆无忌怛的使用“+”了呢?那么我们再来看看编译器到底为我们优化到什么程度:

public class StringTest {
    public String implicit(String[] fields){
        String result="";
        for(int i=0; i<fields.length; i++){
            result+=fields[i];
        }
        return result;
    }
    public String explicit(String[] fields){
        StringBuilder result=new StringBuilder();
        for(int i=0; i<fields.length; i++){
            result.append(fields[i]);
        }
        return result.toString();
    }
    public static void main(String[] args){
    }
}

首先我们查看反编译后的第一个函数:

public java.lang.String implicit(java.lang.String[]);
  Code:
     0: ldc           #16                 // String
     2: astore_2
     3: iconst_0
     4: istore_3
     5: goto          32
     8: new           #18                 // class java/lang/StringBuilder
    11: dup
    12: aload_2
    13: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
    16: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
    19: aload_1
    20: iload_3
    21: aaload
    22: invokevirtual #29                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    25: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    28: astore_2
    29: iinc          3, 1
    32: iload_3
    33: aload_1
    34: arraylength
    35: if_icmplt     8
    38: aload_2
    39: areturn

从上面反编译的代码可以看出,从第4到第32行是for循环体,同时我们也看到,StringBuilder是在for循环中创建的,即每循环一次就创建一新的StringBuilder。再看看第二个函数:

public java.lang.String explicit(java.lang.String[]);
  Code:
     0: new           #18                 // class java/lang/StringBuilder
     3: dup
     4: invokespecial #45                 // Method java/lang/StringBuilder."<init>":()V
     7: astore_2
     8: iconst_0
     9: istore_3
    10: goto          24
    13: aload_2
    14: aload_1
    15: iload_3
    16: aaload
    17: invokevirtual #29                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    20: pop
    21: iinc          3, 1
    24: iload_3
    25: aload_1
    26: arraylength
    27: if_icmplt     13
    30: aload_2
    31: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    34: areturn

不仅代码缩短了,而且整个方法中只在for循环的外面生成了一个StringBuilder对象。

总结:循环中处理字符串最好自己创建一个StringBuilder对象,并用它来构造最终的结果,循环之外则可以信赖编译器对String的优化。

4.一个陷阱:

public class StringTest {
    public static void main(String[] args){
        String  str1="abc";
        StringBuilder sb=new StringBuilder();
        sb.append("is true? ");
        sb.append(str1+str1.length());
        System.out.println(sb.toString());
    }
}

这段代码,编译器会怎么处理呢?

public static void main(java.lang.String[]);
  Code:
     0: ldc           #16                 // String abc
     2: astore_1
     3: new           #18                 // class java/lang/StringBuilder
     6: dup
     7: invokespecial #20                 // Method java/lang/StringBuilder."<init>":()V
    10: astore_2
    11: aload_2
    12: ldc           #21                 // String is true?
    14: invokevirtual #23                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    17: pop
    18: aload_2
    19: new           #18                 // class java/lang/StringBuilder
    22: dup
    23: aload_1
    24: invokestatic  #27                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
    27: invokespecial #33                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
    30: aload_1
    31: invokevirtual #36                 // Method java/lang/String.length:()I
    34: invokevirtual #40                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    37: invokevirtual #43                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    40: invokevirtual #23                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    43: pop
    44: getstatic     #47                 // Field java/lang/System.out:Ljava/io/PrintStream;
    47: aload_2
    48: invokevirtual #43                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    51: invokevirtual #53                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    54: return

由上面代码我们可以看出,在sb.append(str1+str1.length()); 这句代码中,编译器还是采取了创建一个StringBuilder在拼接括号里面的字符串,然后将这个StringBuilder的toString传递给外围的StringBuilder。

结论:禁止在StringBuilder的append中使用“+”。如果StringBuilder处于循环中就更糟糕了。

时间: 2024-10-10 18:15:55

Java中的字符串的相关文章

Java中的字符串常量池

最近做到一个题目: 问题:String str = new String("abc"),"abc"在内存中是怎么分配的?    答案是:堆,字符串常量区. 题目考查的为Java中的字符串常量池和JVM运行时数据区的相关概念."abc"为字面量对象,其存储在堆内存中.而字符串常量池则存储的是字符串对象的一个引用. Java中的字符串常量池 Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid&qu

protobuf在java中的字符串化

最近由于项目需要,大致研究了一下protobuf的java使用.说实话,习惯了C++的protobuf,java用起来真别扭. 由于需要将protobuf序列化后,存入redis,而且redis没法直接存储非字符串的数据,所以我只能想办法将protobuf序列化成字符串. protobuf的java实现里,并没有直接序列化成String类型变量的方法,但是提供了toByteArray()方法,可以序列化成byte[]. 于是乎很容易想到可以这么做: byte[] raw_bytes = prot

Android学习笔记----Java中的字符串比较

用习惯了C#.C++,在做字符串比较时想当然地使用如下语句: 1 string str1 = "abcd", str2 = "abcd"; 2 if(str1==str2) 3 { 4 return true; 5 } 6 else 7 { 8 return false; 9 } 殊不知在Java中,两个String类型的变量,尽管字符相同,使用”==“进行比较,也会返回false. Java中进行字符串比较需采用String类型的equals方法: 1 Strin

JAVA中创建字符串的两种方式的区别

我们知道,通常在Java中创建一个字符串会有两种方式,通过双引号直接赋值和通过构造器来创建. String x = "abcd"; String y = new String("abcd"); 然而,这两种方式之间的区别是什么?分别应用于哪些情况,之前还不是很懂. 1.双引号的方式 String x = "abcd"; String y = "abcd"; System.out.println(x==y);//true Sys

Java中的字符串比较,按照使用习惯进行比较

java中的字符串比较一般可以采用compareTo函数,如果a.compareTo(b)返回的是小于0的数,那么说明a的unicode编码值小于b的unicode编码值. 但是很多情况下,我们开发一款app需要结合“国情”,比如在电话本中,我们希望“李四”排在“zhangsan”的前面,但是如果采用普通的compareTo函数的字符串比较的方式,那么“zhangsan”小于“李四”,由此造成了“zhangsan”的排序先于“李四”. 解决方式是采用java提供的 Collator类. 一.原理

java中判断字符串是否为数字的方法的几种方法

Java中判断字符串是否为数字的方法: 1.用JAVA自带的函数 public static boolean isNumeric(String str){ for (int i = 0; i < str.length(); i++){ System.out.println(str.charAt(i)); if (!Character.isDigit(str.charAt(i))){ return false; } } return true; } 2.用正则表达式 首先要import java.

java中String字符串的替换函数:replace与replaceAll的区别

例如有如下x的字符串 String x = "[kllkklk\\kk\\kllkk]";要将里面的“kk”替换为++,可以使用两种方法得到相同的结果 replace(CharSequence target, CharSequence replacement)       ——          x.replace("kk", "++") replaceAll(String regex, String replacement)       —— 

为什么Java中的字符串是不可变的?

原文链接:https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/ java字符串是不可变的.不可变类只是一个不能修改实例的类.实例创建时所有的信息都被初始化,并且信息不能被修改.不可变类有许多优点.本文总结了字符串为什么被设计成不可变的原因.这说明在记忆的角度不变性的概念,同步和数据结构. 1.字符串池的要求: 字符串池(字符串特定池)是方法区域中的一个特殊存储区域.当创建字符串时,如果字符串已经存在于池中,则将

转载:Java中的字符串常量池详细介绍

引用自:http://blog.csdn.net/langhong8/article/details/50938041 这篇文章主要介绍了Java中的字符串常量池详细介绍,JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池,需要的朋友可以参考下 Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new Stri

2.1号Java复习题目——Java中的字符串(基础知识整理)

Java中的字符串基础知识 作为程序开发当中,使用最频繁的类型之一,字符串有着与基础类型相同的地位,甚至在 JVM(Java 虚拟机)编译的时候会对字符串做特殊的处理,比如拼加操作可能会被 JVM 直接合成为一个最终的字符串,从而到达高效运行的目的. 1 String 特性 String 是标准的不可变类(immutable),对它的任何改动,其实就是创建了一个新对象,再把引用指向该对象: String 对象赋值之后就会在常量池中缓存,如果下次创建会判定常量池是否已经有缓存对象,如果有的话直接返