0
点赞
收藏
分享

微信扫一扫

《JavaSE-第十二章》之String


博客主页:​​KC老衲爱尼姑的博客主页​​

​​博主的github,平常所写代码皆在于此​​

​​刷题求职神器​​

共勉:talk is cheap, show me the code

作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!

刷题求职神器

在下给诸位推荐一款巨好用的刷题求职神器,如果还有小伙伴没有注册该网站,可以点击下方链接直接注册,注册完后就可以立即刷题了。

《JavaSE-第十二章》之String_jvm

传送门:​​牛客网​​

文章目录

  • ​​1.String概述​​
  • ​​2.String类常用的构造方法​​
  • ​​3.字符串方法​​
  • ​​4.什么是池?​​
  • ​​4.1字符串常量池​​
  • ​​4.2再谈String对象创建​​
  • ​​4.3intern方法​​
  • ​​5.字符串的不可变性​​
  • ​​6字符串修改​​
  • ​​7.StringBuilder和StringBuffffe​​
  • ​​8.面试题​​

1.String概述

String是Java中的引用类型,位于java.lang下,该类所定义的变量可用于指向字符串对象,然后来操作该字符串。

String既然是一个类,那么可以从该类的属性以及构造方法出发,去认识该类。

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {

通过源码可知,String实现了三个接口,首先java.io.Serializable是一个空接口,作用就是标识该类,说明此类可以被序列化,Comparable接口是用于比较大小的接口,最后一个CharSequence接口,该接口是char值的可读序列, 该接口为其实现类提供统一的,只读访问许多不同类型的char序列。

2.String类常用的构造方法

String类提供了许多的构造方法,但是最常用有以下几种。

@Test
public void testString(){
//使用常量串构造
String s = "hello";
System.out.println(s);
//newString对象
String s2 = new String("world");
System.out.println(s2);
//使用字符数组进行构造
char [] arr ={'a','b','c'};
String s3 = new String(arr);
System.out.println(s3);
}

通过源码可以看到String的底层是一个被private以及final修饰的字符数组

private final char value[];

private int hash; // Default to 0

通过调试也能证明底层确实是一个数组,只是它的组成部分还有hash。

1.直接使用常量串构造详解

《JavaSE-第十二章》之String_servlet_02

2.newString详解

《JavaSE-第十二章》之String_servlet_03

3.使用字符数组进行构造详解

《JavaSE-第十二章》之String_字符串常量池_04

当传入字符数组时 ,底层会拷贝一份字符数组并将拷贝后数组的引用给字符串对象的value。

传入字符数组时String的构造方法

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

3.字符串方法

String对象的比较

字符串的对象的比较分为以下4中

1.== 比较是否引用同一个对象

@Test
public void testString2(){
String s1 = new String("hmr");
String s2 = new String("hmr");
String s3 = new String("yzq");
String s4 = s1;
System.out.println(s1==s3);//fasle
System.out.println(s1 == s2);//false
System.out.println(s4==s1);//true
}

2.boolean equals(Object anObject) 方法:按照字典序比较

String 重写了Object中的equals方法,因为Object中的equals方法默认按照==来比较,String类重写后会按照字典序来比较

String重写后equals

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

示例

@Test
public void testString3(){
String s1 = new String("hmr");
String s2 = new String("hmr");
String s3 = new String("yzq");
String s4 = s1;
System.out.println(s1.equals(s3));//false
System.out.println(s1 .equals(s2));//true
System.out.println(s4.equals(s1));//true
}

3.int compareTo(String s) 方法 按照字典序进行比较

与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:

  1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
  2. 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值

示例

@Test
public void testString4(){
String s1 = new String("hmr");
String s2 = new String("hmr");
String s3 = new String("yzq");
String s4 = s1;
System.out.println(s1.compareTo(s2));//0
System.out.println(s2 .compareTo(s3));//-17
System.out.println(s4.compareTo(s1));//0
}

4.int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较。

示例

@Test
public void testString5(){
String s1 = new String("HMR");
String s2 = new String("hmr");
String s3 = new String("YZQ");
String s5 = new String("yzq");
String s4 = s1;
System.out.println(s1.compareToIgnoreCase(s2));//0
System.out.println(s2 .compareToIgnoreCase(s3));//-17
System.out.println(s4.compareToIgnoreCase(s1));//0
System.out.println(s5.compareToIgnoreCase(s3));//0
}

4.什么是池?

由于我们经常对这些字符串常量(常用资源)进行操作,而每次使用时都会开辟相应的内存,为了是程序运行的速度加快,就以空间来换时间,即事先将要频繁使用的资源放入空间中,当我们需要操作时直接从空间来拿使用就行了,这个空间就是池。这就好比张三家里没有冰箱,那么想吃冰棒得去小卖部,张三每天都出去觉得太麻烦了,于是自己买了个冰箱,在向冰箱里屯了许多冰棒,以后向吃就可以随时吃,就节约了大量的时间。

4.1字符串常量池

字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(数组+链表(val为字符串对象))。不同版本的jdk下的字符串常量池的位置和大小的是不同的再次讨论的是jdk8下的字符串常量池,jdk8中该池位于堆内存中,池的大小可以设置,其最小值是1009。

4.2再谈String对象创建

1.直接使用字符串常量进行赋值

@Test
public void testString6(){
String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2);//true
}

