Java字符串——String深刻

 

转载请注明原文地址:http://www.javashuo.com/article/p-mozkuidp-hm.htmlhtml

 

一:字符串的不可变性

   一、可变 与 不可变 辨析

    Java中的对象按照建立后,对象的内容是否能够被修改,分为 mutable object 和 immutable object。【注意:是对象的内容不可变,而不是指向该对象的引用变量内容不可变。】java

    咱们常见的不可变对象是几个基本数据类型的包装类——Integer、Double、String等。【想一想为何?——Tips:出于节省内存开销,避免重复建立。】c++

    不可变类有5大基本原则:正则表达式

    1)类定义时,添加final修饰符,保证类不被继承【即:不容许在子类中被修改】数组

    2)类定义时,其成员变量一律使用 final private 修饰,保证变量私有的同时不容许修改缓存

    3)不提供能够修改为员变量的方法,包括setter安全

    4)在构造函数中采用deep copy的形式将参数值拷贝给成员变量,而不是直接将参数值赋给成员变量【由于引用类型的参数只是传了一个地址,这样在外部改变该地址的内容会致使不可变对象的成员变量改变】多线程

    5)在成员变量的getter方法中,不能直接返回成员变量自己,而是返回成员变量的copy对象【这也是为了防止引用类型的成员变量被外部获取后,改变引用指向的对象值引发不可变对象的内容变化】app

 

    二、不可变对象的优缺点

    1)字符串常量池的须要——避免每次使用相同的字符串常量都从新建立相同的对象、节省存储空间jvm

    2)线程安全:当一个String对象被多个线程共享时,无需担忧线程安全问题

    3)支持hash和缓存:因为字符串对象是不可变的而且hashcode就缓存在对象中【在下文讲解】了,不须要从新计算,所以很适合做为Map中的键,由于字符串键的哈希处理速度要快过其它的键对象【这就是HashMap中的键每每都使用字符串的缘由】

    4)使用类加载器要用字符串来传递加载的类名,而字符串的不可变性提供了安全性,以保证正确的类被加载。

    5)  缺点:当对String变量有从新赋值、修改等操做时,会不断建立大量的String对象。【当修改后的值未出如今字符串常量池的前提下】————【延伸:所以,在代码中涉及大量字符串操做时,使用StringBuilder或StringBuffer来进行

 

    三、“不可变对象”的很是规手段修改

    对于不可变对象,能够经过反射机制的手段改变其值——获取类的字段定义->改变该字段的可见性和可修改性->修改对象的变量值

    例如:

  //建立字符串"Hello World", 并赋给引用s
    String s = "Hello World"; 
    System.out.println("s = " + s); //Hello World

    //获取String类中的value字段
    Field valueFieldOfString = String.class.getDeclaredField("value");
    //改变value属性的访问权限
    valueFieldOfString.setAccessible(true);

    //获取s对象上的value属性的值
    char[] value = (char[]) valueFieldOfString.get(s);
    //改变value所引用的数组中的第5个字符
    value[5] = '_';
    System.out.println("s = " + s);  //Hello_World


结果为:
s = Hello World
s = Hello_World

 

 

二:replaceFirst、replaceAll、replace 区别

    咱们先来看一下程序:

String s = "my.test.txt";
System.out.println(s.replace(".", "#"));
System.out.println(s.replaceAll(".", "#"));
System.out.println(s.replaceFirst(".", "#"));

    它们的结果会同样吗?——No。

my#test#txt
###########
#y.test.txt

 

    这三个函数中,replaceFirst、replaceAll在替换时使用了正则表达式,所以上面三个函数的参数含义是不一样的

    replace(src,des):将字符串中的src子串[也是一段字符串]替换成des。

    replaceAll(reg,des):将字符串中符合reg模式的内容替换成des。

    replaceFirst(reg,des):将字符串中,第一个匹配reg模式的内容替换成des。

 

    因此上面示例代码中,replace函数是将字符串中的“.”字符换成“#”,而replaceAll则是将全部字符[“.”是正则表达式的通配符]替换成“#”,replaceFirst则是将第一个字符替换成“#”。

 

三:String对“+”运算符的重载

    “在Java中是不支持重载运算符的!”

    java不支持运算符重载,由于java的语法比较繁杂,会致使使用类对象 像基本数据类型那样 用运算符进行操做时,没法作到像c++同样流畅。所以,Java中针对类对象的运算操做通常都是经过方法来定义,而不是运算符重载。

    惟一例外的是String类,它的拼接运算(+) 通过了重载,这个重载是经过jvm编译实现的,具体原理能够 手写一个字符串相+的java类文件并编译,而后经过 javap -c 文件.class 查看具体的过程。

   其原理是:String的+会被转化为StringBuilder的append方法,并生成一个新的String对象返回。

 

