我所理解的String

来源:互联网 发布:matlab在线编程 编辑:程序博客网 时间:2024/05/01 18:22

@:我所理解的String

java的string

java源码中的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

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];/** Cache the hash code for the string */private int hash; // Default to 0

源码及注释解释

  1. 关于注释中的constant和immutable,指明String是不可变字符序列.对于源码实现,使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容。
  2. 关于shared,指明String对象是可共享的即线程安全的.

JVM内存管理简述

概述

Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。方法区是静态分配(static allocation)的,编译器将变量在绑定在某个存储位置上,而且这些绑定不会在运行时改变。Java方法区的一个 重要部分,也是静态分配最典型的例子,是常数池,源代码中的命名常量、String常量和static 变量保存在其中。
栈(Stack) :存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)。
  堆(heap):存放所有new出来的对象。
  常量池(constant pool):在堆中分配出来的一块存储区域,存放储显式的String常量和基本类型常量(float、int等)。另外,可以存储不经常改变的东西(public static final)。常量池中的数据可以共享。
  静态存储:存放静态成员(static定义的)

Java Stack

Java Stack是一个逻辑概念,特点是后进先出,此外没有特别的要求。Java Stack并不代表任何特定的内存区间,也不限制它的实现方式。一 个栈的空间可能是连续的,也可能是不连续的。最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该 方法则对应的方法帧被弹出(pop)。栈分配存在一些局限:Java栈所处理的方法帧和局部变量,都将随着方法帧弹出而结束,显然局部变量无法从一个帧保 持到下一个帧,被调方法的帧不可能比调用方法的寿命更长。因此,从数据保存来看,栈分配适用于由作用域决定生命周期的局部变量。

Java Heap

Java堆(Heap)堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。

理解String

String的创建–new()与”“的区分

String类有一个特殊的创建方法,就是使用”“双引号来创建。例如new String(“123”)实际创建了2个String对象,一个是”123”通过”“双引号创建的,另一个是通过new创建的.只不过他们创建的时期不同,一个是编译期,一个是运行期。
String str1 = new String(“123”); ps:一般情况下,不要这样写..
String str2 = “123”;
第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。(实际是两个正如上文所说,但是在常量池中存在“123”后就不会再在常量池中创建新的“123”)
第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池里找有没有”abc”,如果没有,则将”abc”存放进字符串常量池,并令str指向”abc”,如果已经有”abc”则直接令str指向“abc”。

这时我们应该注意
一方面,第一种写法有利与节省内存空间.同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“123”);的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。另一方面,我们在使用诸如String str = “123”;的格式定义类时,总是想当然地认为,创建了String类的对象str。
对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。

1. 
String a = “abc”;①
  String b = “abc”;②
  分析:
  ①代码执行后在常量池(constant pool)中创建了一个值为abc的String对象,②执行时,因为常量池中存在”abc”所以就不再创建新的String对象了。
2.
  String c = new String(“xyz”);①
  String d = new String(“xyz”);②
  分析:①Class被加载时,”xyz”被作为常量读入,在常量池(constant pool)里创建了一个共享的值为”xyz”的String对象;然后当调用到new String(“xyz”)的时候,会在堆(heap)里创建这个new String(“xyz”)对象;②由于常量池(constant pool)中存在”xyz”所以不再创建”xyz”,然后创建新的new String(“xyz”)。
3.
  String s1 = new String(“xyz”); //创建二个对象(常量池和栈中),一个引用
  String s2 = new String(“xyz”); //创建一个对象(栈中),并且以后每执行一次创建一个对象,一个引用
  String s3 = “xyz”; //创建一个对象(常量池中),一个引用
  String s4 = “xyz”; //不创建对象(共享上次常量池中的数据),只是创建一个新的引用

String的比较equals和==

==比较的是2个对象的地址,而equals比较的是2个对象的内容

  String a = "123";      String b = "123";      System.out.println(a==b);      System.out.println(a.equals(b));      System.out.println("------------------------------------------");      /**      * true      * true      * 此处创建一个字符串"123"储存在常量池中      * 因为"123"储存在常量区,并且唯一,即两个String引用a和b所的地址相同所以a==b为true      * 并且两个引用在所指对象在堆中的内容相同所以a.equals(b)为true      */      String c = new String("1234");      String d = new String("1234");      System.out.println(c==d);      System.out.println(c.equals(d));      System.out.println("------------------------------------------");      /*      * false      * true      * 此处创建三个字符串“1234”,一个在常量池中,两个通过new储存在堆中      * 因为c和d此时指向的是堆中的两个String对象,所以地址不同 c==d为false      * 但是c与d堆中内容相同所以c.equals(d)为true      */      String e = "a1";      String f = "a"+1;      System.out.println(e==f);      System.out.println(e.equals(f));      System.out.println("------------------------------------------");      /**      * true      * true      * 此处创建“a1”“a”2个字符串其中“a1”“a”他们两个均在常量池中,你可能会问+是个运算符重载么?      * 是的,java自己有一定的运算符重载但是你没法使用定义自己的运算符重载,和c++不同,String f = "a"+1;      * 这句会被编译器做成 String f=“a1”;这与我们讲到的第一种情况相同,不再赘述。      * 编译器之所以这么做是因为他在编译时就能够确定      */      String g = "gh";      String hh = "h";      String h = "g" + hh ;      System.out.println(g==h);      System.out.println(g.equals(h));      System.out.println("------------------------------------------");      /**      * false      * true      * 与上面不同的是这里的h在编译时不能确定(编译器是这样认为的),所以h所指的对象在运行时确定储存在堆中,      * 所以g==h为true而g.equals(h)为false      */              

JAVA String为什么设计为不可变

常量池需要

  1. 字符串常量池的需要

字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
如下面的代码所示,将会在堆内存中只创建一个实际String对象.

[java] view plain copy
String s1 = “abcd”;
String s2 = “abcd”;

假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.

请思考: 假若代码如下所示,s1和s2还会指向同一个实际的String对象吗?

String s1= “ab” + “cd”;
String s2= “abc” + “d”;
也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件.

允许String对象缓存hashcode

String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

安全性

String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

线程安全且效率高

String是不可变类,String对象的状态是不变的,所以线程安全。所有不可变类都是线程安全的,但是线程安全的类并不一定是不可变类。比如StringBuffer,是可变类,但它的线程安全是靠锁来保证的。因为string是不可变的,所以绝对安全。
StringBuilder实际上自身维护一个char[]数组,append是没有synchronized。StringBuffer的append等很多操作都是带有synchronized的,所以同样线程安全。所以单线程字符串拼接一般采用StringBuilder,效率高。多线程环境则采用Stringbuffer,虽然安全,但是相对效率会低些。

String StringBuffer StringBuilder

  1)对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如”I”+”love”+”java”; 的字符串相加,在编译期间便被优化成了”Ilovejava”。这个可以用javap -c命令反编译生成的class文件进行验证。

  对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

  2)String、StringBuilder、StringBuffer三者的执行效率:

  StringBuilder > StringBuffer > String

  当然这个是相对的,不一定在所有情况下都是这样。

  比如String str = “hello”+ “world”的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。

  因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

  当字符串相加操作或者改动较少的情况下,建议使用 String str=”hello”这种形式;

  当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

0 0