Java基础之String的理解与应用,扩展StringBuffer的使用

前言

真的懂String么?真的懂String里面的==与equals的差别么?我想说原来可能我懂,但是后来就没有后来了。。。

想要了解一个类,最好的办法就是看这个类的实现源代码,自己去编译器里面去看吧。我就不粘贴代码了。

一、String类

1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
2)String类其实是通过char数组来保存字符串的。
3)substring,concat,replace从源码中的这三个方法可以看出,无论是substring、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

看下继承结构源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    ..............
}

可以看到String是final的,不允许继承.里面用来存储value的是一个final数组,也是不允许修改的。

构造方法

public String() {
    this.value = "".value;
}
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
...

可以看到默认的构造器是构建的空字符串,其实所有的构造器就是给value数组赋初值.

二、字符串常量池

我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池
每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。

Java中的常量池,实际上分为两种形态:静态常量池运行时常量池
静态常量池:即.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
运行时常量池:则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池

来看下面的程序:

String a = "yzzCool";
String b = "yzzCool";

a、b和字面上的yzzCool都是指向JVM字符串常量池中的"yzzCool"对象,他们指向同一个对象。

String c = new String("yzzCool");

new关键字一定会产生一个对象yzzCool(注意这个yzzCool和上面的yzzCool不同),同时这个对象是存储在堆中。所以上面应该产生了两个对象:保存在栈中的c和保存堆中abcdef。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的abcdef应该是引用字符串常量池中abcdef。所以c、abcdef、池abcdef的关系应该是:c--->yzzCool--->池yzzCool。整个关系如下:


关系图

图水平一般,努力看还是能看清楚的🤦‍。
总结:虽然a、b、c、yzzCool是不同的对象,但是从String的内部结构我们是可以理解上面的。String c = new String("yzzCool");虽然c的内容是创建在堆中,但是他的内部value还是指向JVM常量池的yzzCool的value,它构造yzzCool时所用的参数依然是yzzCool字符串常量。

三、常见String面试题

3.1 String str = new String(“abc”)创建了多少个实例?

这个问题其实是不严谨的,但面试一般会遇到,所以我们要补充来说明。
类的加载和执行要分开来讲:
创建了两个。

1、当加载类时,”abc”被创建并驻留在了字符创常量池中(如果先前加载中没有创建驻留 过)。

2、当执行此句时,因为”abc”对应的String实例已经存在于字符串常量池中,所以JVM会将此实例复制到会在堆(heap)中并返回引用地址。

3.2 JDK1.7的Intern的执行

   /**
     * 测试intern
     */
    private static void testIntern() {

        System.out.println("======================");
        String s0 = "event";
        String s1 = new String("event");
        String s2 = new String("event");
        s2 = s2.intern(); //把常量池中“event”的引用赋给s2
        System.out.println(s0 == s1);//false
        System.out.println(s0 == s1.intern());//true
        System.out.println(s0 == s2);//true


        System.out.println("======================");
        String s11 = new String("look");
        String s12 = s11.intern();
        String s13 = "look";
        System.out.println(s11 == s12);//false
        System.out.println(s12 == s13);//true
        System.out.println(s11 == s13);//false


        System.out.println("======================");
        String s3 = new String("abc") + new String("abc");
        String s4 = "abcabc";
        String s5 = s3.intern();
        System.out.println(s5 == s3);//false
        System.out.println(s5 == s4);//true
        System.out.println(s3 == s4);//false

        System.out.println("======================");
        String s6 = new String("go") + new String("od");
        String s7 = s6.intern();
        String s8 = "good";
        System.out.println(s6 == s7);//true
        System.out.println(s7 == s8);//true
        System.out.println(s6 == s8);//true

    }

结果如下

false
true
true
======================
false
true
false
======================
false
true
false
======================
true
true
true

扩展1:String、StringBuffer、StringBuilder区别

StringBuffer、StringBuilder和String一样,也用来代表字符串。

  • String类是不可变类,任何对String的改变都 会引发新的String对象的生成;
  • StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象。既然可变和不可变都有了,为何还有一个StringBuilder呢?相信初期的你,在进行append时,一般都会选择StringBuffer吧!

先说一下集合的故事,HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBufferd支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。
适合多线程使用的是StringBuffer,性能就没有StringBuilder好,其他大致一样

StringBuffer常用方法

由于StringBuffer和StringBuilder在使用上几乎一样,所以只写一个,以下部分内容网络各处收集,不再标注出处
1.初始化;

StringBuffer s = new StringBuffer();

这样初始化出的StringBuffer对象是一个空的对象,

 StringBuffer sb1=new StringBuffer(512);

分配了长度512字节的字符缓冲区。

StringBuffer sb2=new StringBuffer(“how are you?”)

创建带有内容的StringBuffer对象,在字符缓冲区中存放字符串“how are you?”

2.append()追加

public StringBuffer append(boolean b)

