忆Java String
平时 .NET 写多了, Java 很多东西都忘记了,前两天和同事聊天说到 equals 时谈到 Java 中的 String 时自己没能理清楚,唉,以前都研究过了,但还是忘了(头脑不行了),所以决定记在这里给自己长长脑子。
做 Java 时,我们可以发现对于 String 类型的实例化方式有以下两种:
· String str = “abc”;// 通过直接赋值的方式
· String str = new String(“abc”);// 通过构造方法完成
那么我们来看看这两种方式 Java 内部是如何处理的呢?
1 、直接赋值方式( String str = “abc” ):
2、通过构造方式(String str = new String(“abc”)):
通过上面的内存关系图可以发现通过构造方法实例化时程序开辟了两个空间,与直接赋值的方式相比,要浪费内存空间,所以可以总结出String实例化优先考虑使用直接赋值的方式。
那么通过上面的String堆栈我们又可以引伸出String的比较方式“==”和“equals”,我们以下面的一个简单的范例来说明问题:
public static void main(String[] args) { String str1 = "hello"; // 使用直接赋值的方式完成 String str2 = new String("hello"); // 通过构造方法完成 String str3 = str2; // 通过构造方法完成 System.out.println("str1 == str2 --> " + (str1 == str2)); // false System.out.println("str1 == str3 --> " + (str1 == str3)); // false System.out.println("str2 == str3 --> " + (str2 == str3)); // true System.out.println("str1.equals(str2)-->" + str1.equals(str2));// true System.out.println("str1.equals(str3)-->" + str1.equals(str3));// true System.out.println("str2.equals(str3)-->" + str2.equals(str3));// true }
分析:对上面程序进行内存划分
说明:
我们来看看“ == ”与“ equals ”分别指的是什么?
·“ == ”:比较的是两个对象的地址是否相等,具体的来说就是比较地址的值,因地址是以数值的形式存在的;
·“ equals ”:比较的是两个字符串的内容;
以下是很早以前从网上找的关于 Java 内存划分的说明
1、 寄存器:最快的存储区,由编译器根据需求进行分配,而我们在程序中是无法控的;
2 、栈:存放基本类型的变量数据和对象引用,但对象本身不存放在栈中,而是存放在堆中( new 出来的对象)或者常量池中(字符串常量对象存放在常量池中);
栈的说明:
当在一段代码块定义一个变量时, Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后, Java 会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用;
栈的优缺点:
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量( ,int, short, long, byte, float, double, boolean, char )和对象句柄。
范例:关于存在栈中的数据可以共享
假设我们同时定义:
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=4 ;那么编译器会重新搜索栈中是否有 4 值,如果没有,则将 4 存放进来,并令 a 指向 4 ;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
3 、堆:存放所有 new 出来的对象和数组
堆的说明:
在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量 , 这里引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象;
堆的优缺点:
Java 的堆是一个运行时数据区 , 类的对象从中分配空间。这些对象通过 new 、 newarray 、 anewarray 和 multianewarray 等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的, Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
4 、静态域:存放静态成员( Static 定义的);
5 、常量池:存放字符串常量(注:此处本人只能确定肯定有字符串常量,对于其他本人不确定,待定中… . ,网上也有说可以存放基本类型常量 public static final );
常量池的说明:
当然常量池中的对象可以共享,对于堆中的对象不可以共享,还有一点就是对于 equals 相等的字符串,在常量池中永远只有一份,而在堆中是有多份的;
6 、非 RAM 存储:硬盘等永久存储空间;
补充:
栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性
int i1 = 9; int i2 = 9; int i3 = 9; public static final int INT1 = 9; public static final int INT2 = 9; public static final int INT3 = 9;
package org.lxh.test; class BirthDate { private int day; private int month; private int year; public BirthDate(int d, int m, int y) { day = d; month = m; year = y; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } } public class Test{ public static void main(String args[]) { int date = 9; Testtest = new Test (); test.change(date); BirthDate bd = new BirthDate(7, 8, 98); System.out.println(bd.getDay() + "、" + bd.getMonth() + "、" + bd.getYear()); } public void change(int i) { i = 1234; } }
1). main方法开始执行:int date = 9;
date局部变量,基础类型,引用和值都存在栈中。
2). Test test = new Test();
test为对象引用,存在栈中,对象(new Test())存在堆中。
3). test.change(date);
i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。
4). BirthDate d1= new BirthDate(7,7,1970);
d1为对象引用,存在栈中,对象(new BirthDate())存在堆中,其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中。day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。
5).main方法执行完之后,date变量,test,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收。