Java的内存模型
Java的内存主要分为两种:堆内存与非堆内存,非堆内存主要包括方法区(jdk8中,移除了方法区,转而用Metaspace区域替代,实际区别不大。PS:这个我也是不懂啦,只是看大牛博客,说区别不大,只是换个名字XD),栈内存。
- 堆内存是由jvm的GC负责的,在运行时动态的分配内存空间,生命周期也不必要事先告诉编译器,但是存取速度比较慢。
栈内存存取速度比较快,仅次与寄存器,使命周期必须提前告诉编译器,栈内存是线程私有的,两个栈帧作为虚拟机的元素,是完全独立的的,但是大多数虚拟机会对其进行优化处理,令两个栈帧出现一部分重叠,让下面栈帧部分的操作数与上面栈帧的部分局部变量表重叠在一起,这样方法调用的时候就可以共享一部分数据。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。
虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。
对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率。
String类的概念和不变性
String类的概念和不变性:
API中的String类的描述,发现String 类代表字符串.
Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现而字符串是常量,在创建之后不能更改,其实就是说一旦这个字符串确定了,那么就会在内存区域中就生成了这个字符串。字符串本身不能改变,但str变量中记录的地址值是可以改变的。
源码分析:
String类底层采用的是字符数组:private final char value[]
private 修饰说明value只能在String类内部使用,而且又没有提供get方法,所以外部无法获取value数组,就无法改变数组中元素的值
final修饰说明value是常量,一旦创建,就不能被改变,value一旦被初始化成某个数组,将永远指向这个数组,不可能再指向其它的数组了.
String类特点:
- 一切都是对象,字符串事物 “” 也是对象
- 类是描述事物,String类,描述字符串对象的类
- 所有的 “” 都是String类的对象
- 字符串是一个常量,一旦创建,不能改变
public class StringDemo {
public static void main(String[] args) {
//引用变量str执行内存变化
//定义好的字符串对象,不变
String str = "roger";
System.out.println(str);
str = "Roger";
System.out.println(str);
}
}
String类创建方式和比较
String s3 = "abc";
在内存中只有一个对象。这个对象在字符串常量池中.
String s4 = new String("abc");
在内存中有两个对象。一个new的对象在堆中,一个字符串本身对象,在字符串常量池中.
public class StringDemo2 {
public static void main(String[] args) {
//字符串定义方式2个, 直接= 使用String类的构造方法
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1);
System.out.println(str2);
System.out.println(str1==str2);//引用数据类型,比较对象的地址 false
System.out.println(str1.equals(str2));//true
}
}
String类构造方法
String类构造方法:
public String()
:空构造public String(byte[] bytes)
:把字节数组转成字符串public String(byte[] bytes,int index,int length)
:把字节数组的一部分转成字符串public String(String original)
:把字符串常量值转成字符串
public class StringDemo3 {
public static void main(String[] args) {
function_1();
}
定义方法,String类的构造方法:
String(byte[] bytes)
传递字节数组,字节数组转成字符串,通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。String(byte[] bytes, int offset, int length)
传递字节数组,字节数组的一部分转成字符串,offset 数组的起始的索引,length 个数,转几个 , 不是结束的索引.
public static void function(){
byte[] bytes = {97,98,99,100};
//调用String类的构造方法,传递字节数组
String s = new String(bytes);
System.out.println(s);
byte[] bytes1 ={65,66,67,68,69};
//调用String构造方法,传递数组,传递2个int值
String s1 = new String(bytes1,1,3);
System.out.println(s1);
}
}
public String(char[] value)
:把字符数组转成字符串public String(char[] value,int index,int count)
:把字符数组的一部分转成字符串
public class StringDemo3 {
public static void main(String[] args) {
function_1();
}
/*
* String(char[] value) 传递字符数组
* 将字符数组,转成字符串, 字符数组的参数,不查询编码表
*
* String(char[] value, int offset, int count) 传递字符数组
* 将字符数组的一部分转成字符串
* offset 数组开始索引
* count 个数
*/
public static void function_1(){
char[] ch = {'a','b','c','d','e','f'};
//调用String构造方法,传递字符数组
String s = new String(ch);
System.out.println(s);
String s1 = new String(ch,1,4);
System.out.println(s1);
}
}
String类的其他方法
int length()
: 返回字符串的长度String substring(int beginIndex,int endIndex)
: 获取字符串的一部分String substring(int beginIndex)
: 获取字符串的一部分boolean startsWith(String prefix)
: 判断一个字符串是不是另一个字符串的前缀,开头boolean endsWith(String prefix)
: 判断一个字符串是不是另一个字符串的后缀,结尾boolean contains (String s)
: 判断一个字符串中,是否包含另一个字符串int indexOf(char ch)
: 查找一个字符,在字符串中第一次出现的索引,被查找的字符不存在,返回-1byte[] getBytes()
: 将字符串转成字节数组,此功能和String构造方法相反,byte数组相关的功能,查询编码表char[] toCharArray()
: 将字符串转成字符数组,功能和构造方法相反boolean equals(Object obj)
: 方法传递字符串,判断字符串中的字符是否完全相同,如果完全相同返回trueboolean equalsIgnoreCase(String s)
: 传递字符串,判断字符串中的字符是否相同,忽略大小写
public class StringDemo4 {
public static void main(String[] args) {
function_9();
}
/*
* boolean equals(Object obj)
* 方法传递字符串,判断字符串中的字符是否完全相同,如果完全相同返回true
*
* boolean equalsIgnoreCase(String s)
* 传递字符串,判断字符串中的字符是否相同,忽略大小写
*/
public static void function_9(){
String str1 = "Abc";
String str2 = "abc";
//分别调用equals和equalsIgnoreCase
boolean b1 = str1.equals(str2);
boolean b2 = str1.equalsIgnoreCase(str2);
System.out.println(b1);
System.out.println(b2);
}
/*
* char[] toCharArray() 将字符串转成字符数组
* 功能和构造方法相反
*/
public static void function_8(){
String str = "roger";
//调用String类的方法toCharArray()
char[] ch = str.toCharArray();
for(int i = 0 ; i < ch.length ; i++){
System.out.println(ch[i]);
}
}
/*
* byte[] getBytes() 将字符串转成字节数组
* 此功能和String构造方法相反
* byte数组相关的功能,查询编码表
*/
public static void function_7(){
String str = "abc";
//调用String类方法getBytes字符串转成字节数组
byte[] bytes = str.getBytes();
for(int i = 0 ; i < bytes.length ; i++){
System.out.println(bytes[i]);
}
}
/*
* int indexOf(char ch)
* 查找一个字符,在字符串中第一次出现的索引
* 被查找的字符不存在,返回-1
*/
public static void function_6(){
String str = "roger.cn";
//调用String类的方法indexOf
int index = str.indexOf('x');
System.out.println(index);
}
/*
* boolean contains (String s)
* 判断一个字符串中,是否包含另一个字符串
*/
public static void function_5(){
String str = "roger.cn";
//调用String类的方法contains
boolean b =str.contains("ac");
System.out.println(b);
}
/*
* boolean endsWith(String prefix)
* 判断一个字符串是不是另一个字符串的后缀,结尾
* Demo.java
* .java
*/
public static void function_4(){
String str = "Demo.java";
//调用String类方法endsWith
boolean b = str.endsWith(".java");
System.out.println(b);
}
/*
* boolean startsWith(String prefix)
* 判断一个字符串是不是另一个字符串的前缀,开头
* howareyou
* hOw
*/
public static void function_3(){
String str = "howareyou";
//调用String类的方法startsWith
boolean b = str.startsWith("hOw");
System.out.println(b);
}
/*
* String substring(int beginIndex,int endIndex) 获取字符串的一部分
* 返回新的字符串
* 包含头,不包含尾巴
*
* String substring(int beginIndex)获取字符串的一部分
* 包含头,后面的字符全要
*/
public static void function_2(){
String str = "howareyou";
//调用String类方法substring获取字符串一部分
str= str.substring(1, 5);
System.out.println(str);
String str2 = "HelloWorld";
str2 = str2.substring(1);
System.out.println(str2);
}
/*
* int length() 返回字符串的长度
* 包含多少个字符
*/
public static void function(){
String str = "cfxdf#$REFewfrt54GT";
//调用String类方法length,获取字符串长度
int length = str.length();
System.out.println(length);
}
}
StringBuffer类
StringBuffer类是线程安全的(被synchronized修饰)可变字符序列,底层采用字符数组实现,初始容量为16。
常用方法:
StringBuffer append()
:将任意类型的数据,添加缓冲区,append 返回值,写return this,调用者是谁,返回值就是谁delete(int start,int end)
: 删除缓冲区中字符,开始索引包含,结尾索引不包含insert(int index, 任意类型)
: 将任意类型数据,插入到缓冲区的指定索引上replace(int start,int end, String str)
: 将指定的索引范围内的所有字符,替换成新的字符串reverse()
: 将缓冲区中的字符反转String toString()
: 继承Object,重写toString(),将缓冲区中的所有字符,变成字符串
public class StringBufferDemo {
public static void main(String[] args) {
function_5();
}
/*
* StringBuffer类的方法
* String toString() 继承Object,重写toString()
* 将缓冲区中的所有字符,变成字符串
*/
public static void function_5(){
StringBuffer buffer = new StringBuffer();
buffer.append("abcdef");
buffer.append(12345);
//将可变的字符串缓冲区对象,变成了不可变String对象
String s = buffer.toString();
System.out.println(s);
}
/*
* StringBuffer类的方法
* reverse() 将缓冲区中的字符反转
*/
public static void function_4(){
StringBuffer buffer = new StringBuffer();
buffer.append("abcdef");
buffer.reverse();
System.out.println(buffer);
}
/*
* StringBuffer类方法
* replace(int start,int end, String str)
* 将指定的索引范围内的所有字符,替换成新的字符串
*/
public static void function_3(){
StringBuffer buffer = new StringBuffer();
buffer.append("abcdef");
buffer.replace(1, 4, "Q");
System.out.println(buffer);
}
/*
* StringBuffer类方法 insert
* insert(int index, 任意类型)
* 将任意类型数据,插入到缓冲区的指定索引上
*/
public static void function_2(){
StringBuffer buffer = new StringBuffer();
buffer.append("abcdef");
buffer.insert(3, 9.5);
System.out.println(buffer);
}
/*
* StringBuffer类方法
* delete(int start,int end) 删除缓冲区中字符
* 开始索引包含,结尾索引不包含
*/
public static void function_1(){
StringBuffer buffer = new StringBuffer();
buffer.append("abcdef");
buffer.delete(1,5);
System.out.println(buffer);
}
/*
* StringBuffer类方法
* StringBuffer append, 将任意类型的数据,添加缓冲区
* append 返回值,写return this
* 调用者是谁,返回值就是谁
*/
public static void function(){
StringBuffer buffer = new StringBuffer();
//调用StringBuffer方法append向缓冲区追加内容
buffer.append(6).append(false).append('a').append(1.5);
System.out.println(buffer);
}
}
StringBuilder类与StringBuffer类
jdk的实现中StringBuffer与StringBuilder都继承自AbstractStringBuilder,StringBuffer是线程安全的,StringBuilder是线程不安全的,但是效率较高,在jdk1.5后,”+”默认的是使用new一个StringBuilder的方法实现字符串的拼接,在之前是使用new一个StringBuffer来实现的。
这里随便讲讲AbstractStringBuilder的实现原理:我们知道使用StringBuffer等无非就是为了提高java中字符串连接的效率,因为直接使用+进行字符串连接的话,jvm会创建多个String对象,因此造成一定的开销。AbstractStringBuilder中采用一个char数组来保存需要append的字符串,char数组有一个初始大小,当append的字符串长度超过当前char数组容量时,则对char数组进行动态扩展,也即重新申请一段更大的内存空间,然后将当前char数组拷贝到新的位置,因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式。
总结
String不变性:
- String类是被final修饰的char[]
- String默认存储在运行时常量池,不可变
- 使用”+”进行字符串拼接实际并不会改变String,而是引用指一个新new的String对象
String,StringBuffer,StringBuilder的区别:
- StringBuffer是jdk1.0版本的,是线程安全的,效率低
- StringBuilder是jdk1.5版本的,是线程不安全的,效率高
- String是常量,不可变的数组,StringBuffer,StringBuilder是可变字符数组(默认长度是16)