zoukankan      html  css  js  c++  java
  • Java基础-关键字-String

    1、String的本质

    线程安全  

      打开String的源码,类注释中有这么一段话“Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。这句话总结归纳了String的一个最重要的特点:String是值不可变(immutable)的常量,是线程安全的(can be shared)。

    不可继承

      String类使用了final修饰符,表明了String类的第二个特点:String类是不可继承的。

      在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用以提升执行效率。而从Java SE5/6开始,就渐渐摈弃这种方式了。因此在现在的Java SE版本中,不需要考虑用final去提升方法调用效率。只有在确定不想让该方法被覆盖时,才将方法设置为final。

    值不可变,存储方式为字符数组

    private final char value[];
    private final int count; 

      从final的修饰可以看出其不可变性

      其中的String类的 concat方法为起扩容,如果方法的参数长度等于0这返回this,否则并组成一个新的string,并紧随其后拼接参数后返回新的string

      我们看String类的concat方法。实现该方法第一步要做的肯定是扩大成员变量value的容量,扩容的方法重新定义一个大容量的字符数组buf。第二步就是把原来value中的字符copy到buf中来,再把需要concat的字符串值也copy到buf中来,这样子,buf中就包含了concat之后的字符串值。下面就是问题的关键了,如果value不是final的,直接让value指向buf,然后返回this,则大功告成,没有必要返回一个新的String对象。但是。。。可惜。。。由于value是final型的,所以无法指向新定义的大容量数组buf,那怎么办呢?“return new String(0, count + otherLen, buf);”,这是String类concat实现方法的最后一条语句,重新new一个String对象返回。这下真相大白了吧!

      String类其实是通过char数组来保存字符串的。

    总结:String实质是字符数组,两个特点:1、该类不可被继承;2、不可变性(immutable)

     

    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > count) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        if (beginIndex > endIndex) {
            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
        }
        return ((beginIndex == 0) && (endIndex == count)) ? this :
            new String(offset + beginIndex, endIndex - beginIndex, value);
        }
     
     public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        char buf[] = new char[count + otherLen];
        getChars(0, count, buf, 0);
        str.getChars(0, otherLen, buf, count);
        return new String(0, count + otherLen, buf);
        }
     
     public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = count;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */
            int off = offset;   /* avoid getfield opcode */
     
            while (++i < len) {
            if (val[off + i] == oldChar) {
                break;
            }
            }
            if (i < len) {
            char buf[] = new char[len];
            for (int j = 0 ; j < i ; j++) {
                buf[j] = val[off+j];
            }
            while (i < len) {
                char c = val[off + i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(0, len, buf);
            }
        }
        return this;

    从上面的三个方法可以看出,无论是sub操、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

    “对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

     2、String的内存机制

    JVM运行时,会将内存分为两个部分:堆和栈。堆中存放的是创建的对象,而栈中存放的方法调用过程的局部变量或引用。而设计Java字符串对象内存实现的时候,在堆中又开辟了一块很小的内存,称之为字符串常量池,专门用来存放特定的字符串对象。

    创建Java字符串对象有两种常用的方式:

    String 引用变量名="字符串内容";
    String 应用变量名=new String(<参数序列>);

    我们先来看看创建字符串对象的第一种方式内存如何分配的,代码如下:

    String s1="osEye.net";
    String s2="osEye.net";

     如上图描述了引用对象的关系,以及内存的分配。Java实现的步骤如下:

    1. 查看字符串常量池中是否存在内容与“osEye.net”相同的字符串对象。
    2. 若没有,则新创建一个包含该内容的字符串对象,并让引用变量指向该对象。例如,创建字符串s1的时候,字符串常量池中没有,则创建一个新对象,并让引用s1指向该对象。
    3. 若已经存在包含该内容的字符串对象,则让字符串引用直接指向该对象。例如,在创建字符串s2的时候,字符串常量池中已经有包含该内容的对象了,所以引用s2直接指向已有的对象。

    在来看看第二种创建字符串对象的方式:

    String s1="osEye.net";
    String s2=new String("osEye.net");

    如上图描述了引用对象的关系,以及内存的分配。Java实现的步骤如下:

    1. 首先在堆(不是常量池)中创建一个包含指定内容的字符串对象,并将字符串引用指向该对象。例如上述代码中,使用new创建字符串s3,其会直接在堆中创建一个内容为“osEye.net”的字符串对对象,并将引用s3指向该对象。
    2. 去字符串常量池中查看,是否有包含该内容的对象。
    3. 若有,则将new出来的字符串对象与字符串常量池中内容相同的对象联系起来。例如,本例中s3所指向的对象与s1所指向的联系起来。
    4. 若没有,则在字符串常量池再创建一个包含该内容的字符串对象,并将堆中的对象与字符串常量池中新创建出来的对象联系起来。

     我们知道,new出来的字符串对象和字符串常量池中的对象是有联系的,可以通过intern方法来查看,方法签名:

    public String intern()

    此方法将指定的字符串引用在字符串常量池中对应的对象,若其指向的对象本身就在字符串常量池中,则直接将自己指向的对象返回;若该字符串引用指向的对象在堆中,则返回字符串常量池中与其联系的对象。实例如下:

    package net.oseye;
    public class ExceptionTest {
     
        public static void main(String[] args) {
            String s1="osEye.net";
            String s2=new String("osEye.net");
            
            if(s1==s2){
                System.out.println("字符串引用s1和字符串引用s2所指向的是同一个对象");
            }else{
                System.out.println("字符串引用s1和字符串引用s2所指向的不是同一个对象");
            }
            if(s1.intern()==s2.intern()){
                System.out.println("字符串引用s1和字符串引用s2在字符串常量池中联系的是同一个对象");
            }else{
                System.out.println("字符串引用s1和字符串引用s2在字符串常量池中联系的不是同一个对象");
            }
        }
    }

    输出结果:

    字符串引用s1和字符串引用s2所指向的不是同一个对象
    字符串引用s1和字符串引用s2在字符串常量池中联系的是同一个对象
  • 相关阅读:
    如何只通过Sandboxed Solution启动一个定时执行的操作
    创建与SharePoint 2010风格一致的下拉菜单 (续) 整合Feature Custom Action框架
    创建与SharePoint 2010风格一致的下拉菜单
    《SharePoint 2010 应用程序开发指南》第二章预览
    SharePoint 2013 App 开发 (1) 什么是SharePoint App?
    使用Jscex增强SharePoint 2010 JavaScript Client Object Model (JSOM)
    搜索范围的管理
    SharePoint 2010 服务应用程序(Service Application)架构(1)
    SharePoint 2010 服务应用程序(Service Application)架构(2)
    SharePoint 2013 App 开发 (2) 建立开发环境
  • 原文地址:https://www.cnblogs.com/hwaggLee/p/4492954.html
Copyright © 2011-2022 走看看