面试官再问你字符串,就把这篇文章扔给他

前言


前两天说好了要写一篇Java字符串的,这不,现在就要开始了。实不相瞒,在我最开始学字符串的时候,也遇到了一点小麻烦,不过好在是解决了。(PS:在写这篇文章的时候,我内心原本坚定的理想又开始动摇了,这个就放在最后讲。)

这篇文章的大致结构如下:

针对这篇文章题目,试着思考一下面试官会问你的问题,自然不可能是十分基础的。那么,这篇文章的侧重点就出来了,那就是深入理解字符串的不可变性

字符串的基本使用


关于字符串的使用,我打算从以下两个方向讲:

  • 从创建字符串开始
  • 学会使用字符串在API

从创建字符串开始

创建字符串对象有两种方法:

  • 直接创建(也叫使用字面量)
  • 使用关键字new

看完下面的例子你就知道了:

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   //字面量直接创建
        String school = new String("重庆邮电大学");  //使用关键字new
    }
}
复制代码

上面的代码创建了两个字符串,第一个是使用字面量的方法创建的,第二个是使用关键字new创建的,简单吧。至于这两者的区别嘛,我们放到后面来再讲。

学会使用字符串在API

Java字符串提供了大量的API供我们使用,如果要一一列举的话,是肯定讲不完的,而且这也违背了这篇文章的本意。我就说一说常用的方法:

获取字符串长度

计算字符串的长度用的是length() 方法,注意这里要和数组的length 区分,数组是没有括号的,而字符串是有括号的。

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String[] arr = {"A", "B", "C"};  //定义一个数组,长度为3
        String school = new String("重庆邮电大学");   //字符串的长度为6

        System.out.println(arr.length);  //输出:3
        System.out.println(school.length()); // 输出:6
    }
}
复制代码

连接两个字符串

连接两个字符串用的方法是concat(String str),格式是这样的string1.concat(string2);下面写一个例子:

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   //字面量直接创建
        String school = new String("重庆邮电大学");  //使用关键字new

        String newString = name.concat(school);  //连接两个字符串
        System.out.println(newString);  //输出:Yangc重庆邮电大学
    }
}

复制代码

访问确定位置的字符

我们提前给定了一个字符串,知道了某个字符在该字符串的位置,现在要把这个字符取出来,就可以使用charAt(int index) 方法。该方法返回某个确定位置的字符,现在来看一个例子:

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   //字面量直接创建
        String school = new String("重庆邮电大学");  //使用关键字new

        char newChar = school.charAt(1);
        System.out.println(newChar);  //输出:庆
    }
}
复制代码

截取字符串

给定了一个字符串,但是我们只需要其中的某一个部分,这个时候就会用到substring() 这个方法截取了字符串的某一个部分:

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   //字面量直接创建
        String school = new String("重庆邮电大学");  //使用关键字new

        String subString = school.substring(1, 3);
        System.out.println(subString);  //输出:庆邮
    }
}
复制代码

好了,就讲这几个方法吧。组合使用上面说到的方法,就可以对一个字符串进行各种操作了。这里会有更多的介绍:菜鸟教程---Java字符串的各种API

深入理解字符串的不可变性


正如文章开头所说的那样,在理解字符串的不可变性上我是吃了亏的,还好在各位前辈大神的指引下我终于彻悟了,不过这也让我更加认识到学习英语的重要性。

如果说仅仅是学会了前面的内容,那么也只能说是学会了冰上一角。现在,才真正开始这篇文章。

Java虚拟机的内存模型

如果要彻底理解,我们就必须得了解一点Java虚拟机的小知识。那么,什么是Java虚拟机呢?下面引用一下wiki上面的解释:

1、Java虚拟机(英语:Java Virtual Machine,缩写为JVM),一种能够运行Java bytecode的虚拟机,以堆栈结构机器来进行实做。最早由太阳微系统所研发并实现第一个实现版本,是Java平台的一部分,能够运行以Java语言写作的软件程序。

