走进JDK(二)------String

时间:2022-09-14 15:12:52

本文基于java8.

基本概念:

  • Jvm 内存中 String 的表示是采用 unicode 编码
  • UTF-8 是 Unicode 的实现方式之一

一、String定义

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

String是个final类,不允许继承。并且实现了Serializable, Comparable<String>, CharSequence接口

  • java.io.Serializable

    这个序列化接口没有任何方法和域,仅用于标识序列化的语意。

  • Comparable<String>

    这个接口只有一个compareTo(T 0)接口,用于对两个实例化对象比较大小。

  • CharSequence

    这个接口是一个只读的字符序列。包括length(), charAt(int index), subSequence(int start, int end)这几个API接口,值得一提的是,StringBuffer和StringBuild也是实现了改接口。

二、主要成员变量

//String的底层是一个字符数组,并且为private final,决定了String一旦创建,无法通过方法去改变该String对象的值
private final char value[];
//hash是String实例化的hashcode的一个缓存。因为String经常被用于比较,比如在HashMap中。如果每次进行比较都重新计算hashcode的值的话,那无疑是比较麻烦的,而保存一个hashcode的缓存无疑能优化这样的操作。
private int hash;
//Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,
否则就会出现序列化版本不一致的异常,即是InvalidCastException。
private static final long serialVersionUID = -6849794470754667710L;

三、构造函数

public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//当传入char[]时,通过Arrays.copyOf()复制该数组给到Stirng的成员变量value[]中
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//传入char[],可以自定义起始位置,元素个数
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
} if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
//也可以传入byte[],并且指定起始位置,长度以及编码类型
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
//也可以传入byte[],并且指定起始位置,长度
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
} public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
} public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

总之,String提供的构造函数可以将String、char[]、byte[]、StringBuffer、StringBuilder等多种参数类型的初始化方法。但本质上,其实就是将接收到的参数传递给全局变量value[]。

四、length()、isEmpty()、charAt()

//返回当前字符串的字符数量
public int length() {
return value.length;
}
//判断字符串为空的方法就是判断字符数组的长度是否为0
public boolean isEmpty() {
return value.length == 0;
}
//根据对应的index获取char
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
//根据指定的编码格式,将String转换成byte[]
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
//得到一个操作系统默认的编码格式的字节数组
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}

五、equals()、compareTo()

1、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 = ;
//挨个字符进行比较
while (n-- != ) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

2、compareTo()

//compareTo():
//1、当当前String对象<入参,返回-1
//2、当前String对象=入参,返回0
//3、当前String对象>入参,返回1
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
//取两个String中较小的长度
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value; int k = ;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
//当出现第一个不同字符的时候,比较大小
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果在规定的长度内,两个字符串一致,则比较字符串的长度
return len1 - len2;
}

六、hashCode()、getBytes()

public int hashCode() {
//hash用于保存当前字符串的hash值
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
//为啥乘以31?主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

getBytes()的实现主要有以下几种:

//按照给定的字符编码返回对应的字节数组
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
} public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
//按照系统默认编码方式进行
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}

七、intern()

public native String intern();

作用:将该字符串人工写入到字符串常量池。

先来看一组面试当中经常会出现的,如下:

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8; System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true

在java中,给String赋值主要有两种方式:

//一、直接字面量进行赋值
String str = "Hello";
//二、通过new关键字创建一个String对象
String str = new String("Hello");

再来看一下jvm中的内存模型:

走进JDK(二)------String

先说第二种方式,由于是new一个对象,无可厚非,先在堆中开辟一块空间,然后创建一个String对象,并且在需要使用此对象的地方保存一个引用,指向堆中的位置。

而由于String又是不可改变的,自然就会想到利用缓存的概念。试想如果一段代码或是一个jvm中有一万个String str = "阿里马云";难道要在堆中保存一万个对象吗?很明显这种设计是比较low的。因此jvm对于字面量声明的方式,在堆中创建字符串,

、然后将字符串的引用存放在方法区的字符串常量池中,也就是说如果都是String str = "阿里马云";的声明方式,那么整个jvm中只会有一个对象存放在堆中,常量池中保存该对象的引用。

ok,那么上面的面试题就可以一题题来解释了

1、s1、s2都是字面量声明,是一个对象,true
2、s3则是两个字面量拼接而成,编译器会进行优化(编译器优化的目的只有一个,就是提高性能),在编译时s3就变成“Hello”了,所以s1==s3。
3、s4虽然也是拼接,但“lo”是通过new关键字创建的,在编译期无法知道它的地址,所以不能像s3一样优化。所以必须要等到运行时才能确定,必然新对象的地址和前面的不同。
4、同理,s9由两个变量拼接,编译期也不知道他们的具体位置,不会做出优化。
5、s5是new出来的,在堆中的地址肯定和s4不同。
6、s6利用intern()方法得到了s5在字符串池的引用,并不是s5本身的地址。由于它们在字符串池的引用都指向同一个“Hello”对象,自然s1==s6。

