String在Java中存储

本文探讨了Java中字符串的存储方式,包括中文字符的存储、字符串常量池的改变以及String、StringBuffer和StringBuilder的异同。String是不可变的,而StringBuffer和StringBuilder在多线程环境下表现不同,后者在单线程环境中性能更优。同时,文章还对比了抽象类和接口的使用场景和特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

总结:


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在内存中如何存储(Java)

 


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("我游得很慢");
    }
}

总结:

抽象类:

  1. 除了不能被实现,抽象类和其他类没有任何区别
  2. java不支持多继承,所以一个子类只能有一个父类
  3. 抽象类中可以写方法的实现(方法体)
  4. 抽象类的字段和方法都可以用权限修饰符(public、private、protected等),如果不写权限修饰符,则默认是包内的类可访问

接口:

  1. 接口只能有常量字段,public static final
  2. 一个类可以实现多个接口,如这里的Carp还可以再声明一个卵生接口去实现它
  3. 接口中不能写方法的实现(方法体),但在jdk1.8以后可以接口里也可以写方法的默认实现(default标签),说明接口和抽象类的界限的确没那么严格了
  4. 抽象类的方法只能也默认是public的(不用写权限修饰符)

Java抽象类和接口的区别

Java抽象类与接口的区别

Java中接口和抽象类的区别?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值