一、两种实例化方式:
String str = “abc”;
String str = new String("abc");
一个字符串就是String的匿名对象。
"hello".equals(str) 一个字符串能够调用一个函数,可以看出,一个字符串是String的匿名对象。
二、比较
(1)直接赋值
String str = “abc”;表示一个堆内存指向给了栈内存。
直接赋值可以节省内存
举例:
- public class StringDemo01{
- public static void main(String args[]){
- //享元模式
- //在堆内存中有一个"hello"的String对象,而str1和str2和str3指向这个匿名对象
- String str1 = "hello";
- String str2 = "hello";
- String str3 = "hello";
- System.out.println("str1 == str2 --> "+(str1 == str2));
- System.out.println("str1 == str3 --> "+(str1 == str3));
- System.out.println("str2 == str3 --> "+(str2 == str3));
- }
- }
(2)new
举例:
- public class StringDemo01{
- public static void main(String args[]){
- String str1 = new String("hello");
- String str2 = new String("hello");
- System.out.println("str1==str2 -->"+(str1==str2));
- }
- }
1.创建了"hello"的匿名String对象。
2.创建一个str的对象,并开辟新的堆空间。
3.原来的“hello”的匿名对象变成垃圾。
因此开发中最好使用直接赋值。
三、字符串的不可改变性
为什么str+="world"这种连接操作效率低,就是因为这个。
所以才会有StringBuilder或者StringBuffer。
四、String和StringBuilder的性能比较
- public class StringDemo02{
- public static void main(String args[]){
- long begin1 = System.currentTimeMillis();
- String str = "";
- for(int i=0;i<10000;i++){
- str+=i;
- }
- long end1 = System.currentTimeMillis();
- System.out.println("String用时: "+(end1-begin1)+" ms"); //用时2000ms
- long begin2 = System.currentTimeMillis();
- StringBuilder sbuilder = new StringBuilder();
- for(int i=0;i<10000;i++){
- sbuilder.append(i);
- }
- long end2 = System.currentTimeMillis();
- System.out.println("StringBuilder用时: "+(end2-begin2)+" ms");//用时0ms,所以快了很多
- }
- }
===========================================================================================
String str=new String("x");
和String str="x";
这两句有什么区别?
在计算机的内存中有堆和栈两块空间,其中栈中存放所有对象、函数、变量等的饮用;而堆中存放对象、值等。
不管是new出的对象还是用类似String str = "x";这类的对象,都是放在堆中的。
对于你的第一个问题,举一个简单的例子:
- String str1 = "x" ;
- String str2 = "x" ;
- String str3 = new String("x") ;
表面上看这三句回实例化3个String对象,实际上是两个。 再java中有一个叫“字符数据池”的内存管理机制, 在对str2赋值之前,会检查池中有没有相同的数据,如果有,则直接引用;若没有,系统会实例化这个String的对象;而执行到String str3 = new String("x") ;系统会直接实例化该对象,而不会在池中查找。也不会将这个对象放在池中。
你可能会问,如果改动str1或str2时会不会改动另外一个引用的值。答案是不会。系统会首先在池中查找有没有相同的对象,如果没有,会实例化这个对象。
public static void main(String[] arg) {
String b = "1";
// String a=new String("a");
int c = 1;
String a = String.valueOf(c); // a!=b
// String a= String.valueOf("1"); //a=b
if (a == b) {
System.out.print("a");
}
}
http://fluagen.blog.51cto.com/146595/78018
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
3. Java中的数据类型有两种。
一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。 值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3; int b = 3; |
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。
4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。
5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。
(3)将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
String str1 = "abc"; String str2 = "abc"; System.out.println(str1==str2); //true |
注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。
我们再来更进一步,将以上代码改成:
String str1 = "abc"; String str2 = "abc"; str1 = "bcd"; System.out.println(str1 + "," + str2); //bcd, abc System.out.println(str1==str2); //false |
这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。
事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
再修改原来代码:
String str1 = "abc"; String str2 = "abc"; str1 = "bcd"; String str3 = str1; System.out.println(str3); //bcd String str4 = "bcd"; System.out.println(str1 == str4); //true |
str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。
当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。
可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。
我们再接着看以下的代码。
String str1 = new String("abc"); String str2 = "abc"; System.out.println(str1==str2); //false |
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
String str1 = "abc"; String str2 = new String("abc"); System.out.println(str1==str2); //false |
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。
(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。
(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。
(3)当比较包装类里面的数值是否相等时,用equals()方法;
(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
定义一个string类型的变量有两种方式:
string name= "tom ";
string name =new string( "tom ")
使用第一种方式的时候,就使用了串池,
使用第二中方式的时候,就是一种普通的声明对象的方式
如果你使用了第一种方式,那么当你在声明一个内容也是 "tom "的string时,它将使用串池里原来的那个内存,而不会重新分配内存,也就是说,string saname= "tom ",将会指向同一块内存
另外关于string类型是不可改变的问题:
string类型是不可改变的,也就是说,当你想改变一个string对象的时候,比如name= "madding "
那么虚拟机不会改变原来的对象,而是生成一个新的string对象,然后让name去指向它,如果原来的那个 "tom "没有任何对象去引用它,虚拟机的垃圾回收机制将接收它。
据说这样可以提高效率!!!哈哈
String a="123";
public static void test(Pass passA) {
passA.a="abc";
}
public static void main(String[] args) {
Pass passB=new Pass();
passB.a= "123";
System.out.println(passB.a);
test(passB);
System.out.println(passB.a);
}
}
结果是:
123
abc
从这个结果来看是通过引用传递的.String不是简单类型,那么是不是也是通过引用来传递呢?看下面这个例子:
public class Pass {
public static void test(String str) {
str = "World";
}
public static void main(String[] args) {
String string = "Hello";
System.out.println(string);
test(string);
System.out.println(string);
}
}
结果是:
Hello
Hello
第三个例子:
public class Pass {
public static void test(StringBuffer str) {
str.append("World");
}
public static void main(String[] args) {
StringBuffer string = new StringBuffer("Hello");
System.out.println(string);
test(string);
System.out.println(string);
}
}
结果:
Hello
HelloWorld
这我就不太明白了,既然String不是简单类型,那么它应该和对象是一样通过引用来传递的,可是结果却相反.而StringBuffer却是通过引用来传递的?
这到底是为什么?哪位帮忙解释一下.
所有的参数传递都是 传值,从来没有 传引用 这个事实。(
所有的参数传递都会在 程序运行栈上 新分配一个 值 的复制品.
楼主的第一段代码。
public static void test(Pass passA) {
passA.a="abc";
}
这个传的 PassA 的 地址值。这个 地址值 被复制了一份。
不信,你写:
public static void test(Pass passA) {
passA = null;
}
看看, 对passA有什么影响?
毫无作用。函数调用出来后,passA还是原来的值,不会变成Null.
但是,你的代码对 passA进行了操作 passA.a ,改变了passA的成员变量。
这个成员变量是一个真实指向String 的 地址,当然能够被改变。
这就是操作 (.) 和 赋值 (=) 的区别。
这是对 成员变量 a 的 赋值。真正改变了成员变量 a 的值。
注意,这里传递的参数是 passA, 而不是 a.
所以,passA 被复制了一份。passA 的这个副本的 a 变量还 指向 原来的 passA 的 a 变量。
public static void test(String str) {
str = "World";
}
只有对参数的 赋值,没有对参数的操作,当然不会产生影响。
栈内存 | 堆内存 |
基础类型,对象引用(堆内存地址) | 由new创建的对象和数组, |
存取速度快 | 相对于栈内存较慢 |
数据大小声明周期必须确定 | 分配的内存由java虚拟机自动垃圾回收器管理。动态分配内存大小 |
共享特性 栈中如果有字符串,则直接引用 如果没有,开辟新的空间存入值 | 每new一次在堆内存中生成一个新的对象。 |
创建之后值可以改变 | String类声明后则不可改变 |
一、栈内存
基础类型int, short, long, byte, float, double, boolean, char和对象引用
栈的共享特性
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
1、编译器先处理String str1 = "abc";它会在栈中创建一个变量为str1的引用,然后查找栈中是否有abc这个值,如果没找到,就将abc存放进来,然后将str1指向abc。
2、 接着处理String str2 = "abc";在创建完b的引用变量后,因为在栈中已经有abc这个值,便将str2直接指向abc。这样,就出现了str1与str2同时均指向abc的情况。
二、堆内存
new、newarray、anewarray和multianewarray等指令建立
要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。 由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
三、 == 内存地址比对
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true str1和str2同时指向 栈内存 中同一个内存空间
String str3 = "abc";
String str4 = new String("abc") ;
System.out.println(str3 == str4); //flase str3值在栈内存中,str4值在堆内存中
String hello = "hello" ;
String hel = "hel" ;
String lo = "lo" ;
System.out.println(hello == "hel" + "lo") ; //true
//两个常量相加,先检测栈内存中是否有hello如有有,指向已有的栈中的hello空间
System.out.println(hello == "hel" + lo) ; //flase
System.out.println(hello == hel + lo) ; //flase
//lo是在常量池中,不检查栈内存,在堆中产生一个新的hello
四、 equals 值进行比对
public boolean equals(Object anObject)
将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。
String str5 = "abc";
String str6 = new String("abc") ;
System.out.println(str5.equals(str6)); //true str5的值str6的值比对
五、 intern 栈中值的内存地址
Public String intern()
当调用 intern 方法时
1、如果池已经包含一个等于此 String 对象的字符串(用equals(Object) 方法确定),则返回池中的字符串。
2、将此 String 对象添加到池中,并返回此 String 对象的引用。
String s7 = new String("abc") ;
String s8 = "abc" ;
System.out.println(s7 == s7.intern()) ;//flase;
System.out.println(s8 == s7.intern() );//true
1.检查栈内存中有没有abc对象如果有
2.将s7指向pool中abc
===================================================================================================导读:对于程序员来说,知道堆空间,设置堆空间,处理堆空间的outOfMemoryError错误,分析heap dump是非常重要的。文中介绍了Java堆的学习教程以及Java堆内存(heap memory)的十个要点。
文章内容如下:
我刚开始学习Java编程时,可不知道什么是堆内存或堆空间(heap space),甚至根本不管对象创建时都放在哪里去了。正式了写一些程序后,经常会遇到java.lang.outOfMemoryError等错误,我才开始关注堆内存。
对大多数程序员都经历过这样的过程,因为学习一种语言是非常容易来的,但是学习基础是非常难的,因为没有什么特定的流程让你学习编程的每个基础,使你发觉编程的秘诀。
对于程序员来说,知道堆空间,设置堆空间,处理堆空间的outOfMemoryError错误,分析heap dump是非常重要的。这个关于Java堆的教程是给我刚开始学编程的兄弟看的。如果你知道这个基础知识或者知道底层发生了什么,当然可能帮助不是那么大。除非你知道了对象被创建在堆中,否则你不会意识到OutOfMemoryError是发生在堆空间中的。我尽可能的将我所知道的所有关于堆的知识都写下来了,也希望你们能够尽可能多的贡献和分享你的知识,以便可以让其他人也受益。
Java中的堆空间是什么?
当Java程序开始运行时,JVM会从操作系统获取一些内存。JVM使用这些内存,这些内存的一部分就是堆内存。堆内存通常在存储地址的底层,向上排列。当一个对象通过new关键字或通过其他方式创建后,对象从堆中获得内存。当对象不再使用了,被当做垃圾回收掉后,这些内存又重新回到堆内存中。要学习垃圾回收,请阅读”Java中垃圾回收的工作原理”。
如何增加Java堆空间
在大多数32位机、Sun的JVM上,Java的堆空间默认的大小为128MB,但也有例外,例如在32未Solaris操作系统(SPARC平台版本)上,默认的最大堆空间和起始堆空间大小为 -Xms=3670K 和 -Xmx=64M。对于64位操作系统,一般堆空间大小增加约30%。但你使用Java 1.5的throughput垃圾回收器,默认最大的堆大小为物理内存的四分之一,而起始堆大小为物理内存的十六分之一。要想知道默认的堆大小的方法,可以用默认的设置参数打开一个程序,使用JConsole(JDK 1.5之后都支持)来查看,在VM Summary页面可以看到最大的堆大小。
用这种方法你可以根据你的程序的需要来改变堆内存大小,我强烈建议采用这种方法而不是默认值。如果你的程序很大,有很多对象需要被创建的话,你可以用-Xms and -Xmx这两个参数来改变堆内存的大小。Xms表示起始的堆内存大小,Xmx表示最大的堆内存的大小。另外有一个参数 -Xmn,它表示new generation(后面会提到)的大小。有一件事你需要注意,你不能任意改变堆内存的大小,你只能在启动JVM时设定它。
堆和垃圾回收
我们知道对象创建在堆内存中,垃圾回收这样一个进程,它将已死对象清除出堆空间,并将这些内存再还给堆。为了给垃圾回收器使用,堆主要分成三个区域,分别叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation是用来存放新建的对象的空间,在对象新建的时候被使用。如果长时间还使用的话,它们会被垃圾回收器移动到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta数据的地方,例如类,方法,字符串池和类级别的详细信息。你可以查看“Java中垃圾回收的工作原理”来获得更多关于堆和垃圾回收的信息。
Java堆中的OutOfMemoryError错误
当JVM启动时,使用了-Xms 参数设置的对内存。当程序继续进行,创建更多对象,JVM开始扩大堆内存以容纳更多对象。JVM也会使用垃圾回收器来回收内存。当快达到-Xmx设置的最大堆内存时,如果没有更多的内存可被分配给新对象的话,JVM就会抛出java.lang.outofmemoryerror,你的程序就会当掉。在抛出 OutOfMemoryError之前,JVM会尝试着用垃圾回收器来释放足够的空间,但是发现仍旧没有足够的空间时,就会抛出这个错误。为了解决这个问题,你需要清楚你的程序对象的信息,例如,你创建了哪些对象,哪些对象占用了多少空间等等。你可以使用profiler或者堆分析器来处理 OutOfMemoryError错误。”java.lang.OutOfMemoryError: Java heap space”表示堆没有足够的空间了,不能继续扩大了。”java.lang.OutOfMemoryError: PermGen space”表示permanent generation已经装满了,你的程序不能再装在类或者再分配一个字符串了。
Java Heap dump
Heap dump是在某一时间对Java堆内存的快照。它对于分析堆内存或处理内存泄露和Java.lang.outofmemoryerror错误是非常有用的。在JDK中有一些工具可以帮你获取heap dump,也有一些堆分析工具来帮你分析heap dump。你可以用“jmap”来获取heap dump,它帮你创建heap dump文件,然后,你可以用“jhat”(堆分析工具)来分析这些heap dump。
Java堆内存(heap memory)的十个要点:
1. Java堆内存是操作系统分配给JVM的内存的一部分。
2. 当我们创建对象时,它们存储在Java堆内存中。
3. 为了便于垃圾回收,Java堆空间分成三个区域,分别叫作New Generation, Old Generation或叫作Tenured Generation,还有Perm Space。
4. 你可以通过用JVM的命令行选项 -Xms, -Xmx, -Xmn来调整Java堆空间的大小。不要忘了在大小后面加上”M”或者”G”来表示单位。举个例子,你可以用 -Xmx256m来设置堆内存最大的大小为256MB。
5. 你可以用JConsole或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()来查看Java中堆内存的大小。
6. 你可以使用命令“jmap”来获得heap dump,用“jhat”来分析heap dump。
7. Java堆空间不同于栈空间,栈空间是用来储存调用栈和局部变量的。
8. Java垃圾回收器是用来将死掉的对象(不再使用的对象)所占用的内存回收回来,再释放到Java堆空间中。
9. 当你遇到java.lang.outOfMemoryError时,不要紧张,有时候仅仅增加堆空间就可以了,但如果经常出现的话,就要看看Java程序中是不是存在内存泄露了。
10. 请使用Profiler和Heap dump分析工具来查看Java堆空间,可以查看给每个对象分配了多少内存。