【Java】【基础类】详解String类
简介
String类是干什么用的?为什么需要出现String这个类?
String类是我们日常最常用的字符串操作类了,因此String类作为Java语言的核心类,String类位于java.lang包下面,String类提供了很多操作字符串的方法,比如字符串的比较、查找、截取、大小写转换等操作,还提供了“+”连接符(字符串连接符)和对象转换为字符串的支持,也就是说字符串对象可以使用“+”连接其他对象。就像我们看到的“A”、"B"等字面值,都是String类的实例对象;例如String str="A"和String str=new String("A"),都是Stirng实例对象,只不过它们的实现方式有点不同:
前者是默认调用String.valueOf()静态方法来返回一个String对象的(valueOf方法有很多种,具体看实际):
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
而后者是直接通过构造器new出来的一个String对象(其实是先通过上面的方式创建一个字符串“A”的String实例,然后把该实例对象当作构造器参数传进去的):
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
最后会将其值(“A”)保存到String类的char数组中:
/** The value is used for character storage. */
private final char value[];
我们可以从源码角度去分析String类,获取更多的信息。
提前说一下,String类是一个不可变类,称为不可变类需要满足的条件为:
- 使用private和final修饰该类的成员变量
- 提供带参数的构造器用于对成员变量进行初始化
- 仅为该类提供getter方法,不提供setter方法,因为普通方法无法修改final修饰的成员变量
一、String类结构图
可以看出String类是一个不可变类,使用final修饰,表示该类不可以被继承,同时实现了三个接口:
- Serializable 接口是为了实现类对象的序列化,主要是能把堆内存中的对象的生命周期延长,做持久化操作。当下次再需要这个对象的时候,我们不用 new了,直接从硬盘中读取就可以了。
- Comparable 接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序 ,类的 compareTo 方法被称为它的自然比较方法 。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射表中的键或有序集合中的元素,无需指定比较器。后续会讲解 String 类中的 compareTo 方法。
- CharSequence 是一个接口,它只包括 length(), charAt(int index), subSequence(int start, int end)这几个 API 接口。除了 String 实现了 CharSequence 之外,StringBuffer 和 StringBuilder 也实现了 CharSequence 接口。CharSequence 就是字符序列,String, StringBuilder 和 StringBuffer 本质上都是通过字符数组实现的!
二、源码注释
/**
1.Stirng类表示字符串,所有Java程序中的字符串字面量,如"ABC"都是都是该类的一个实例
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
* implemented as instances of this class.
* <p>
2.Stirng字符串字面量是一个常量,它的值一旦被创建就不能被改变,StringBuffer是支持多次修改字符串的,String对象不支持修改,因此可以被共享。
* Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared.
3.String类包含了字符序列的很多方法,像比较字符串、搜索字符串、截取字符串、复制字符串,或者字符串大小写转换等等方法,遵循unicode编码标准
* The class {@code String} includes methods for examining
* individual characters of the sequence, for comparing strings, for
* searching strings, for extracting substrings, and for creating a
* copy of a string with all characters translated to uppercase or to
* lowercase. Case mapping is based on the Unicode Standard version
* specified by the {@link java.lang.Character Character} class.
* <p>
4.String支持Java连接符"+"操作,转换成其他的字符串对象,其实是通过StringBuilder或者StringBuffer类的append方法拼接实现的。最后会调用Object的toString()方法返回拼接好的String对象。
但是需要注意的是,由于使用连接符+,jvm会隐式创建StringBuiller对象来进行对象的拼接,如果频繁大量的使用连接符+,就会造成创建大量的StringBuilder对象在堆内存中,造成GC的频繁回收工作,这样很影响工作效率。所以需要注意这一点。例如:
String s = "abc";
for (int i=0; i<10000; i++) {
s += "abc";
}
这样就会很影响效率,我们可以这样做:
String s ;
StringBuilder ss=new StringBuilder(s);
for (int i=0; i<10000; i++) {
ss += "abc";
}
s=ss.toString();
三、字段属性
称为不可变类的必要条件之一就是成员属性都是私有private的,如下:
/** The value is used for character storage. */
1.String底层是有char数组构成的,为private final修饰,是不可变的,不可以被继承,通过char数组来保存数据的。数组大小在初始化就 确定了,不可变。
private final char value[];
/** Cache the hash code for the string */
2.用来保存String对象的hashCode值,默认为0。
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
3.因为String实现了Serializable接口,所以支持序列化和反序列化。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
四、构造器
String类的构造器有很多:
1.无参的
public String() {
this.value = "".value;
}
2.字符串参数
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
3.char数组参数
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
4.带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;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
5.参数为int数组并带范围偏移量参数
其中int数组需要满足Unicode编码点条件(通过codePoint >>> 16验证):
作为参数的 int 数组中值,至少需要满足“大写字母(A-Z):65 (A)~ 90(Z);小写字母(a-z):97(a) ~ 122(z);字符数字(‘0’ ~ ‘9’):48(‘0’) ~ 57(‘9’)”的条件。当数组中值为其他值时,得到的字符串结果可能为空或特殊符号。
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
6.参数为字节数组或偏移量或编码方式
Jdk1.1版本:
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
//检查字节数组偏移量相关边界问题
checkBounds(bytes, offset, length);
//根据编码方式给value赋值
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
Jdk1.6版本:
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);
}
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
7.参数为StringBuffer或StringBuilder
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());
}
五、常用方法
length() 返回字符串长度
isEmpty() 返回字符串是否为空
charAt(int index) 返回字符串中第(index+1)个字符(数组索引)
char[] toCharArray() 转化成字符数组
trim()去掉两端空格
toUpperCase()转化为大写
toLowerCase()转化为小写
boolean matches(String regex) 判断字符串是否匹配给定的regex正则表达式
boolean contains(CharSequence s) 判断字符串是否包含字符序列 s
String[] split(String regex, int limit) 按照字符 regex将字符串分成 limit 份
String[] split(String regex) 按照字符 regex 将字符串分段
1.连接函数concat
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
会新new一个String对象对原来的没有影响
2.getBytes()方法
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
这两种方法其实都是根据编码方法来解码返回字节数组
3.equals和hashCode方法
String类重写的Object类的equals方法和hashCode方法,我们先看一下Object类的equals方法和hashCode方法:
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
它的equals方法是是直接使用==来判断两个对象是否相等的(基本类型比较的它们的值,引用类型比较的是它们的引用地址),hashCode方法是本地Native方法
我们再来看一下String类重写的equals方法和hashCode方法:
public boolean equals(Object anObject) {
//1.两个字符串对象相同(即引用地址相同),则返回true
if (this == anObject) {
return true;
}
//2.长度一致且类型相同时,比较两个对象的内容是否相等
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;
}
//hashCode的计算公式为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
为什么要使用这个公式,就是在存储数据计算 hash 地址的时候,我们希望尽量减少有同样的 hash 地址。如果使用相同 hash 地址的数据过多,那么这些数据所组成的 hash 链就更长,从而降低了查询效率。
所以在选择系数的时候要选择尽量长的系数并且让乘法尽量不要溢出的系数,因为如果计算出来的 hash 地址越大,所谓的“冲突”就越少,查找起来效率也会提高。
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;
}
4.其他的方法还有很多,用到的时候再回来分析写上去
- 点赞
- 收藏
- 关注作者
评论(0)