String
( jdk 1.0 不可变字符序列)
- 字符串常量,字符串长度不可变 引用类型
关于String的思考
String str =new String("Hello World")
对象的创建 但是通过字符串Hello World
来创建另一个对象?String str2="Hello World"
这个是基础数据类型的创建 但是String是个对象类型 为何也可以这样创建
了解Class的文件结构 常量池
-
Class的文件结构class文件是8位字节的二进制流 。这些二进制流的涵义由一些紧凑的有意义的项 组成。比如class字节流中最开始的4个字节组成的项叫做魔数 (magic),其意义在于分辨class文件(值为0xCAFEBABE)与非class文件。class字节流大致结构如下图左侧, 其中,在class文件中有一个非常重要的项——常量池 。这个常量池专门放置源代码中的符号信息(并且不同的符号信息放置在不同标志的常量表中)。如上图右侧是
Hello World
代码中的常量表(Hello World
代码如下),其中有四个不同类型的常量表(四个不同的常量池入口)。
- 源代码编译成class文件之后,JVM就要运行这个class文件。它首先会用类装载器加载进class文件。然后需要创建许多内存数据结构来存放class文件中的字节数据。比如class文件对应的类信息数据、常量池结构、方法中的二进制指令序列、类方法与字段的描述信息等等。当然,在运行的时候,还需要为方法创建栈帧等。这么多的内存结构当然需要管理,JVM会把这些东西都组织到几个“运行时数据区 ”中。这里面就有我们经常说的“方法区 ”、“堆 ”、“Java栈 ”等
解析 1
String s1=new String("Hello world");编译成class文件后的指令
//Class字节码指令集代码 0 new java.lang.String [15] //在堆中分配一个String类对象的空间,并将该对象的地址堆入操作数栈。 3 dup//复制操作数栈顶数据,并压入操作数栈。该指令使得操作数栈中有两个String对象的引用值。 4 ldc <String "Hello world"> [17] //将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈 6 invokespecial java.lang.String(java.lang.String) [19] //调用String的初始化方法,弹出操作数栈栈顶的两个对象地址, //用拘留String对象的值初始化new指令创建的String对象,然后将这个对象的引用压入操作数栈 9 astore_1 [s] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是new指令创建出的,已经被初始化的String对象的地址。</pre>
- 事实上,在运行这段指令之前,JVM就已经为
"Hello World"
在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello World"
字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new
指令创建出来的新的String
对象,局部变量s1实际上存储的是new
出来的堆对象地址。 大家注意了,此时在JVM管理的堆中,有两个相同字符串值的String对象:一个是拘留字符串对象,一个是new
新建的字符串对象。如果还有一条创建语句String s2=new String("Hello World");
堆中有几个值为"Hello World"
的字符串呢? 答案是3个:一个拘留字符串对象,两个new的对象
- 换而言之 运行
String s1=new String("Hello world");
代码后 JVM会创建一个拘留字符串 但是S1的值储存的是拘留字符串的值在堆中用new
关键字创建出来的新的String
对象 每次用过new String
对象创建的字符串都会有二个对象的存在 即拘留字符串对象 和 通过拘留字符串的值创建的新String对象
解析 2
String s1="Hello world";编译成class文件后的指令
//Class字节码指令集代码 0 ldc <String "Hello world"> [15]//将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈 2 astore_1 [str] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是拘留字符串对象在堆中的地址
- 和上面的创建指令有很大的不同,局部变量s1存储的是早已创建好的拘留字符串的堆地址。 大家好好想想,如果还有一条创建语句
String s2="Hello word"
;此时堆中有几个值为"Hello world"
的字符串呢?答案是1个。s1和s2对引用了拘留字符串对象。并没有去创建新的String对象 这里解释了为什么String是引用类型但是可以用过基础类型的创建方式来创建
2. String不可变字符序列
//String源码
public final class String
{
private final char value[];
public String(String original) {
// 把原字符串original切分成字符数组并赋给value[];
}
}
- String中的是常量(final)数组,只能被赋值一次。
new String("abc")
使得value[]={'a','b','c'}
,之后这个String对象中的value[]
再也不能改变了。这也正是大家常说的,String
是不可变的原因 。
StringBuffer
(JDK 1.0 线程安全的可变字符序列)
- 字符串变量,(线程安全)如果要对字符串频繁修改,处于效率最好使用
StringBuffer
//StringBuffer源码
public final class StringBuffer extends AbstractStringBuilder {
char value[]; //继承了父类AbstractStringBuilder中的value[]
// 默认为16个字符
public StringBuffer() {
super(16);
}
public StringBuffer(String str) {
super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组
append(str); //将str切分成字符序列并加入到value[]中
}
//append方法
public synchronized StringBuffer append(Object obj) {
super.append(String.valueOf(obj));
return this;
}
//调用父类AbstractStringBuilder的方法
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
//这个方法中调用了ensureCapacityInternal ()方法判断count(字符数组原有的字符个数)+str.length() 的长度是 否大于value容量
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
//如果count+str.length() 长度大于value的容量 则调用方法进行扩容 下面是进行了数组的拷贝
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
}
StringBuffer
中的value[]
就是一个很普通的数组,而且可以通过append()
方法将新字符串加入value[]末尾。这样也就改变了value[]
的内容和大小了。对比发现
String
和StringBuffer
可变性本质上是指对象中的value[]
字符数组可不可变,而不是对象引用可不可变。在使用
StringBuffer
对象的时候尽量指定大小这样会减少扩容的次数,也就是会减少创建字符数组对象的次数和数据复制的次数,当然效率也会提升。
StringBuilder
(JDK 1.5 非线程安全的可变字符序列)
字符串变量(非线程安全) 字符序列的变长数组
由于
StringBuilder
相较于StringBuffer
有速度优势,所以多数情况下建议使用StringBuilder
类。然而在应用程序要求线程安全的情况下,则必须使用StringBuffer
类。
自动装箱和自动拆箱 (JDK1. 5)
类型 | 存储空间 | 范围 |
---|---|---|
int | 32位 4个字节 | -231——231-1 |
double | 64位 8个字节 | -21074------21024-1 |
float | 32位 4个字节 | -2149------2128-1 |
short | 16位 2个字节 | -215----215-1 |
long | 64位 8个字节 | -263-----263-1 |
char | 16位 2个字节 | 0----2^16-1 unicode字符 |
byte | 8位 1个字节 | -27----27-1 |
boolean | 1位 | ture /false |
一个字节占8个bit
-
关于
Intge
r和Int
int
初始值为0Integer
的初始值为Null
java会将[-128,127]之间的数进行缓存。 如果
Integer i1 = 127
时,会将127缓存,Integer j2 = 127
时,就直接从缓存中取,不会new了,所以结果为true。Integer i2 = 128
时,不会将128缓存,Integer j2 = 128
时,会return new Integer(128)
。所以结果为false。
-
结论
两个通过
new
出来的Integer
变量比较,结果为false。非
new
生成的Integer
变量与new Integer()
生成的变量比较,结果为false。两个非
new
生成的Integer
对象进行比较,如果两个变量的值在区间[-128,127]之间,比较结果为true;否则,结果为false。Integer
变量(无论是否是new生成的)与int变量比较,只要两个变量的值是相等的,结果都为true。
关于"==" ,equals
和hasCode
== 比较的是值是否相等 ,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址
对于
equals
方法,注意:equals
方法不能作用于基本数据类型的变量,equals
继承Object
类,比较的是是否是同一个对如果没有对equals
方法进行重写,则比较的是引用类型的变量所指向的对象的地址;诸如String、Date
等类对equals
方法进行了重写的话,比较的是所指向的对象的内容。hascode
返回一个数值 方法只有在集合中用到,将对象放入到集合中时,首先判断要放入对象的hashcode
值与集合中的任意一个元素的hashcode
值是否相等,如果不相等直接将该对象放入集合中。如果hashcode
值相等,然后再通过equals
方法判断要放入对象与集合中的任意一个对象是否相等,如果equals
判断不相等,直接将该元素放入到集合中,否则不放入。
为什么重写 equals
方法要重写 hashCode
(1)同一对象上多次调用
hashCode()
方法,总是返回相同的整型值。(2)如果
a.equals(b)
,则一定有a.hashCode()
一定等于b.hashCode()
。(3)如果
!a.equals(b)
,则a.hashCode()
不一定等于b.hashCode()
。此时如果a.hashCode()
总是不等于b.hashCode()
,会提高hashtables的性能。(4)
a.hashCode()==b.hashCode()
则a.equals(b)
可真可假(5)
a.hashCode()!= b.hashCode()
则a.equals(b)
为假。
线程sleep和wait有什么区别
功能差不多,都是用来进行线程控制的,他们最大本质区别是sleep不释放同步锁,wait()释放同步锁
sleep可以用指定时间自动唤醒,时间不到只能调用Inrerreput()来强行打断, wait可以用notify直接唤醒
二个方法来自于不同的类,Thread 和 Object
sleep需要捕获异常,wait.notify 不需要捕获
wait,notify ,notifyAll只能在同步代码块使用,sleep可以在任意位置使用