2.通过new创建String类对象

@Test
public void testString6(){
String s3 = new String("hello");
String s4 =new String ("hello");
System.out.println(s3==s4);//false
}

《JavaSE-第十二章》之String_servlet_05

当直接使用字符串常量进行赋值时,在加载字节码文件时,“hello”在字符串中已经创建好并保存在字符串常量池中,当代码走到String s1 = “hello”;创建对象时,会优先在字符串常量池中查找是否有该字符串,当找到了该字符串则将该字符串的引用赋值给s1,如果没有则创建新的字符串对象并入池。通过new创建的字符串类对象,首先会在堆内存开辟一个String对象,然后向字符串常量池中查找该字符是否存在,如若存在则将字符数组的引用赋值给字符串对象的value,反之则直接创建新的字符串对象。

通过上述,可以得出new的对象是唯一,并且使用常量串创建的String类型对象的效率更高,更节约空间。

4.3intern方法

intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。

代码示例

@Test
public void testString7(){
char [] arr = new char[]{'a','b','c'};
String s1 = new String(arr);
String s2 = "abc";
System.out.println(s1==s2);//false
}

@Test
public void testString7(){
char [] arr = new char[]{'a','b','c'};
String s1 = new String(arr);
s1.intern();
String s2 = "abc";
System.out.println(s1==s2);//true
}

当s1调用ntern()方法后,会将s1对象放入到字符串常量池,故s1==s2。

5.字符串的不可变性

String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:

  1. String类在设计时就是不可变的,
  2. String类中的字符实际是在内部的value字符数组中,通过源码可知String是被final修饰,不能被继承,同时把value被final以及private修饰表明value本身的值是不能修改的,也是就是不能引用其它数组,但是对于一个数组是可以通过下标访问修改其数组对应的值,而此时在String类外压根拿不到value故字符串不可变。
  3. 所以涉及到可能修饰字符串内容的操作都是创建一个新的对象,改变的新的对象,源码如下。

public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */

while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}

6字符串修改

错误的使用String进行拼接

public class Test {
public static void main(String[] args) {
String s = "";
for (int i = 0;i<10_000;i++) {
s+= i;
}
System.out.println(s);
}
}

使用String进行字符串拼接效率极其低下,之所以速度慢,可以通过查看Test的汇编代码来究其本质

《JavaSE-第十二章》之String_servlet_06

通过汇编得知每次进行字符串拼接时都会new一个StringBuilder对象,这也意味着会程序的运行速度是非常低下的,因此尽量不使用String直接拼接字符串,可以使用StringBuilder或者StringBuffer。

7.StringBuilder和StringBuffffe

由于String类型的字符串不可更改,为了可以高效的进行字符串修改,Java提供了StringBuffer和StringBuilder类,这两个类大同小异,这里就介绍几个常用的API更多的方法,更多需求自行查看 StringBuildre在线文档。

1.使用StringBuilder进行拼接字符串

@Test
public void testString9(){
long start = System.currentTimeMillis();
StringBuffer s = new StringBuffer("");
for (int i = 0;i<10_0000;i++) {
s.append(i);
}
long end = System.currentTimeMillis();
System.out.println(end-start);//18
}

@Test
public void testString8(){
long start = System.currentTimeMillis();
String s = "";
for (int i = 0;i<10_0000;i++) {
s+= i;
}
long end = System.currentTimeMillis();//32139
System.out.println(end-start);
}

从上述代码的运行时间上来看,StringBulder比String速度快了n倍。

2.反转一个字符串

@Test
public void testStringBuilder(){
StringBuilder s= new StringBuilder("hello world");
System.out.println(s.reverse().toString());
}

注意:String和StringBuilder类不能直接转换。如果需要转换可以采用一下方式:

  1. String转StringBuilder:利用StringBuilder的构造方法或者append()方法。
  2. StringBuilder变成Sring:调用toString()方法。

8.面试题

1,String、StringBuffffer、StringBuilder的区别

  • String的内容不可修改,StringBuffffer与StringBuilder的内容可以修改。
  • StringBuffffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操
    作。
  1. 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】

1.String str = “hello”;

只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象(1个)

2.String str = new String(“hello”)

会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟

的String对象赋值。(2个)

3.String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})

先在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到

String对象中(三个)




举报

相关推荐

0 条评论