目录
1、中文怎么存储?
2、字符串存储
3、String、StringBuffer、StringBuilder
4、类与接口的区别
中文怎么存储?
什么时候char型不能存储汉字?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。
Java中用16位(也就是两字节)来表示一个char,一个汉字需要两字节来存储,所以,一个char可以存下一个汉字,所以可以存储中文。如果你指定了utf8编码就会占用三个字节,占用内存会变大。
字符串存储
JDK1.8中JVM把String常量池移入了堆中,同时取消了“永久代”,改用元空间代替(Metaspace)
java中对String对象特殊对待,所以在heap区域分成了两块,一块是字符串常量池(String constant pool),用于存储java字符串常量对象,另一块用于存储普通对象及字符串对象。
字符串创建的两种方法:
public static void main(String[] args) {
String a = "abc"; //第一种,==> 字符串常量,字符串常量池
String b = new String("abc"); //第二种 new新对象,==> 字符串对象
String c = "abc"; // 第一种,字符串常量池
System.out.println(a == b);//false
System.out.println(a == c);//true
}
new出来的对象怎么才能与常量池中的对象比较?
// 手动调用String.intern(),将new 出来的字符串对象加入到 字符串常量池 中
String s1 = new String("abc");
String s2 = "abc";
System.out.println(s1 == s2); //false
System.out.println(s1.intern() == s2); //true
引申案例:
public static void main(String[] args) {
String s1 = "abc";//字符串常量池
String s2 = "xyz";//字符串常量池
String s3 = "123";//字符串常量池
String s4 = "A";//字符串常量池
String s5 = new String("abc"); //堆里
char[] c = {'J','A','V','A'};
String s6 = new String(c);//堆里
String s7 = new String(new StringBuffer());//堆里
}
总结:
对于字符串:其对象的引用都是存储在栈中的,如果是【编译期创建好(双引号)的就存储在常量池中】,如果是【运行期(new出来的)才能确定的就存储在堆中】。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
String、StringBuffer、StringBuilder
1、可变与不可变:
a.String类中使用字符数组保存字符串,因为有“final”修饰符,所以string对象是不可变的。
private final char value[];
b.StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,这两种对象都是可变的。
char[] value; //char数组的初始长度为16
2.是否多线程安全
a.String中的对象是不可变的,可以理解为常量,显然线程安全。(不可变可以理解成:String类型对象本身是不可变的)
线程安全可以理解为共享变量值是安全的,而String用final修饰,会被存储到一个叫做常量池的内存区域。
但什么又会觉得String是可以被修改的,那么String的线程安全是怎么来的呢
String a="test";
a="test1"
这里a就变成了test1,其实在这里是新建了一个"test1"字符串对象(如果常量池没有这个值的话就是新建),然后将变量引用指向它。注意:这里并没有修改"test"这个变量的内部状态,"test"这个字符串对象是线程安全的。
b.StringBuilder是非线程安全的,StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public synchronized StringBuffer reverse() {
toStringCache = null;
super.reverse();
return this;
}
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
速度:StringBuilder > StringBuffer > String (string要每次创建一个新对象)
但是在连接字符串的时候,String+的速度大于StringBuilder、StringBuffer,因为String+在编译期间做了优化
例:
String str = "a" + "bc"; //对于String+的赋值语句,在编译期间,直接将String赋值为abc
但若有成百上千个的连接字符串,把String+语句拆分成for循环语句时,String+的速度小于StringBuilder、StringBuffer,因为String会每次循环就创建一个新对象
StringBuffer 与 stringBuilder的共同点
a. StringBuffer与StringBuilder有公共父类:AbstractStringBuilder(抽象类)。(抽象类与接口的区别)
b. 都会调用父类中的公共方法,如super.append()、super.delete()、super.reverse()等。只是StringBuffer会在方法上加synchronized关键字,进行同步。
使用append方法添加字符串的过程:
(1). 先检查内部char[]数组是否需要扩容
(2). 如需要扩容则进行扩容,然后将原来元数据copy到新数组中。 (因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式,这里是2倍。)
(3). 再将新添加的元数据加入到新char[]数组中
String、StringBuffer、StringBuilder区别
抽象类与接口的区别:
抽象类的实现往往是is-a关系:
例如提供Fish抽象类:
abstract class Fish{//鱼
private boolean isFast;
public abstract void desrcibe();
protected void swim() {
System.out.println("我能游泳");
}
}
它有自己的字段(isFast),有非抽象方法(swim),也有抽象方法(describe)
在声明它的一个子类时(如:金枪鱼),只需实现它的抽象方法(字段isFast可以留给对象来赋值),这个类就能用了,已实现的方法不用再写出来
class Tuna extends Fish{//金枪鱼
boolean isFast = true;
@Override //重写
void desrcibe() {
System.out.println("这是一条金枪鱼");
}
}
接口:
接口的实现往往是can-do关系: 例如提供aquatic(水生)接口:
interface aquatic{//水生接口
boolean isAquatic = true;
void swim();
void breath();
}
a.接口里的字段都默认是static final的,也就是常量,意味着在声明接口的时候就必须完成赋值;
b.它的方法都没有权限修饰符,因为接口的方法只能也默认是public的
c.在实现该接口时,需将它的方法全部实现:
class Carp implements aquatic{//鲤鱼
@Override
public void breath() {
System.out.println("我用鳃呼吸");
}
@Override
public void swim() {
System.out.println("我游得很慢");
}
}
总结:
抽象类:
- 除了不能被实现,抽象类和其他类没有任何区别
- java不支持多继承,所以一个子类只能有一个父类
- 抽象类中可以写方法的实现(方法体)
- 抽象类的字段和方法都可以用权限修饰符(public、private、protected等),如果不写权限修饰符,则默认是包内的类可访问
接口:
- 接口只能有常量字段,public static final
- 一个类可以实现多个接口,如这里的Carp还可以再声明一个卵生接口去实现它
- 接口中不能写方法的实现(方法体),但在jdk1.8以后可以接口里也可以写方法的默认实现(default标签),说明接口和抽象类的界限的确没那么严格了
- 抽象类的方法只能也默认是public的(不用写权限修饰符)