2、Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。通过对中央处理器(CPU)所执行的软件实现,实现能执行编译过的Java程序码(Applet与应用程序)。

3 、作为一种编程语言的虚拟机,实际上不只是专用于Java语言,只要生成的编译文件符合JVM对加载编译文件格式要求,任何语言都可以由JVM编译运行。此外,除了甲骨文,也有其他开源或闭源的实现。

维基百科上面解释不少的概念,我觉得有必要提炼一下信息:

  • JVM是一种执行Java bytecode(即字节码)的虚拟机。
  • 因为JVM的存在,实现了Java语言的跨平台的特性。
  • JVM不只可以用于Java语言,还可以用于其他语言。

先说一说上面的那个图,我们在执行Java程序的,编译器会把源文件(.java)编译为字节码文件(.class),Java虚拟机只对字节码文件进行操作,而不会对源文件进行操作。正是因为JVM和字节码的共同作用,使得Java程序具有了跨平台的特性。那么JVM内部到底怎样工作的呢,下面我就简单介绍一下。

不过先得扯一扯JVM内存模型了,看看下面这张图:

在执行Java程序的时候,计算机内存会专门开辟一块连续的内存,这块内存空间由五个部分组成(见上图)。其中,红色部分的区域是线程共享的区域,绿色部分的区域是线程独有的区域,程序计数器是唯一一块没有内存泄漏的区域。具体介绍戳这里:JVM内存模型(jvm 入门篇)

有了上面的知识还得清楚一个叫常量池的东西,常量分为两种:

  • 静态常量池:所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
  • 运行时常量池:运行时常量池指的是JVM执行字节码文件时,把字节码的常量池加载到了方法区。

我们通常所说的常量池指的是运行时常量池

现在,是时候来解释前面两种创建字符串的方式了有何差异了。

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   // 字面量创建,存储在字符串常量池中
        //使用关键字new,在JVM的堆上开辟了一块空间,用来存储对象
        String school = new String("重庆邮电大学");  
    }
}
复制代码

更加形象的说明,请看下面这张图:

要说明的是,在JDK1.6之前,字符串常量池是在方法区,而在JDK1.7以及之后常量池从方法区移到了堆区,相应的规则也发生了改变。接下来,就开始详细讲解String的不可变性了(多图警告⚠️)。

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   // 字面量创建,存储在字符串常量池中
        String school = new String("重庆邮电大学");  //在JVM的堆上开辟了一块空间,用来存储对象

        name = "三少";  //相当于在常量池字符串重新创建了一个对象,原来的"Yangc"仍然存在常量池中
        System.out.println(name);   // 输出:三少
    }
}
复制代码

这就是字符串的不可变性,简单吧,清晰感人吧。都说学了不练,等于白学。现在,你需要完成下面的练习。

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String name = "Yangc";   // 字面量创建,存储在字符串常量池中
        String newName1 = "Yang" + "c";
        String newName2 = new String("Yangc");
        String newName3 = "Yangc" + "hao";
        String newName4 = name + "hao";

        System.out.println(name==newName1);  
        System.out.println(name==newName2);  
        System.out.println(newName3==newName4);   
    }
}
复制代码

输出结果如下:

现在来分别讲一讲:

  • “name==newName1” ,输出为“true”。Java的编译器会把“+”号优化,这就相当于newName1 = “Yangc”了,而neme = “Yangc”创建的字符串存在于字符串常量池中,这时候他们指向的是同一个字符串。那自然就是相等的。

  • “name==newName2” ,输出为“false”。name 指向的是字符串常量池的“Yangc”,而“newName2”指向的是堆区的“Yangc”对象,因此这两个是不同的。

  • “newName3==newName4” ,输出为“false”。前面说过,编译器会给我们优化“+”号,这时newName3 = “Yangchao”,但是我们需要注意的是,编译器不会优化变量名,所以在这里name是未知的,故newName4 = name + “hao”也是未知的。因此,输出的结果为false。

