zoukankan      html  css  js  c++  java
  • String的解析

    String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处。

    1.String是不可变类。

    这句话其实大家都很熟悉了,那么具体什么是不可变类呢?一般认为:当对象一旦创建完成后,在正常情况下,对象的状态不会因外界的改变而改变(对象的状态是指对象的属性,包括属性的类型及属性值)。

    首先看一个基本的例子:

    1 String s = "abc";
    2 System.out.println("s:" + s);  // 输出s:abc
    3 s = "def";
    4 System.out.println("s:" + s);  // 输出s:def

    此时,初看上去,输出的结果变了,发现s的值发生了变化,那么这与上面的说法——String类是不可变类是否矛盾呢?答案是否定的,因为s只是指向堆内存中的引用,存储的是对象在堆中的地址,而非对象本身,s本身存储在栈内存中。

    实际上,此时堆内存中依然存在着"abc"和"def"对象。对于"abc"对象本身而言,对象的状态是没有发生任何变化的。

    那么为什么String类具有不可变性呢,显然,既然不可变说明String类中肯定没有提供对外可setters方法。接下来来具体看一下String类的定义。

    下面是String类中主要属性的定义(Java 1.7源码):

    复制代码
    1 public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
    2     
    3     /** The value is used for character storage. */
    4     private final char value[];
    5     
    6     /** Cache the hash code for the string */
    7     private int hash; // Default to 0
    8     
    9 }
    复制代码

    与之前版本的Java String源码相比,String类减少了int offset 和 int count的定义。这样变化的结果主要体现在:

    1.避免之前版本的String对象subString时可能引起的内存泄露问题;

    2.新版本的subString时间复杂度将有O(1)变为O(n);

    具体分析可见文章:

    http://www.importnew.com/7656.html

    http://www.importnew.com/14105.html

    通过上面String类的定义,类名前面用了final class修饰,因此,String类不能被继承。对于其属性定义,可以看出,属性value[]和hash都是被定义成private类型,且由于没有 提供对外的public setters方法,String类属性不可被改变。

    其中,需要重点关注属性value[],其被final char修饰,因此字符型数组value只会被赋值一次就不可修改。其存储内容正好是String中的单个字符内容。

     

    2.String相关的 +

    String中的 + 常用于字符串的连接。此处为了说明清楚这个问题,首先可以安装Eclipse 查看字节码插件ByteCode Outline。

    在线安装网址: http://zipeditor.sourceforge.net/update/ Disabled。Help >> install new software >> 输入网址 >> 选择 bytecode outline >> ... ... 安装成功。

    Window >> show view >> other >> java >> ByteCode即可在Eclipse下方面板栏中查看。

    看下面一个简单的例子:

    复制代码
     1 class D {
     2 
     3     public static void main(String[] args) {
     4 
     5         String a = "aa";
     6         String b = "bb";
     7         String c = "xx" + "yy " + a + "zz" + "mm" + b;
     8         System.out.println(c);
     9     }
    10 }
    复制代码

    编译运行后,点击ByteCode查看,主要字节码部分如下:

    复制代码
     1 public static main([Ljava/lang/String;)V
     2    L0
     3     LINENUMBER 5 L0
     4     LDC "aa"
     5     ASTORE 1
     6    L1
     7     LINENUMBER 6 L1
     8     LDC "bb"
     9     ASTORE 2
    10    L2
    11     LINENUMBER 7 L2
    12     NEW java/lang/StringBuilder
    13     DUP
    14     LDC "xxyy "
    15     INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
    16     ALOAD 1
    17     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    18     LDC "zz"
    19     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    20     LDC "mm"
    21     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    22     ALOAD 2
    23     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    24     INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    25     ASTORE 3
    26    L3
    27     LINENUMBER 8 L3
    28     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    29     ALOAD 3
    30     INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    31    L4
    32     LINENUMBER 9 L4
    33     RETURN
    34    L5
    35     LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
    36     LOCALVARIABLE a Ljava/lang/String; L1 L5 1
    37     LOCALVARIABLE b Ljava/lang/String; L2 L5 2
    38     LOCALVARIABLE c Ljava/lang/String; L3 L5 3
    39     MAXSTACK = 3
    40     MAXLOCALS = 4
    41 }
    复制代码

    显然,通过字节码我们可以得出如下几点结论:

    1.String中使用 + 字符串连接符进行字符串连接时,连接操作最开始时如果都是字符串常量,编译后将尽可能多的直接将字符串常量连接起来,形成新的字符串常量参与后续连接(通过反编译工具jd-gui也可以方便的直接看出);

    2.接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字 符串为参数创建StringBuilder对象,然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方 法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。

    也就是说

    String c = "xx" + "yy " + a + "zz" + "mm" + b; 实质上的实现过程是: String c = new StringBuilder("xxyy").append(a).append("zz").append("mm").append(b).toString();

    由于得出结论:当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。

     

     3.String中的常用方法

    1).与String类中value[]数组存储直接相关的有:

    int length(); // 返回String长度,亦即value[]数组长度;

    char charAt(int index); // 返回指定位置字符;

    int indexOf(int ch, int fromIndex); //从fromIndex位置开始,查找ch字符在字符串中首次出现的位置。fromIndex默认为0,ch直接传入字符即可。如'C',区分大小写,未查找到返回-1;

    char[] toCharArray() ;   // 将字符串转换成一个新的字符数组

    2).与其他字符串即字串相关的方法有:

    int indexOf(String str, int fromIndex) ;

    与indexOf含义相反有lastIndexOf(..),反向索引。

    boolean contains(String str); //实际上 contains内部实现也是调用的indexOf,然后将其结果与-1相比较。

    boolean startsWith(String str); // 判断字符串是否以str开头

    boolean endsWith(String str); //.....是否以str结尾

    String replace(CharSequence target, CharSequence replacement) ;  // 替换

    String substring(int beginIndex,  int endIndex);  //字符串截取,不传第二个参数则表示直接截取到字符串末尾

    String[] split(String regex);  // 字符串分割

     

    4.String中的equals()与hashCode()

    String类重写了Object类的equlas方法,使得比较字符串内容是否相等可以直接使用equlas方法。关于equals以及相应的hashCode方法参见博文《Java总结篇系列:java.lang.Object 》

     

    5.String字符串常量池

    JVM为了提高性能和减少内存开销,内部维护了一个字符串常量池,每当创建字符串常量时,JVM首先检查字符串常量池,如果常量池中已经存在,则返回池中的字符串对象引用,否则创建该字符串对象并放入池中。

    因此下述结果返回true。

    1 String a = "abc";
    2 String b = "abc";
    3 System.out.print(a == b); //true

    但与创建字符串常量方式不同的是,当使用new String(String str)方式等创建字符串对象时,不管字符串常量池中是否有与此相同内容的字符串,都会在堆内存中创建新的字符串对象。

    因此,下面代码片段有如下结果。

    1 String a = "Hello";
    2 String b = new String("Hello");
    3 System.out.println(a == b);  //false
    4 System.out.println(a.equals(b)); //true

    即使字符串内容相同,字符串常量池中的字符串与通过new String(..)等方式创建的字符串对象之间没有直接的关系,但是,可以通过字符串的intern()方法找到此种关联。intern()方法返回字符串对象在字符串常量池中的对象引用,若字符串常量池中尚未有此字符串,则创建一新的字符串常量放置于池中。

    于是,很据如上理解,很自然的,可以得到如下结果。

    复制代码
     1 String a = "Hello";
     2 System.out.println(a == a.intern()); //true
     3 
     4 String b = new String("corn");
     5 String c = b.intern();
     6 
     7 System.out.println(b == c); //false
     8 
     9 String d = "corn";
    10 
    11 System.out.println(c == d); //true
    复制代码

     

    6.String/StringBuilder/StringBuffer区别

    String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变),StringBuffer线程安全,StringBuilder非线程安全。

     

    7.既然String是不可变字符串对象,如何才能改变让其可变?

    既然String对象中没有对外提供可用的public setters等方法,因此只能通过Java中的反射机制实现。因此,前文中说到的String是不可变字符串对象只是针对“正常情况下”。而非必然。

    复制代码
     1 public static void stringReflection() throws Exception {
     2 
     3     String s = "Hello World";
     4 
     5     System.out.println("s = " + s); //Hello World
     6 
     7     //获取String类中的value字段
     8     Field valueField = String.class.getDeclaredField("value");
     9 
    10     //改变value属性的访问权限
    11     valueField.setAccessible(true);
    12 
    13     char[] value = (char[]) valueField.get(s);
    14 
    15     //改变value所引用的数组中的第5个字符
    16     value[5] = '_';
    17 
    18     System.out.println("s = " + s); //Hello_World
    19 }
    复制代码

    由此可见Java中反射的强大之处。

  • 相关阅读:
    OLAP ODS项目的总结 平台选型,架构确定
    ORACLE ORA12520
    ORACLE管道函数
    ORACLE RAC JDBC 配置
    ORACLE RAC OCFS连接产生的错误
    ORACLE 启动和关闭详解
    OLAP ODS项目的总结 起步阶段
    ORACLE RAC 配置更改IP
    ORACLE RAC OCR cann't Access
    ORACLE RAC Debug 之路 CRS0184错误与CRS初始化
  • 原文地址:https://www.cnblogs.com/shangxiaofei/p/5316945.html
Copyright © 2011-2022 走看看