JDK源码学习(二)---String

it2025-04-25  11

目录

一、简介

1、Java虚拟机JVM的内存块及其变量、对象内存空

2、String简介

 二、代码解析

1、类和成员变量

2、String的构造函数

3、intern()

4、trim()

5、int  compareTo(String anotherString)

6、 boolean equals

7、hashCode()

8、charAt(int index)

9、subString()

10、length与length()

 三、String常见问题

1、String直接赋值与创建对象的区别

2、String字符串的比较


一、简介

1、Java虚拟机JVM的内存块及其变量、对象内存空

 1. 栈:存放基本数据类型及对象变量的引用,对象本身不存放于栈中而是存放于堆中              1)、基础类型 byte (8位)、boolean (1位)、char (16位)、int (32位)、short (16位)、float (32位)、double (64位)、long (64位)              2)、java代码作用域中定义一个变量时,则java就在栈中为这个变量分配内存空间,当该变量退出该作用域时,java会自动释放该变量所占的空间  2. 堆:new操作符的对象              1)、new创建的对象和数组              2)、在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理  3. 静态域:static定义的静态成员变量  1. 常量池:存放常量

2、String简介

Java中String不是基本数据类型,而是一种特殊的类。String代表的是不可变的字符序列,为不可变对象,一旦被创建,就不能修改它的值,对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去。 String对象的存放位置:java中的对象大都是存放在堆中的,但是String对象是一个特例,它被存放在常量池中。 当创建一个字面量String对象时,首先会去检查常量池中这个对象的存在与否。 java本地方法:一个本地方法就是一个java调用非java代码的接口。该方法是非java实现,由C或C++语言实现。形式是: 修饰符 native 返回值类型 本地方法名(); 如public native String intern();

 二、代码解析

1、类和成员变量

// 一 属性 /** * 用来存字符串,字符串的本质: * jdk1.8之前是一个final的char型数组,且不可变: * private final char value[]; * jdk1.9带来了字符串两大改善,更小的内存空间和改善表现, * 内存改为final的byte数组,StringBuffer和StringBuild也做了一样的处理, * 这样英文字母就可以用一个字节存储,节省了内存,汉字的话意义不大 * 因为java内部使用UTF-16编码(定长),一个char为两个字节,所以英文字母一个字节用char存储就会造成空间浪费 */ @Stable private final byte[] value; /** * jdk9新增的一个编码格式的标识,使用LATIN1还是UTF-16,这个是在String生成的时候自动的, * 如果字符串中都是能用LATIN1,coder值就是0,否则就是1 表示UTF-16 * 例如: String a="a"; 断点查看 value数组只有一个即一个字节,code=0 * String h="侯"; value数组有两个即两个字节, code=1 */ private final byte coder; /** * hash是String实例化的hashcode的一个缓存。因为String经常被用于比较,比如在HashMap中。 * 如果每次进行比较都重新计算hashcode的值的话,那无疑是比较麻烦的, * 而保存一个hashcode的缓存无疑能优化这样的操作 */ private int hash; // Default to 0 /** 序列化的标识 */ private static final long serialVersionUID = -6849794470754667710L; /** 字符串压缩标识: COMPACT_STRINGS默认为true,即该特性默认是开启的 */ static final boolean COMPACT_STRINGS; //默认开启,可通过JVM参数-XX:-CompactStrings 关闭,这个时候JVM会进行再赋值处理 ,COMPACT_STRINGS 就为false了,可以debug验证 static { COMPACT_STRINGS = true; } // 新增两种编码格式的标识符 @Native static final byte LATIN1 = 0; @Native static final byte UTF16 = 1; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; // coder方法判断COMPACT_STRINGS为true的话,则返回coder值,否则返回UTF16; final byte getCoder() { return COMPACT_STRINGS ? coder : UTF16; } // 判断编码是够是isLatin1 final boolean isLatin1() { return COMPACT_STRINGS && coder == LATIN1; }

属性里面主要就是看下final内省的byte[].jdk8之前是char[],还有新增的一个coder编码标识喝两盅编码方式

2、String的构造函数