说一说intern()方法

先来看看代码,看完代码后再来解释作用:

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) { 
        String s = new String("Yangc");
        s.intern();  //在字符串常量池里建立了一个连接,指向堆空间,返回这个引用
        
    }
}
复制代码

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        String s = new String("Yangc");
        String s1 = "Yangc";
        System.out.println(s.intern() == s1);  //输出:true
    }
}
复制代码

这里调用s.intern()方法后,回去字符串常量池中寻找是否存在“Yangc”,如果存在,则返回这个字符串。这一点在JDK的版本中没有区别,但是如果字符串常量池中不存在这个字符串。那么JDK1.6以及之前都会在常量池中创建这个字符串,JDK1.7以及之后就会在常量池中创建一个引用,指向对空间的这个对象,就像上面那个图那样。

能够改变的字符串:StringBuilder

前面我们知道了,Java字符串是不可改变的,但是有时候我们有需要动态改变某个存在的字符串,这时候就用到了StringBuilder。看看下面的例子你就能明白了:

/**
 * @author Yangc
 * @Time 2020-5-23
 * @Action 字符串的不可变性
 */
public class Test {
    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder("Yang");
        System.out.println(builder);  //输出:Yang
        builder.append("c");   //调用append方法
        System.out.println(builder);  //输出:Yangc
    }
}
复制代码

在这里,我们创建了一个StringBuilder对象,这是一个可以动态增加的可变对象。更多相关的操作:StringBuilder

未来之路


距离我高中毕业已经快两年了,不由衷心感叹,时间真快啊!在去年,也就是大一结束的暑假,我就想写这么一篇总结,由于某些原因没写。后来又想大二寒假结束的时候写,但,我毕竟不是一个勤快的人,一拖再拖,直到现在。我终于肯认真地敲一下键盘,写写自己的心路历程(我知道,我可能不配,嘿嘿)。毕竟,我是一个懒散的人,不喜欢被约束,也不喜欢那些形而上的东西(所以,这两年来,我做青*大学习的次数屈指可数,对不起我们团支书了)。

过去的两年

高中毕业的那个我已经不在了,那个才走出大山,一脸青涩的少年,已经流逝在时间的长河里了。人,都会长大!

拿着稀里糊涂的成绩单,误打误撞地叩开了大学的校门,我还没有做好准备,已然成了一个大学生。小时候的我希望快快长大,长大之后又希望回到小的时候。拿到通知书的那一刻,我就知道,这一切已经成为过去。

就这样开始大一的生活。军训的时候下着雨,这应该是我过的最舒适的一个军训。我的大一是极其普通的,加了一个书法社团和一个学生会部门,成为辅导员的助理,参加校运会,拿了一个不大不小的奖。第一次参加马拉松(这算是圆了我小时候的一个小愿望),对竞技体育保持了最初的热爱。在大一下学期开始的时候,我也感到了无尽的迷茫。这时候我就开始出去兼职(现在看来这是最愚蠢的决定,然而我当还比较得意),那时候也小赚了六七K,重新换了一台电脑。后来我开始接触前端这个东西,写了一段时间,还是放弃了。

大一的暑假,我留校了,计划在学校把驾照拿到手,顺便在学校的实验室跟着学长好好学习。当我当时太腼腆了,也没学到啥东西。也是驾照这东西耽搁了我太长的时间,这也怪我倔强的性格,总喜欢把一件事情做到底,在开始另外一件事。