总结:

  • 字面量创建字符串会先在字符串池中找,看是否有相等的对象,没有的话就在堆中创建,把地址驻留在字符串池;有的话则直接用池中的引用,避免重复创建对象。
  • new关键字创建时,在运行时会创建一个新对象,变量所引用的都是这个新对象的地址。

其他还有类似indexOf、substring等相对简单,不补充说明了

														
		

走进JDK(二)------String的更多相关文章

  1. JDK中String类的源码分析&lpar;二&rpar;

    1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 public boolean s ...

  2. 调试过程中发现按f5无法走进jdk源码

    debug 模式 ,在fis=new FileInputStream(file); 行打断点 调试过程中发现按f5无法走进jdk源码 package com.lzl.spring.test; impo ...

  3. 走进JDK&lpar;十二&rpar;------TreeMap

    一.类定义 TreeMap的类结构: public class TreeMap<K,V> extends AbstractMap<K,V> implements Navigab ...

  4. 走进JDK&lpar;十&rpar;------HashMap

    有人说HashMap是jdk中最难的类,重要性不用多说了,敲过代码的应该都懂,那么一起啃下这个硬骨头吧!一.哈希表在了解HashMap之前,先看看啥是哈希表,首先回顾下数组以及链表数组:采用一段连续的 ...

  5. 走进JDK&lpar;一&rpar;------Object

    阅读JDK源码也是一件非常重要的事情,尤其是使用频率最高的一些类,通过源码可以清晰的清楚其内部机制. 如何阅读jdk源码(基于java8)? 首先找到本地电脑中的jdk安装路径,例如我的就是E:\jd ...

  6. 走进JDK&lpar;九&rpar;------AbstractMap

    map其实就是键值对,要想学习好map,得先从AbstractMap开始. 一.类定义.构造函数.成员变量 public abstract class AbstractMap<K,V> i ...

  7. 走进JDK&lpar;八&rpar;------AbstractSet

    说完了list,再说说colletion另外一个重要的子集set,set里不允许有重复数据,但是不是无序的.先看下set的整个架构吧: 一.类定义 public abstract class Abst ...

  8. 走进JDK&lpar;四&rpar;------InputStream、OutputStream、Reader、Writer

    InputStream InputStream是java中的输入流,下面基于java8来分析下InputStream源码 一.类定义 public abstract class InputStream ...

  9. 走进JDK&lpar;三&rpar;------AbstractStringBuilder、StringBuffer、StringBuilder

    AbstractStringBuilder是一个抽象类,StringBuffer.StringBuilder则继承AbstractStringBuilder,所以先说AbstractStringBui ...

随机推荐

  1. Monkeyrunner 常用按键

    MonkeyRunner常用的按键介绍 Home键:KEYCOD_HOME   Back键:KEYCODE_BACK  send键:KEYCODE_CALL  end键:KEYCODE_ENDCALL ...

  2. RT-Thread的线程间同步

    禁止系统调度上一节< 多线程导致的临界区问题>中由于 test1 线程被 test2 线程打断,才导致了我们没有得到预期的结果,我们一般可通过关闭中断和调度器上锁这两种简单的途径来禁止系统 ...

  3. 转--CSS选择器详解(一)常用选择器

    今天复习一下CSS的知识,看了篇文章觉得很好,转来备用. 转自:http://www.cnblogs.com/fattydoit/p/3492028.html 目录 类型选择器 类选择器 ID选择器 ...

  4. goldengate 12c对teradata的支持

    OGG12c在2014.4.24发布了对TD的最新支持版本,软件可在此下载:https://edelivery.oracle.com.新版本特性如下: 当前支持TD 14.1的捕获和交付,仍然基于Te ...

  5. UDP 打洞 原理解释

    终于找到了一份满意的UDP打洞原理解释,附上正文,自己整理了一下源码 3.3. UDP hole punching UDP打洞技术 The third technique, and the one o ...

  6. log&lowbar;bin&lowbar;trust&lowbar;function&lowbar;creators错误解决

    log_bin_trust_function_creators错误解决   当有mysql本地或远程建立function或procedure时报上面的错误  经试验是log_bin_trust_fun ...

  7. &lbrack;LeetCode&rsqb;题解(python):005-Longest Palindromic Substring

    题目来源: https://leetcode.com/problems/longest-palindromic-substring/ 题意分析: 这道题目是输入一段不超过1000的字符串,输出最长的回 ...

  8. oracle导入dmp文件的2种方法

    使用imp.impdp方式导入数据 1.使用imp导入数据 打开cmd窗口,然后直接敲入一下命令即可,需要注意的是,要事先把dmp文件放到正确的路径中去 imp yx_base/@yx_192. fi ...

  9. jenkins maven 自动远程发布到服务器,钉钉提醒团队

    jenkins 自动远程发布到服务器 1.安装jenkins 安装过程:自行百度 英文不好的,不要装最新版的jenkins.建议安装Jenkins ver. 2.138.4,此版本可以设置中文语言,设 ...

  10. 常用vi编辑器命令行

    游标控制: h 游标向左移 j 游标向下移 k 游标向上移 l(or spacebar) 游标向右移 w 向前移动一个单词 b 向后移动一个单词 e 向前移动一个单词,且游标指向单词的末尾 ( 移到当 ...