四:字符串拼接的5种方式比较

    Java中有如下五种方法处理字符串拼接:

    1. 加号 “+”:适用于小数据量的操做,代码简洁方便。

    2. String contact() 方法:适用于小数据量的操做,代码简洁方便。

    3. StringUtils.join() 方法:适用于将ArrayList转换成字符串的情景,能够省掉用for循环读取ArrayList手动拼接的过程。

    4. StringBuffer append() 方法:继承自AbstractStringBuilder,效率高,大批量的数据处理的好选择,该方法线程安全,因为加了线程锁,速度会比下面第5中慢一点。

    5. StringBuilder append() 方法:继承自AbstractStringBuilder,效率最高,大批量的数据处理的好选择,该方法线程不安全,所以速度最快,若是不涉及多线程操做,优先使用此方法。

 

五:String.valueOf(val) 和 obj.toString 的异同

    同:都返回参数的字符串表示形式。

    异: 对空值的调用结果不一样:

           java.lang.Object类里已有public方法.toString(),因此对任何严格意义上的java对象均可以调用此方法。但在使用时要注意,必须保证object不是null值,不然将抛出NullPointerException异常。

           而valueOf(Object obj)对null值进行了处理,不会报任何异常。但当object为null 时,String.valueOf(object)的值是字符串”null”,而不是null。

 

     

六:switch 对 String 的支持

    Java1.7以前,switch只能局限于int 、short 、byte 、char四类作判断条件,由于在JVM内部实际大部分字节码指令只有int类型的值。

    在使用switch的时候,若是是非int型,会先转为int型,再进行条件判断

    可是在Java1.7中,switch增长了对String做为判断条件的支持,可String并不能直接转为int型,这是怎么作到的呢?

    原理:switch比较的是字符串常量的哈希值(缓存的int类型值,前文提到过),可是hash值可能会有冲突,因此还须要再调用equals方法将 switch(param)的param 与 case str的str 进行二次比较,两者综合之下达到惟一匹配的目的。

 

七:字符串池、常量池(运行时常量池、Class 常量池)、intern

    全局字符串常量池(string pool):全局常量池在每一个JVM中只有一份,存放的是字符串常量值。

                                                string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是咱们常说的用双引号括起来的),
                                                字符串在第一次出现时被建立而且把引用放到stringtable中,在后面再出现时就不会重复建立而是直接从stringtable中找到字符串字面值的地址,返回给字符串引用变量。

  

    Class常量池:class常量池是在编译的时候每一个class都有的,用于编译阶段,存放的是class文件中的字面量(常量)和符号引用

                       字面量就是咱们所说的常量,如文本字符串、八种基本类型的值、被声明为final的常量值等。

                       符号引用是一组符号,用来描述所引用的目标,符号能够是任何形式的字面量,只要使用时能无歧义地定位到目标便可。
                       直接引用是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄。

                                               

    运行时常量池:运行时常量池是在类加载完成以后,将每一个class常量池中的符号引用值转存到运行时常量池中,将符号引用替换成直接引用,与全局常量池中的引用值保持一致

                         运行时常量池更具动态性,在运行期间也能够将新的变量放入常量池中,而不是必定要在编译时肯定的常量才能放入。最主要的运用即是String类的intern()方法

 

    str.intern()函数:把字符串对象str加入常量池中,若是常量池中已有该字符串字面值,则返回stringtable中的引用值。(这样作主要是为了不在堆中不断地建立新的字符串对象)

                           常量池咱们都知道他是存在于方法区的,他是方法区的一部分,而方法区是线程共享的,因此常量池也就是线程共享的,可是他并非线程不安全的,他实际上是线程安全的,由于它让有相同值的引用指向同一个位置。若是引用值变化了,可是常量池中没有新的值,那么就会新开辟一个常量结果来交给新的引用。

 

    详细原理请看:http://www.javashuo.com/article/p-ghvihton-ko.html

 

八:String的New操做建立了几个对象?——两个

咱们以String s1=new String("abc");为例

首先当咱们的类Class在被ClassLoader加载时,"abc"被做为常量读入,在String Pool(字符串常量池)建立了一个"abc"的实例。

而后,调用到new String("abc")的时候,会在Heap里面复制一个相同的对象。

 

(1)类加载对一个类只会进行一次。"abc"在类加载时就已经建立并驻留了(若是该类被加载以前已经有"abc"字符串被驻留过则不须要重复建立用于驻留的"abc"实例)。驻留的字符串引用是放在全局共享的字符串常量池中的。[加载时建立一次]

(2)在这段代码后续被运行的时候,"abc"字面量对应的String实例已经固定了,不会再被重复建立。因此这段代码将常量池中的对象实例复制一份放到heap中,而且把heap中的这个对象的引用交给s1 持有。[运行时复制一次]

  所以,这条语句建立了2个对象。【这两个引用,它们的对象实例是不一样的。】

九:String的hashCode的缓存和懒加载初始化

    String类中定义了一个私有成员变量——hash,它是一个整数,保存String对象的哈希值,也就是说String类型的对象的哈希值不会重复计算,计算过一次后就保存起来了,之后在被hash时直接取该值便可,无需从新计算

 

    这个值在String对象第一次被调用时进行初始化(懒加载,不是在String对象建立时初始化)。

相关文章
相关标签/搜索