Java的String
1.简介
学习一下 java.lang.String类的方法和源码
2.作用和特性
只有一个父类就是Object 所有Java类都是继承于Object类
实现了三个接口:Serizable 序列化接口,CharSequence(char的可读序列),Comparable<T>排序接口
String 代表字符串,在创建后不可变。
被final修饰 俗称的断子绝孙类 >_< 不能被继承
本质是字符数组:
String str="abc";
相当于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
2.1 成员变量
见图知意:注意都是final修饰的
value[] :
用来存储组成字符串的字符数组
使用了private,也没有提供setter方法,所以在String类的外部不能修改value,
同时value也使用了final进行修饰,那么在String类的内部也不能修改value
hash:
存储的是String的hash值 就是散列码 具体算法下面hashcode方法
PS:
final在修饰引用类型变量时,表示该引用在构造对象之后不能指向其他的对象
但该引用指向的对象的状态可以改变
关键是因为SUN公司的工程师,在后面所有String的方法里都很小心的没有去动字符数组里的元素。
所以String类不可变的关键都在底层的实现,而不仅仅是一个final
3.构造方法(弃用的构造方法不赘述了)
1.String() 初始化新创建的 String对象,使其表示空字符序列。
2.String(byte[] bytes,Charset charset):根据指定的字节的数组解码charset创建一个新的String 。
3.String(byte[] bytes, int offset, int length):和上面一样不过指定了字节数组的长度和起始位置
4.String(byte[] bytes, int offset, int length, String charsetName)
5.String(byte[] bytes, String charsetName) :见参数知意
6.String(char[] value):见参数知意
7.String(char[] value, int offset, int count) :见参数知意
8.String(int[] codePoints, int offset, int count) :分配一个新的 String ,其中包含 Unicode code point数组参数的子阵列中的 字符 。
9.String(String original) :根据指定的字符串 初始化一个字符串对象 新的字符串的hash值和value字符数粗的值是一样的
10.String(StringBuffer buffer) : 根据StringBuffer生成String
11.String(StringBuilder builder) : 根据StringBuilder 生成String 区别是StringBuffer的 length方法是线程安全的
3.1 涉及到String的基本原理的方法:arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
java.lang.System 类 (这个类不能被实例化就是说不能new 他的构造方式是private)下得到方法:
arraycopy(Object src, int srcPos, Object dest, int destPos, int length) :
将指定源数组中的数组从指定位置复制到目标数组的指定位置。
参数:
src:要被拷贝的数组对象 srcPos:要被拷贝数组的起始位置 dest:新的数组 destPos:新的数组存储的起始位置 length:要拷贝的数组长
举了例子:
public static void main(String[] args) {
int[] a = new int[]{1, 4, 5, 6};
int[] b = new int[4];
System.out.println( Arrays.toString( a ) );
System.out.println( Arrays.toString( b ) );
/**
* 从数组a的第2个元素开始拷贝到b 从b的第二个元素开始写入 长度为3
* 注意:1 这是指 数组的下标索引 而数组都是从0开始的
*/
System.arraycopy( a,1,b,1,3 );
System.out.println( "——————————数组拷贝后输出————————————");
System.out.println( Arrays.toString( a ) );
System.out.println( Arrays.toString( b ) );
}
输出结果:
[1, 4, 5, 6]
[0, 0, 0, 0]
——————————数组拷贝后输出————————————
[1, 4, 5, 6]
[0, 4, 5, 6]
4.常用方法
4.1 charAt(int index):
返回指定索引处的字符值 记住String本质是字符数组构建的 所以索引下标都是从0开始
4.2 compareTo(String anotherString) :
按照字典序与指定的字符串比较 返回结果:相等-0 参数在String之前返回>0 反之小于0
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; //获取指定字符串对应的字符数组
int k = 0;
while (k < lim) { //从0开始 相同索引处的字符开始比较 如果都是相等的一直到到 最小长度
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2; //返回 比较结果
}
k++;
}
return len1 - len2; //如果没个字符都相等 返回长度比较值 还是相等 那么就是相等
}
PS:这个方法大小写敏感 compareToIgnoreCase(String str) :这个方法忽略大小写比较
4.3 concat(String str) :
把指定的字符串 连接到字符串的末尾 字符串四大拼接方式之一
源码:
public String concat(String str) {
int otherLen = str.length(); //获取指定字符串的长度属性
if (otherLen == 0) { //如果长度为0 返回当前这个字符串即可
return this;
}
int len = value.length; //获取当前字符串的长度属性
//字符数组拷贝一个新的数组 注意这就是字符串不可变的根本原因 不是简单的final修饰一句话概括
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true); //构造一个新的字符串返回
}
4.4 length() :返回此字符串的长度。
很简单直白就是返回字符数组的长度
4.4 equals(Object object) :比较对象是否相等。
4.5 hashCode():、
数学公式:val[0]31^(n-1) + val[1]31^(n-2) + ... + val[n-1] n是数组长度
具体系数为什么选择31 粗浅的理解: 31是一个素数 31可以表示二进制位 11111-1 只占用5个字节 散列性比较好 而且不容易造成溢出 丢失精度 据说很多虚拟机都是31做了优化
4.5substring(int beginIndex, int endIndex)
原理:
JDK6和JDK7之后这个方法的区别
JDK6 是在堆空间创建新的String对象 但是不创建新的数组
数组还是采用原来的数组 和原来String对象区别是String的成员变量 offset字符数组开始下标和count数组长度改了
但是原来的数组还在 问题因为这个非常长的字符数组一直在被引用,所以无法被回收,就可能导致内存泄露
解决方法方法 substring(int beginIndex, int endIndex)+“” 这样就能创建一个新的数组
JDK7 以后直接新建一个数组 让原来的数组不被引用 让垃圾回收清理掉
4.6 replace(),replaceAll()、replaceFist()的原理和区别
replaceFirst(): 正则匹配 只替换匹配到的第一个 用StringBuffer做的替换
replaceAll() 正则匹配替换 全部 用StringBuffer做的替换
replace()方法原理:![replace源码]
4.7 String 对“+”的重载的两种情况:
1.String s1 = "yves"; String s2 = s1 + "he";等于 String s1 = "yves";
String s2 = (new StringBuilder(String.valueOf(s1))).append("he").toString();
2.String s1 = "yves" + "he"; 这种JDK 会自动优化处理 “yveshe”
字符串拼接的四种方式:
plus 所谓"+"号 拼接: 效率低下 每次拼接都会创建一个 StringBuilder对象 多次拼接会创建大量对象
concat拼接 :concat其实就是声明一个char类型的buf数组,将需要拼接的字符串都放在这个数组里,最后再转换
成String对象 本质还是数组的拷贝 concat是要多少扩容多少 扩容效率低下
StringBuilder/StringBuffer append方法: 调用父类AbstractStringBuilder的append方法,
而 StringBuffer是的append方法加了sychronized关键字,因此是线程安全的
本质还是数组的拷贝 和 concat区别是数组的扩容 这两个是指数级扩容
效率:StringBuilder>StringBuffer >concat>plus
StringBuilder线程不安全 没加锁 StringBuffer线程安全加了锁 concat扩容机制低下 plus需要创建对象
需要扩容的的时候直接扩大2倍+2 MAX_ARRAY_SIZE定义是 Integer.MAX_VALUE - 8 应该是21亿多(2^31 -1 -8)
4.8 特殊的方法:String intern()
这是一个原生的方法:
简单来说就是 如果字符串常量池中有新建的字符串 调用这个方法会直接去找一次字符常量池 如果有直接指向常量池的地址 没有的话就新建一个String对象
public static void main(String[] args) {
String str1="Java";
String str2 = new String( "Java" );
System.out.println("Str1==Str2:"+(str1==str2));
String str3 = new String( "Java" ).intern();
System.out.println("Str1==Str3:"+(str1==str3));
}
结果:
Str1==Str2:false
Str1==Str3:true