您的位置 首页 java

Java中String操作字符串杂记

 public final class String
    implements  java .io. Serializable , Comparable<String>, CharSequence {}  

首先,熟悉一下 String类 如上。里面细节请自行翻阅源码。

主要围绕下一段代码来理解学习String操作字符串:(Java版本:Jdk8)

 public static void testStringConstantPool(){
    String string = new StringBuilder("lin").append("jiao").toString();
    //(1)true
    System.out.println(string.intern().equals(string));
    //(2)true
    System.out.println(string.intern()==string);

    String string2 = new StringBuilder("ja").append("va").toString();
    //(3)true
    System.out.println(string2.intern().equals(string2));
    //(4)false
    System.out.println(string2.intern()==string2);
}  

字符串常量池

诞生时间:编译时

所处区域:堆

储存内容:堆内的字符串对象的引用和字符串常量。

String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

注:自 Jdk 7起原本存放在永久代的字符串常量池被移至Java堆中,而Jdk8也由元空间代替了永久代的存在。

(1)(3)处比较是String对象的内容。

(2)(4)处比较的是String对象的引用。

Jdk7及以后版本,intern()方法实现就不需要再拷贝字符串的实例到永久代,字符串常量池既然已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此在面代码中(1)(2)处intern()返回的引用和由StringBuilder创建的字符串实例是同一个。而对strig2进行的==比较返回false,这是因为”java”这个字符串在执行 StringBuilder ().toString()之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern()方法要求的“首次出现”原则,而”linjiao”是首次出现,因此==结果返回true。

String string = new StringBuilder(“linjiao”)是通过构造函数,在堆上创建一个对象。而”linjiao”首次出现。

String string2 = new StringBuilder(“java”),字符串“java”不是首次出现 ,字符串常量池已经有它。new StringBuilder(“java”)返回的是堆中引用,string2.intern()返回的是 字符串对象实例的引用。

字符串常量池里的内容是在类加载时完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用存到string pool中 (记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的) 。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个 哈希表 ,里面存的是驻留字符串(也就是我们常说的用双引号括起来的字符串内容)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

 //在堆中分配一个StringBuilder类对象空间,并将该对象堆地址压入操作数栈
0: new           #2                  // class java/lang/StringBuilder
//复制操作数栈顶数据并压入操作数栈,该指令使得操作数栈中有两个StringBuilder对象的引用值
*          3: dup
//将常量池中的字符串常量"lin"的驻留字符串引用地地址压入操作数栈
*          4: ldc           #3                  // String lin
//调用StringBuilder的初始化方法,弹出操作数栈栈顶的两个对象地址,用拘留字符串的引用值初
//始化new指令创建的StringBuilder对象,然后将这个对象的引用地址压入操作数栈  
//即:拘留字符串的引用值 = new StringBuilder("");
//string.intern()=拘留字符串的引用值
*          6: invokespecial #4                  // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
*          9: ldc           #5                  // String jiao
*         11: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
*         14: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
// 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是new指令创建出的,
//已经被初始化的StringBuilder对象的地址 (此时的栈顶值弹出存入局部变量中去)。 
*         17: astore_1
*         18: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
*         21: aload_1
*         22: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
*         25: aload_1
*         26: invokevirtual #10                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
*         29: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
*         32: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
*         35: aload_1
*         36: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
*         39: aload_1
*         40: if_acmpne     47
*         43: iconst_1
*         44: goto          48
*         47: iconst_0
*         48: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
*         51: new           #2                  // class java/lang/StringBuilder
*         54: dup
*         55: ldc           #12                 // String ja
*         57: invokespecial #4                  // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
*         60: ldc           #13                 // String va
*         62: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
*         65: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
*         68: astore_2
*         69: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
*         72: aload_2
*         73: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
*         76: aload_2
*         77: invokevirtual #10                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
*         80: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
*         83: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
*         86: aload_2
*         87: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
*         90: aload_2
*         91: if_acmpne     98
*         94: iconst_1
*         95: goto          99
*         98: iconst_0
*         99: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
*        102: return  

通过分析:String a = “a”;与String b = new String(“b”);这两个的区别,扩展理解String。

1、直接定义的String a = “a”,“a”是储存在堆中字符串对象实例空间中,而字符串对象实例的引用返回给a对象;new String(“a”)是通过构造函数初始化创建,存储在堆中,堆引用值返回给对象;

2、常量池中相同的字符串只会有一个,但是new String(),每new一个对象就会在堆中新建一个对象,不管这个值是否相同;

String a = “linjiao” ;String b = “linjiao”; a b都指向字符串常量池中的“a”,所以 a==b 为 true;

String a = new String(“linjiao”) 与String b = new String(“alinjiao);是在堆中创建两个对象new String() “a”是常量池中的”a”,这两个对象的值都为 a,所以a==b 返回false;a.equals(b)返回true;

编译过程会把会把字符串“linjiao”放到在常量池中。用构造器创建的对象,是生成不同的对象。每new一次 JVM 就会在堆中创建一个对象。String a,b只是内容相同罢了。用equals()或者System.out.print(a.intern()==b.intern());就返回true了。(intern() 方法返回字符串对象的规范化表示形式),==比较的是两个对象的引用。

3、String a = “a”在编译阶段就会在内存中创建;

String a = new String(“a”)是在运行时才会在堆中创建对象。

4、String a=”abcdef”;与System.out.print(a==”abcdef”);结果:true

运行出现的字符串常量,若是在常量池中出现过,则JVM会认为同一个对象,以节省内存开销,所以这两个字符串会被认为是同一个对象。

5、String a=”linjiao”;

String b=””;

String c=a+b;

System.out.print(c==”linjiao”);

结果:false

原因:编译时,先将”abcedf”放在常量池中,而c的值则是在运行时在堆里创建的。所以为false。

String的综合运用:编写一个方法,输入一个字符串,统计出每个字符的个数,并输出。

分析:借助Map key-value的原理实现。key不变,value++。

HashMap/TreeMap,这里使用TreeMap。

 public static void statisticsString(String string){
  //使用带有排序功能的TreeMap
    TreeMap<Character,Integer> treeMap = new TreeMap<>();
    //将treeMap中的key全部取出来,然后存储到set中
    //Set可以去重
    Set set = treeMap.keySet();
    //将所需要统计的字符串转换成一个字符数组
    char [] chars = string.toCharArray();
    //通过for循环逐一统计每个字符出现的次数
    for (int i = 0; i < chars.length; i++) {
        if (!set.contains(chars[i])){
            treeMap.put(chars[i],1);
        }else{
            treeMap.put(chars[i],treeMap.get(chars[i])+1);
        }
    }
    printStr2(treeMap);
}  

编写一个方法,输入一个字符串,判断其是否对称,如“abdba”;

 public static boolean test_boolean(String string){
    int len = string.length();
    if(len==0) return false;
    int head = 0;
    int tail = len-1;
    int temp = 0;
    for (head = 0; head < len/2; head++,tail--) {
        //以头尾元素进行比较,出现不等返回false
        if (string.charAt(head)!=string.charAt(tail)){
            return false;
        }
    }
    //没有出现不等返回true
    return true;
}  

对String的操作及字符串常量池的实现原理,理解尚欠缺太多,文章是在基于自身理解层次撰写,如有错误,敬请评论指正,相互交流学习。

谢谢!

未完待续。

文章来源:智云一二三科技

文章标题:Java中String操作字符串杂记

文章地址:https://www.zhihuclub.com/192767.shtml

关于作者: 智云科技

热门文章

网站地图