// 二 构造器 /** 空字符串构造器 */ public String() { this.value = "".value; this.coder = "".coder; //新增coder赋值 } /** 参数为一个String对象的构造器 * 将形参的value和hash以及coder赋值给实例对象作为初始化 * 相当于深拷贝了一个形参String对象 */ @HotSpotIntrinsicCandidate public String(java.lang.String original) { this.value = original.value; this.coder = original.coder; this.hash = original.hash; } /** * 通过char数组参数来构造String对象,实际将参数char数组值复制给String对象的char数组 * 不能直接赋值,因为cahr数组是可变的,会影响构造的String对象值 */ public String(char value[]) { this(value, 0, value.length, null); } /** * 截取入参数组的一部分来构造String对象,具体哪一部分由offset和count决定, * 其中做了些参数检查,传入非法参数会报数组越界异常StringIndexOutOfBoundsException */ public String(char value[], int offset, int count) { this(value, offset, count, rangeCheck(value, offset, count)); } // 返回Void占位符类型 private static Void rangeCheck(char[] value, int offset, int count) { checkBoundsOffCount(offset, count, value.length);//数组越界检查 return null; } /* * 截取char数组一部分构造对象 * Void类是一个不可实例化的占位符类,如果方法返回值是Void类型,那么该方法只能返回null类型, * 即不需要返回任何数据,只做相应处理,比如检查参数,消费参数等,这里占位符达到方法复用的目的, * 调用时不需要的可以直接传null,需要的可以是一个返回Void类型的方法,在里面做一些检查等 */ String(char[] value, int off, int len, Void sig) { if (len == 0) { this.value = "".value; this.coder = "".coder; return; } //COMPACT_STRINGS 默认为true,即会先尝试是否可以压缩,即用一个char表示 if (COMPACT_STRINGS) { //会先遍历char数组中每个char,对齐进行和0xFF比较,如果有任何一个char大于0xFF,则返回null,否则返回构造的byte数组 byte[] val = StringUTF16.compress(value, off, len); if (val != null) { this.value = val; this.coder = LATIN1; return; } } //使用 this.coder = UTF16;// UTF-16编码,直接转字节数组 //会先创建一个双倍大小的byte数组,new byte[len << 1],然后进行put数据,每个char都会put两个byte进去 // 会根据高端或者低端字节序进行 8位8位的put, this.value = StringUTF16.toBytes(value, off, len); } /** * 使用int数组构造String对象,int值会对应unicode编码的值,比如 65->A */ public String(int[] codePoints, int offset, int count) { checkBoundsOffCount(offset, count, codePoints.length); if (count == 0) { this.value = "".value; this.coder = "".coder; return; } if (COMPACT_STRINGS) { //这里会遍历int[]中每个元素,会进行 >>> 逻辑右移,如果不为0(即大于8位,大于256),则返回null byte[] val = StringLatin1.toBytes(codePoints, offset, count); if (val != null) { this.coder = LATIN1; this.value = val; return; } } this.coder = UTF16; this.value = StringUTF16.toBytes(codePoints, offset, count); } /** * 使用byte数组构造String对象并按照指定编码格式(参数是String) */ public String(byte bytes[], int offset, int length, java.lang.String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException("charsetName"); checkBoundsOffCount(offset, length, bytes.length); StringCoding.Result ret = StringCoding.decode(charsetName, bytes, offset, length); this.value = ret.value; this.coder = ret.coder; }

3、intern()

在jdk1.6中,常量池在方法区,intern()会把首次遇到的字符串实例复制到永久代中返回的也是这个永久代中字符串实例的引用。而在jdk1.7、jdk1.8中,String的intern方法不会再复制实例到常量池中,如果不存在,则这个String肯定在堆中,则将当前实例的应用复制到实例中并返回该引用,如果存在则知己返回该实例。

4、trim()

该方法用于删除字符串的首尾空白符。

5、int  compareTo(String anotherString)

按照字典顺序比较两个字符串,该比较基于字符串中各个字符的Unicode值,按字典顺序将此String对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按照字典顺序此String对象位于参数字符串之前,则比较结果为一个负整数。相等返回0,否则返回一个正整数。

6、 boolean equals

