String基础
Java提供了String类来创建和操作字符串
String类有多个构造方法,根据提供不同的参数来初始化字符串
我们来看几个String的构造方法
public String() {
this.value = "".value;
}
上面这个是一个无参构造,用来初始化一个“ ”空字符串。但是字符串是不可更改的,所以这个构造就没有用了。注释中也明确说明了这个构造函数是不必要的。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
上面这个构造方法,是用来复制字符串的,只有当显式调用才会有用
比如
String a="10";
String b =new String(a);
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;
}
}
// 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);
}
上面这个函数是对于,char数组的截取,截取一段转成字符串
比如:
@Test
public void test1(){
char array[]={'a','b','c','d','e','f','g','h','i'};
String c= new String(array,0,3);
String d= new String(array,1,4);
String e= new String(array,2,5);
System.out.println(c);
System.out.println(d);
System.out.println(e);
}
我们可以知道这个构造方法的第一个参数为数组,第二个参数为数组的下表,第三个参数为截取的数量。
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
上面这个构造方法,是用于 byte数组转换成String字符串
@Test
public void test1() throws UnsupportedEncodingException{
String str="这是一个贵州姑娘";
byte[] arrs=str.getBytes("GBK");
System.out.println(arrs);
String str1 = new String(arrs,"GBK");
System.out.println(str1);
}
上面这个方法,我们先把一个字符串转成字节,再把字节转换成字符串
String类中提供了多个关于字节的操作,我们来看一看其中一个源码
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
常用的String类的方法
lenth() 返回字符串的长度
源码
public int length() {
return value.length;
}
substring() 用于截取字符串
源码:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
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);
}
只有一个参数的,截取从参数值开始截取到最后一个。
charAt() 用于获取指定位置的字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
看看字符串是放在虚拟机的哪个位置
来看看常见的String对象的声明
@Test
public void test1() throws UnsupportedEncodingException{
String str1="hellow";
String str2="hellow";
String str3=new String("hellow");
String str4=new String("hellow");
}
首先 String str1=“hellow”;这个代码是编译期间完成的,就直接存储在常量池中,
而String str3=new String(“hellow”); 是运行期间new出来的,首先去常量池中去查找“hellow”对象,如果没有先在常量池中创建一个“hellow”对象,然后在堆中载创建一个“hellow”对象的拷贝对象
有一道面试题问:String str3=new String(“hellow”);这个语句创建了几个对象,答案有一个或者两个,原因就是上面那段话阐述的意思。
常量池:是方法区的一部分,用于存放编译期间生成的各种字面量和符号引用
@Test
public void test1() throws UnsupportedEncodingException{
String str1="hellow";
String str2="hellow";
String str3=new String("hellow");
String str4=new String("hellow");
System.out.println(str1==str2);
System.out.println(str3==str4);
}
所以运行结果
str1和str2指向同一个地方,所以相等,而str3和str4指向的是堆里面的不同对象,虽然值相同,但是不是同一个对象。这就引出了下面的对象的比较了
==和euqals
通常我们的比较最常用的就是这两个了。
通常我们会说
== 是比较的地址,equals比较的是值,这个说法是比较通用的,我们只需要记住特殊情况就可以了。
==比较的就是地址,也就是对象的物理存储位置。
而equals有比较地址和值的,大多数就是比较值的
提一个比较地址的equals
下面这个方法时Object类中的equals方法,这个方法时调用==来实现的,所以是比较的地址,记住这个返利就可以。
public boolean equals(Object obj) {
return (this == obj);
}
记住上面那个反例就可以,因为重写equals基本就是比较的值的,Object类是所有的类的基类,如果重写equal还是比较地址的,那就有点多此一举了。
下面举String类中的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 = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
大家有兴趣可以去,看看别人重写equals是怎么实现的,就知道比较的是值还是地址了。
字符串的不可变性
String 的对象一旦被创建,就不能被修改,是不可变的
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
我们通过源码,知道这个String被声明成了final类型,所以String类不能被继承,以及String类中最关键的字段value数组也由final修饰,也就是常量,只允许赋值一次,并且赋值后不能修改。并且在转换成字符串的过程用的修饰符都是private类型的,是私有的。能够最大程度的保护String类的使用安全,这就是封装好处的体现。
如何个不能修改法?
@Test
public void test1() {
String str1="hellow";
str1=str1+"ord";
System.out.println(str1);
这里并不是str1不能修改,而是在变量池中的"hellow"不能被修改
这里内存模型是下图所示
str1本来指向常量池中的hellow,str1=str1+“ord”;执行了这个语句,将在常量池中新建一个字符对象helloword,然后str1指向了helloword
也就是这个常量池中的“hellow”和“hellword”都是不能被修改的,重新给str1赋值,只会在常量池中创建一个又一个的String对象。如果频繁修改的字段用了String类型,就容易造成内存的占用,浪费内存空间。由于String类的这个缺陷,所以程序员设计了其他类来解决这个情况。
String、StringBuilder和StringBuffer的区别?
String
正如上面所说的String在拼接的时候产生很多无用的中间对象,很影响性能
StringBuilder
就是为了解决大量拼接字符串产生很多无用的中间对象而提供的一个类,StringBuffer类中提供了append和add方法,可以将字符串添加在末尾或者指定位置。是一个线程不安全的对象。因为不需要考虑线程安全所以,性能好。
StringBufferr
StringBuffer和StringBulider差不多,只不过StringBufferr是线程安全的,在修改数据的方法都添加了synchronized关键字。保证了线程额安全,但是性能是低下的。
正如上面所总结的,在不经常变化的场景用String类,在单线程场景用StringBuilder类,在多线程场景用StringBuffer
本文属于自己参考文章和结合自己的总结来学习和巩固Java基础
参考文章:https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/1%E3%80%81%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%9F%BA%E7%A1%80.md