String 继承关系,核心成员变量,主要构造函数:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/**
* 1.final会被jvm缓存,提高了性能
* 2.fianl变量线程安全,节省了线程同步的开销
* 正因为是final的,所有不可变,即所有String都是新的
* 注:这个数组是不可变的,不存在容器的扩容问题
*/
private final char value[];
// 空参构造 如果只是new String()那么只会产生一个空数组
public String() {
this.value = new char[0];
}
// char[]构造,调用 Arrays.copyOf
// 注:Arrays.copyOf是拷贝后返回一个新数组,system.arrayCopy是可以对目标数组拷贝
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
// 常用于subString方法,实际是调用Arrays.copyOfRange(部分拷贝)
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// 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);
}
// 构造器有很多,这里只列举3个.....
}
private static final long serialVersionUID = -6849794470754667710L;
// 注:只有在实现Compareable时指定了泛型,这里才会是myString ====> 指定泛型是必要的
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
// 1.先逐个字符比较,若遇见不同了,即使A比B长,但B[i]>A[i],那么也是B>A
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
// 不相同,就ASCII相减
if (c1 != c2) {
return c1 - c2;
}
k++;
}
// 2.再比较长度
return len1 - len2;
}
String通过充分利用final关键字实现了不变性,所以string的绝大多数写法都是返回新的string
两个string不变性的示例:
String str ="hello world !!";
// 这种写法是替换不掉的,必须接受 replace 方法返回的参数才行,这样才行:str = str.replace("l","dd");
str.replace("l","dd");
String s ="hello";
s ="world";
从代码上来看,s 的值好像被修改了,但从 debug 的日志来看,其实是 s 的内存地址已经被修改了,也就说 s =“world” 这个看似简单的赋值,其实已经把 s 的引用指向了新的 String,debug 的截图显示内存地址已经被修改,两张截图如下:
String str = "abc";
String str1 = new String("abc");
System.out.println(str == str1); // false
System.out.println(str.equals(str1)); // true
public boolean equals(Object anObject) {
// 1.判断内存地址是否相同
if (this == anObject) {
return true;
}
// 2.待比较的对象是否是 String,如果不是 String,直接返回不相等
if (anObject instanceof String) {
// 注:instance使其父类也为true
String anotherString = (String)anObject;
int n = value.length;
// 3.两个字符串的长度是否相等,不等直接返回不相等
// 注:同一个类中可以直接调用private属性
if (n == anotherString.value.length) {
// 注:这里先用一个数组存下来value是为了下面代码的可读性
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 4.依次比较每个字符是否相等,若有一个不等,直接返回不相等
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public String substring(int beginIndex, int endIndex) {
// 左闭右开
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
// 调用构造函数
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
public String substring(int beginIndex)
public String replace(char oldChar, char newChar) {
// 字符替换,全部
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
// 1.整体遍历一遍,看有无需要被替换字符
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
// 2.1 若有,再创建一个新数组,然后替换后放入
if (i < len) {
char buf[] = new char[len];
// 注:这里是先将 i 之前的直接赋给新数组buf
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++;
}
// 重新构造一个String
return new String(buf, true);
}
}
// 2.2 若无,则直接返回本身
return this;
}
public String replaceFirst(String regex, String replacement) {
// 只替换第一次出现的字符串
// 正则表达式
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
public String replaceAll(String regex, String replacement) {
// 字符串替换,全部
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
字符串分割,返回string[]
public String[] split(String regex, int limit) // regex:分隔符 ,limit:拆分的个数
public String[] split(String regex) // return split(regex, 0);
示例:对字符串 s 进行各种拆分
String s ="boo:and:foo";
// 演示的代码和结果是:
s.split(":") // 结果:["boo","and","foo"]
s.split(":",2) // 结果:["boo","and:foo"]
s.split(":",5) // 结果:["boo","and","foo"]
s.split(":",-2) // 结果:["boo","and","foo"]
s.split("o") // 结果:["b","",":and:f"]
s.split("o",2) // 结果:["b","o:and:foo"]
字符串拼接,返回字符串
// delimiter:分隔符,elements:数据(list/array)
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// 底层是StringBuilder.append()
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
示例:
List<String> names=new ArrayList<String>();
names.add("1");
names.add("2");
names.add("3");
System.out.println(String.join("-", names)); // 1-2-3
String[] arrStr=new String[]{
"a","b","c"};
System.out.println(String.join("-", arrStr)); // a-b-c
indexOf返回index,没找到就返回-1
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
charAt返回指定索引的char
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
底层就是char数组存储的,所以直接返回char数组就行
注:这里不能直接将value返回,因为value是final不可变的,那么返回后使用者也无法操作
public char[] toCharArray() {
// 新建一个数组,拷贝过去返回
char result[] = new char[value.length];
// arraycopy(src, srcPos, dest, destPos, length)
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
在生活中,我们经常碰到这样的场景,进行二进制转化操作时,本地测试的都没有问题,到其它环境机器上时,有时会出现字符串乱码的情况,这个主要是因为在二进制转化操作时,并没有强制规定文件编码,而不同的环境默认的文件编码不一致导致的。
我们也写了一个 demo 来模仿一下字符串乱码:
String str ="nihao 你好 喬亂";
// 字符串转化成 byte 数组,转化成ISO-8859-1编码的二进制数组
// 注:ISO-8859-1编码格式不能支持所有的汉字,所以大概率乱码
byte[] bytes = str.getBytes("ISO-8859-1");
// byte 数组转化成字符串
String s2 = new String(bytes);
log.info(s2);
结果打印为:nihao ?? ??
打印的结果为??,这就是常见的乱码表现形式。是不是我把代码修改成 String s2 = new String(bytes,“ISO-8859-1”); 就可以了?这是不行的。主要是因为 ISO-8859-1 这种编码对中文的支持有限,导致中文会显示乱码。唯一的解决办法,就是在所有需要用到编码的地方,都统一使用 UTF-8,对于 String 来说,getBytes 和 new String 两个方法都会使用到编码,我们把这两处的编码替换成 UTF-8 后,打印出的结果就正常了。
转换String:new String(~~) 或 valueOf() 或 toString()
// 注:value是个静态方法,可以将基本类型和Object转换为String
public static String valueOf(Object obj) {
// 转换对象时调用的也是toString,但允许null
// 因此在做一些操作时要慎重,如Integer.valueOf(str);这是就会报错提示类型转换出错
return (obj == null) ? "null" : obj.toString(); // 当obj==null时,"null"
}
public static String valueOf(int i) {
// 先装箱成Integer再转换为String
return Integer.toString(i);
}
总结:对象 --> String :toString 基本类型 --> new String(~~)
将当前字符串(value)拷贝到另一个数组(dst)
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
// 直接调用System.arraycopy,value就是当前字符串
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
StringBuilder原理?为什么拼接效率高于String?
StringBuilder的方法实质上是调用父类AbstractStringBuilder的
abstract class AbstractStringBuilder implements Appendable, CharSequence {
// 实质上也是char[],但他不是final的==>也就是说,在数组空间够大的情况下,一个数组可以存多个字符串
char[] value;
// 记录当前容量,扩容的前提
int count;
}
StringBuilder的append方法实际上就是调用父类AbstractStringBuilder的append方法
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
// 确保容量够,不够则扩容
// 扩容机制是2*capcity+2
ensureCapacityInternal(count + len);
// 将要append的string拷贝到当前数组,0表示从要拼接的字符串第一位开始拷贝
str.getChars(0, len, value, count);
count += len;
return this;
}
接效率高的原因?
StringBuilder与StringBuffer的区别
StringBuffer线程安全,在append方法前面加上了synchronized,但相对效率就低了
public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}