String类中的equals方法对父类中的equals方法的覆盖。

父类中的equals方法直接return (this == obj);

/** The value is used for character storage. */ private final char value[]; /** * Compares this string to the specified object. The result is {@code * true} if and only if the argument is not {@code null} and is a {@code * String} object that represents the same sequence of characters as this * object. * * @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(String) */ 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; }

可以看出来,value是一个char类型的数组,用于存放字符串中的每个字符。

如果当前对象和比较的对象是同一个对象,即返回return true。

如果当前传入的对象是String类型,则比较两个字符串的长度,即value.length的长度。

        如果长度不相同,则return false。

        如果长度相同,则按照数组value中每一位进行比较,不同返回false,每一位都相同返回true。

如果当前传入的对象不是String类型,直接返回false。

7、hashCode()

/** * Returns a hash code for this string. The hash code for a * {@code String} object is computed as * <blockquote><pre> * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] * </pre></blockquote> * using {@code int} arithmetic, where {@code s[i]} is the * <i>i</i>th character of the string, {@code n} is the length of * the string, and {@code ^} indicates exponentiation. * (The hash value of the empty string is zero.) * * @return a hash code value for this object. */ public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }

在String类中有个私有实例字段hash表示该字符串的哈希值,在第一次调用hashCode方法时,字符串的哈希值被计算并且赋值给hash字段,之后在调用hashCode方法边可以直接取hash字段返回。

为什么取31为权计算哈希值?

主要因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i。

8、charAt(int index)

/** * Returns the {@code char} value at the * specified index. An index ranges from {@code 0} to * {@code length() - 1}. The first {@code char} value of the sequence * is at index {@code 0}, the next at index {@code 1}, * and so on, as for array indexing. * * <p>If the {@code char} value specified by the index is a * <a href="Character.html#unicode">surrogate</a>, the surrogate * value is returned. * * @param index the index of the {@code char} value. * @return the {@code char} value at the specified index of this string. * The first {@code char} value is at index {@code 0}. * @exception IndexOutOfBoundsException if the {@code index} * argument is negative or not less than the length of this * string. */ public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }

返回 指定索引 处的 char值。索引范围 是从0 到length() - 1。  对于数组的索引,序列的第一个 char值 是在索引 为0,索引1,以此类推。。

9、subString()

从指定位置截取到字符串结尾str1.subString(6) 从第7个位置开始截取

截取指定范围的内容str1.subString(1,4) 从第1个位置开始截取,第4个位置停止截取,结果不包括第4个位置的内容,只有3个字符。

10、length与length()

1、字符串变量,length()用于求String字符串对象的长度,求String字符串对象中字符的个数。

2、字符串数组,length用于求String字符串数组的长度,length是求字符串数据中有多少个字符。

 三、String常见问题

1、String直接赋值与创建对象的区别

首先String s = “hello world"是赋值语句,它会先在常量池查找是否有这个值,如果有,就将这个地址赋值给s,如果没有就在常量池开一块空间给hello world 然后赋地址给s。new String(“hello world”) 也一样,会先在常量池寻找是否存在"hello world”,如果存在,直接将它赋给在堆中创建的对象。这里还涉及另外一个比较需要注意的点,也就是String s = new String(“hello world”)这句代码一共创建多少个对象,通过前面的描述我们可以知道答案是一个或者两个,当常量池中存在"hello world"是只要在堆中创建一个对象即可,当常量池中没有"hello world"时,在常量池中需要创建一个对象,在堆中也要创建一个对象。

public class Test{ public static void main(String[] args){ String s = "hello world"; String ss = "hello world"; String s1 = new String("hello world"); String s2 = new String("hello world"); System.out.println(s == ss); System.out.println(s1 == s); System.out.println(s1.equals(s)); System.out.println(s1 == s2); System.out.println(s1 == ss); } } true false true false false

在基本数据类型中==比较的是值,基本数据类型不能使用equals()进行值的比较。 在引用数据类型中,==比较的是地址值,equals()比较的是数值。

2、String字符串的比较

Java为我们提供了compareTo、“==”、equals对字符串进行比较。

最新回复(0)