大二就这样开始了,没想到我也成了班学长,我得引导我的学弟做好开学的准备。辅导员那边也成了年级长(在我现在看来这就是镜中月,水中花),参与整个年级奖学金的评定,还有各种学生工作,以及大二上那令人疯狂的期末考试,整个人都炸裂了。最终,我《信号与系统》挂科了,三天时间完全不够,这个在我考完我就知道了的(挂科率40%+真不是吹的)。于是,我放弃了后面的考试,全部裸考。。。。后果可想而知,后面考的三门,只有《模拟电路》飘过,其他的全部挂了。像简单的《概率论》,也有平时没听课就能考及格的《大学物理》(其实大学物理是真的挺难的)全部都挂了。

其实,真正的改变是从我大二上结束才开始的。我也开始有了危机意识,我开始考虑大学毕业以后的职业,毕业之后自己能够去干嘛。带着这个问题,我思考了两天,最终选择移动互联网这个行业,决定开始从零学Android。一旦目标确定,我就开始了一发不可收拾地学习。先学了Java语言,跟着视频学,刷了不少大牛的博客,看了核心技术这本书,也算是系统地学了一边Java语言。然后,我开始学Android,前面有大牛带路,我感觉还是学得挺快的。于是,我开始在简书上面写笔记,当然仅仅是供我自己看的。后来知道大公司很看重计算机基础,于是我又开始学计算机网络,重拾数据结构与算法,再加上自己本来的专业课,好几次通宵达旦,经常深夜三点以后才睡。我想我高中都没这么努力,特别是高三的大课间那30分钟还要出去打篮球。 其实我现在都怀疑我是怎么考上大学的。

好了,我现在来说一说我为什么选择互联网。我想很重要的一个原因是家里穷,对于我这个从大山走出来的穷孩子,最想要知道的便是怎么赚钱,这也是我大一兼职的重要原因。一般人可能不知道,互联网这个行业,并不是其他行业所能比拟的。你能见到本科一毕业就拿十几 ,二十万的年薪吗,这在其他行业人看来简直是不可能发生的事,在互联网这个行业就很平常了(当然前提是你得好好学习,而且自学的能力非常强)。

关注当下

后来我才知道,移动互联网已经趋于平缓并开始往下走了,Android不行了。这对于我来说,显然是不甘心的,难道自己选择的路是错的。前两天我一个在字节跳动(不知道字节跳动的小伙伴可以百度一下)的直系学长也发说说,说是移动互联网确实是不行了。我的内心又开始有了一点触动,难道我要开始转行Java了吗?这两天,我的内心确实是颇不平静的,现在一大堆人一股脑涌向Java,过几年Java不流行的时候咋办?这是一个矛盾的世界,又是一个矛盾被解决的世界,在这样一个循环往复的过程中,人类进程不断向前进步。我想,我也会这个过程中,看的更加清晰。总之,现在计算机基础是非常中重要的,这是我目前最关心的(当然也关心自己专业的课程不挂科)。

未来

大学阶段,说一下大二剩下的时间。最近在准备期末考试,以及上学期的补考科目,我想这是我这一两周最重要的事情,补考或者重修的话,都太浪费时间了。(我可不想补考发生第二次)进入6月份,补考结束,也就是6月7号以后,我就可以开始准备数据结构和算法的复习(也可以理解为预习),整理计算机网络的相关知识,希望能在期末考的时候帮到我朋友。在考试结束,暑假到来之前,我得先整理目前的一些Java、Android相关知识,写一些文章出来,梳理一下自己的知识结构。

暑假能留校的话,我是肯定留校的。这时候重要的是准备大三上学期的秋招了,提前一年准备也是挺好的。我想这个暑假一定要比别人更努力才能找回我以前浪费的时间,才能在秋招上获得更多的主动权。

至于未来十年,我还没有清晰的轮廓,但我知道,既然选择了互联网这条路,这是一条冒险坎坷的路,就一定得不断学习。技术更新换代是非常快的,一旦停止了学习,就只能被淘汰。我想,我喜欢这样的生活,不被约束,崇尚自由。一杯咖啡,一台高配的MBP,一写就是一个下午。

猜你喜欢

转载自juejin.im/post/5ec7fafa5188254336106372