append里面的参数是多种多样啊!
该方法的作用是追加内容到当前StringBuffer对象的末尾,类似于字符串的连接,调用该方法以后,StringBuffer对象的内容也发生改 变,例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.append(true);
则对象sb的值将变成”abctrue”
使用该方法进行字符串的连接,将比String更加节约内容,经常应用于数据库SQL语句的连接。
3.deleteCharAt(int index)删除

public StringBuffer deleteCharAt(int index)

该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。例如:
StringBuffer sb = new StringBuffer(“KMing”);
sb. deleteCharAt(1);
该代码的作用删除字符串对象sb中索引值为1的字符,也就是删除第二个字符,剩余的内容组成一个新的字符串。所以对象sb的值变 为”King”。
如果是这么写:
StringBuffer sb = new StringBuffer(“KMing”);
StringBuffer sb1 = sb. deleteCharAt(1);
你会发现sb和sb1的值是一样的。
还存在一个功能类似的delete方法:

public StringBuffer delete(int start,int end)

该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间。例如:
StringBuffer sb = new StringBuffer(“TestString”);
sb. delete (1,4);
该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余的字符形成新的字符串。则对象sb的值是”TString”。

4.insert(int offset, Object obj)插入

public StringBuffer insert(int offset, Object obj)

该方法的作用是在StringBuffer对象中插入内容,然后形成新的字符串。第二个参数可以是好多形式,下面以boolean为例子。
StringBuffer sb = new StringBuffer(“TestString”);
sb.insert(4,false);
该示例代码的作用是在对象sb的索引值4的位置插入false值,形成新的字符串,则执行以后对象sb的值是”TestfalseString”。

5.reverse()反转

public StringBuffer reverse()

该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串。例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.reverse();
经过反转以后,对象sb中的内容将变为”cba”。

6.setCharAt(int index, char ch)插入字符

public void setCharAt(int index, char ch)

该方法的作用是修改对象中索引值为index位置的字符为新的字符ch。例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.setCharAt(1,’D’);
则对象sb的值将变成”aDc”。

7.trimToSize()缩小存储空间

public void trimToSize()

该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费,和String的trim()是一样的作用,不在举例。

8.length()得到字符的长度

 public  void setLength(int newLength)

该方法的作用是设置字符串缓冲区大小。
StringBuffer sb=new StringBuffer();
sb.setlength(100);
如果用小于当前字符串长度的值调用setlength()方法,则新长度后面的字符将丢失。
意思就是:我们现在的StringBuffer的长度是120,如果我们设置setLength(100),则我们的StringBuffer最终的长度是100,如果是提前运行setLength(100),然后进行append()操作,则结果是是多少就是多少。

9.capacity()得到字符串容量的大小

 public int capacity() 

该方法的作用是获取字符串的容量。
StringBuffer sb=new StringBuffer(“string”);
int i=sb.capacity();

10.ensureCapacity(int minimumCapacity)设置字符串的容量。

 public void ensureCapacity(int minimumCapacity)

该方法的作用是重新设置字符串容量的大小。
StringBuffer sb=new StringBuffer();
sb.ensureCapacity(32); //预先设置sb的容量为32
注意:我们来分析一下capacity()和ensureCapacity(int minimumCapacity),如果我们设置ensureCapacity(30),但是我们得到的capacity()的值并不是30,这是什么玩意???下面自己的理解:这里设置容量就是提前告诉字符串,我会达到这个水平,有一个明显的现象就是字符串有一个默认的容量是16,当目前的容量小于16的时候,用capacity()得到的值都是16,当容量大于等于16时,要扩容了扩容的原则是“目前的容量+2”就是16+16+2=34,容量在16~34之间时,capacity()得到的值都是34..........以后根据这个原则扩容。
又有问题了,我的容量不是16,34,80.....这是什么情况??嘿嘿,是这样的如果你在新建StringBuffer的时候是这样写的,如下:

  StringBuffer buffer1 = new StringBuffer("1234567890");

那么他的初始容量不是16,是“16+字符串的长度”,也就是上面的容量是10+16=26;那么它的规则就是26,54,110.........

11.getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)处理字符串的子字符串复制给数组。

 public  void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

该方法的作用是将字符串的子字符串复制给数组。
StringBuffer sb = new StringBuffer("I love You");
int begin = 0;
int end = 5;
//注意ch字符数组的长度一定要大于等于begin到end之间字符的长度
//小于的话会报ArrayIndexOutOfBoundsException
//如果大于的话,大于的字符会以空格补齐
char[] ch = new char[end-begin];
sb.getChars(begin, end, ch, 0);
System.out.println(ch);
结果:I lov
参数的意义:srcBegin:从字符串的第几位开始,(包括这一位);
srcEnd:从字符串的第几位开始,(不包括这一位);
dst:要操作的字符串数组;
dstBegin:从数组的第几个下表开始处理。

注意:

上面的解释如果感觉不是很详细可以看Java中String的实现与应用这篇博客。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容