java面试之String字符串

String对象的另一种初始化方式

String() --------- 初始化一个String对象,表示一个空字符序列    String s = new String();

String(String value) --------- 利用一个直接量创建一个新串  String s1 = new String(“hello”);

String(char[] value) --------- 利用一个字符数组创建  char[] c = {'h','e','l','l','o'};   String s2 = new String(c);

String(char[] value,int offset,int count) --------- 截取字符数组,从offset开始count个字符创建 'String s3 = new String(c,1,3);

String(StringBuffer buffer) --------- 利用StringBuffer创建

equals()和==的区别 

equals()比较的是对象的内容,也就是JVM堆内存中的内容,==比较的是地址,也就是栈内存中的内容
  1. public static void main(String[] args) {  
  2.           
  3.         String s = "hello world";  
  4.         String s1 = new String("hello world");  
  5.         String s2 = new String("hello world");  
  6.         String s3 = new String("hello");  
  7.         String s4 = "hello world";  
  8.           
  9.         System.out.println(s.equals(s1));;  //true
  10.         System.out.println(s1.equals(s2));  //true
  11.         System.out.println(s1.equals(s3));  //false
  12.         System.out.println("------------------");  
  13.         System.out.println(s == s1);  
  14.         System.out.println(s == s3);  
  15.         System.out.println(s == s4);  直接量的字符串会产生缓存池,所以,当声明s4的时候,编译器检测到缓存池中存在相同的字符串,所以就直接使用,只要将s4指向s所指向的字符串就行了,二者指向同一字符串,所以地址当然相等
  16.     } 

常用方法

public char charAt(int index)-----------

获取指定位置的字符

indexOf():根据给定的字符串,返回他的位置

public String[] split(String regex)-----------

该方法用于分割字符串


字符串截取substring

  1. public static void main(String[] args) {  
  2.         String s = "helloworld";  
  3.         String s1 = s.substring(2);  截取从指定位置到字符串结束的子字符串
  4.         String s2 = s.substring(27);  截取从指定位置开始,到endIndex-1位置的子字符串
  5.         String s3 = (String) s.subSequence(27);  和substrin一样
  6.         System.out.print("s1:"+s1+"\n"+"s2:"+s2+"\n"+"s3:"+s3);  
  7.     }  

输出:

s1:lloworld
s2:llowo
s3:llowo

String s = "123" + "456"内存中产生几个字符串对象?
Java字符串的缓存机制,编译器在编译的时候会进行优化,所以在编译的过程中123和456被合成了一个字符串"123456",因此,如果缓存池中目前没有123456这个对象,那么会产生一个,即""123456",且栈中产生一个引用s指向它,如果缓存池中已经存在"123456",那么将产生0个对象,直接用s指向它

如果spilt()函数的参数在要分割的字符串中没有怎么办?如String s = "helloworld" ,我现在调用String[] s2 = s.spilt("abc"),返回什么?
  1. package com.xtfggef.string;  
  2.   
  3. public class StringSpilt {  
  4.     public static void main(String[] args) {  
  5.         String s = "helloworld";  
  6.         String[] s2 = s.split("abc");  
  7.         for (int i = 0; i < s2.length; i++) {  
  8.             System.out.println(s2[i] + " " + i);  
  9.         }  
  10.     }  
  11. }  
package com.xtfggef.string;

public class StringSpilt {
	public static void main(String[] args) {
		String s = "helloworld";
		String[] s2 = s.split("abc");
		for (int i = 0; i < s2.length; i++) {
			System.out.println(s2[i] + " " + i);
		}
	}
}

输出:helloworld 0


会把它整个串放入到数组中

s1+"b"和"a"+"b"的不同

  1. String s = "ab";  
  2. String s1 = "a";  
  3. String s2 = s1 + "b";  
  4. String s3 = "ab";   
  5. System.out.println(s == s2);//false  
  6. System.out.println(s2 == s3);//false  
  7. System.out.println(s2.hashCode() == s3.hashCode());  
  8. String s4 = "ad";  
  9. String s5 = "a" + "d";  
  10. String s6 = "ad";  
  11. System.out.println(s4 == s5);//true  
  12. System.out.println(s4 == s6);//true  
  1. System.out.println(s1.hashCode());  97
  2. System.out.println(s2.hashCode());  3105
  3. System.out.println(s3.hashCode());  3105
  4. System.out.println(s4.hashCode());  3107
  5. System.out.println(s5.hashCode());  3107
                  System.out.println(s1.hashCode());
		System.out.println(s2.hashCode());
		System.out.println(s3.hashCode());
		System.out.println(s4.hashCode());
		System.out.println(s5.hashCode());

为什么s1+"b"会产生新的对象?而没有去常量池查找是否已经存在ab对象,以致于s==s2返回false。因为我们说过常量池(下文会讲常量池)是在编译期确定好的,所以如果我们的语句时String s5 = "ab"的话,这个是在编译期确定的,会去常量池查找,而此处我们的语句时s2 = s1+"b",s2的值只有在运行期才能确定,所以不会去常量池查找,也就是产生新串

s2在堆上分配,因为+的内部实现是用StringBuilder来实现的。String s2 = s1+"b" 内部是这样实现的:String s2 = new StringBuilder(s1).append("b").toString();所以是在堆上来分配的

s2.hashCode() == s3.hashCode()返回true
所以和s和s2用==比,返回false,如果用equals的话,返回肯定是true,因为equals()比较的是对象的内容(String类是这样的)。
对于Object类而言,原生的equals()方法,必须两个对象的地址和内容都一样才返回true
Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer a=new Integer(50),则a.hashCode的值就是50 

常量池、字符串常量池、运行时常量池的介绍
常量池一般就是指字符串常量池, 是用来做字符串缓存的一种机制,当我们在程序中写了形如String s = "abc"这样的语句后,JVM会在栈上为我们分配空间,存放变量s和对象”abc“,当我们再次需要abc对象时,如果我们写下:String s1 = "abc"的语句时,JVM会先去常量池中找,如果不存在,则新创建一个对象。如果存在,则直接将s1指向之前的对象”abc“,此时,如果我们用==来判断的话,返回的true。这样做的好处就是节省内存,系统响应的速度加快,(因为省去了对象的创建时间)这也是缓存系统存在的原因。常量池是针对在编译期间就确定下来的常量而言的,如上所说的String类的一些对象。但是,当类被加载后,常量池会被搬到方法区的运行时常量池,此时就不再是静态的了,那么是不是就不能向常量池中添加新的内容了呢(因为我们刚刚说过,常量池是在编译期确定好的)?答案是否定的,我们依然可以在运行时向常量池添加内容!这就是我们说过的String类有个方法叫intern(),它可以在运行时将新的常量放于常量池。

StringBuffer、StringBuilder

1、初始化

StringBuffer和StringBuilder就是所谓的可变字符串类,共四个构造方法:

StringBuffer()

public StringBuffer(int paramInt)

public StringBuffer(String paramString)

public StringBuffer(CharSequence paramCharSequence)

观察其源码发现,使用StringBuffer()时,默认开辟16个字符的长度的空间,使用public StringBuffer(int paramInt)时开辟指定大小的空间,使用public StringBuffer(String paramString)时,开辟paramString.length+16大小的空间。都是调用父类的构造器super()来开辟内存。这方面StringBuffer和StringBuilder都一样,且都实现AbstractStringBuilder类

2.区别

二者几乎没什么区别,基本都是在调用父类的各个方法,一个重要的区别就是StringBuffer是线程安全的,内部的大多数方法前面都有关键字synchronized,这样就会有一定的性能消耗,StringBuilder是非线程安全的,所以效率要高些。

  1. public static void main(String[] args) throws Exception {  
  2.         String string = "0";  
  3.         int n = 10000;  
  4.         long begin = System.currentTimeMillis();  
  5.         for (int i = 1; i < n; i++) {  
  6.             string += i;  
  7.         }  
  8.         long end = System.currentTimeMillis();  

-----------------------public synchronized int length()--------------------------

-------------------------public synchronized int capacity()---------------------------

二者都是获取字符串的长度,length()获取的是当前字符串的长度,capacity()获取的是当前缓冲区的大小。举个简单的例子:

  1. StringBuffer sb = new StringBuffer();  
  2.         System.out.println(sb.length());;  
  3.         System.out.println(sb.capacity());  
StringBuffer sb = new StringBuffer();
		System.out.println(sb.length());;
		System.out.println(sb.capacity());
输出:

0

16

  1. StringBuffer sb = new StringBuffer("hello");  
  2.         System.out.println(sb.length());;  
  3.         System.out.println(sb.capacity());  
StringBuffer sb = new StringBuffer("hello");
		System.out.println(sb.length());;
		System.out.println(sb.capacity());
输出:

5

21

因为默认分配16个字符大小的空间,所以不难解释上面的结果。


  1.         long between = end - begin;  
  2.         System.out.println("使用String类耗时:" + between+"ms");  
  3.   
  4.         int n1 = 10000;  
  5.         StringBuffer sb = new StringBuffer("0");  
  6.         long begin1 = System.currentTimeMillis();  
  7.         for (int j = 1; j < n1; j++) {  
  8.             sb.append(j);  
  9.         }  
  10.         long end1 = System.currentTimeMillis();  
  11.         long between1 = end1 - begin1;  
  12.         System.out.println("使用StringBuffer类耗时:" + between1+"ms");  
  13.   
  14.         int n2 = 10000;  
  15.         StringBuilder sb2 = new StringBuilder("0");  
  16.         long begin2 = System.currentTimeMillis();  
  17.         for (int k = 1; k < n2; k++) {  
  18.             sb2.append(k);  
  19.         }  
  20.         long end2 = System.currentTimeMillis();  
  21.         long between2 = end2 - begin2;  
  22.         System.out.println("使用StringBuilder类耗时:" + between2+"ms");  
  23.     }  
public static void main(String[] args) throws Exception {
		String string = "0";
		int n = 10000;
		long begin = System.currentTimeMillis();
		for (int i = 1; i < n; i++) {
			string += i;
		}
		long end = System.currentTimeMillis();
		long between = end - begin;
		System.out.println("使用String类耗时:" + between+"ms");

		int n1 = 10000;
		StringBuffer sb = new StringBuffer("0");
		long begin1 = System.currentTimeMillis();
		for (int j = 1; j < n1; j++) {
			sb.append(j);
		}
		long end1 = System.currentTimeMillis();
		long between1 = end1 - begin1;
		System.out.println("使用StringBuffer类耗时:" + between1+"ms");

		int n2 = 10000;
		StringBuilder sb2 = new StringBuilder("0");
		long begin2 = System.currentTimeMillis();
		for (int k = 1; k < n2; k++) {
			sb2.append(k);
		}
		long end2 = System.currentTimeMillis();
		long between2 = end2 - begin2;
		System.out.println("使用StringBuilder类耗时:" + between2+"ms");
	}

输出:

使用String类耗时:982ms
使用StringBuffer类耗时:2ms
使用StringBuilder类耗时:1ms

虽然这个数字每次执行都不一样,而且每个机子的情况也不一样,但是有几点是确定的,String类消耗的明显比另外两个多得多。还有一点就是,StringBuffer要比StringBuilder消耗的多,尽管相差不明显

------------------public boolean equals(Object paramObject)---------------------

  1. StringBuffer sb = new StringBuffer("hello");  
  2.         StringBuffer sb2 = new StringBuffer("hello");  
  3.         System.out.println(sb.equals(sb2));  //false
究其原因,String类重写了Object的equals(),所以只需要看内容是否相等即可,但是StringBuffer没有重写equals(),此处的equals()仍然是调用的Object类的,所以,调用StringBuffer类的equals(),只有地址和内容都相等的字符串,结果才会返回true。

另外StringBuffer有一系列追加、插入、删除字符串的方法,首先append(),就是在原来的字符串后面直接追加一个新的串,和String类相比有明显的好处:

String类在追加的时候,源字符串不变(这就是为什么说String是不可变的字符串类型),和新串连接后,重新开辟一个内存。这样就会造成每次连接一个新串后,都会让之前的串报废,因此也造成了不可避免的内存泄露。


  1. StringBuffer sb = new StringBuffer("helloworld, ");  
  2. sb.append("I'm ").append("erqing ").append("who ").append("are you ?");  
  3. System.out.println(sb);  
  4. //public synchronized StringBuffer insert(int paramInt, Object paramObject)  
  5. sb.insert(12/*9*/"nice! ");  
  6. System.out.println(sb);  
  7. //public synchronized StringBuffer reverse()  
  8. sb.reverse();  
  9. System.out.println(sb);  
  10. sb.reverse();  
  11. System.out.println(sb);  
  12. //public synchronized StringBuffer delete(int paramInt1, int paramInt2)  
  13. //public synchronized StringBuffer deleteCharAt(int paramInt)  
  14. sb.delete(1218);  
  15. System.out.println(sb);  
  16. sb.deleteCharAt(5);  
  17. System.out.println(sb);  
               //append()
		StringBuffer sb = new StringBuffer("helloworld, ");
		sb.append("I'm ").append("erqing ").append("who ").append("are you ?");
		System.out.println(sb);
		//public synchronized StringBuffer insert(int paramInt, Object paramObject)
		sb.insert(12, /*9*/"nice! ");
		System.out.println(sb);
		//public synchronized StringBuffer reverse()
		sb.reverse();
		System.out.println(sb);
		sb.reverse();
		System.out.println(sb);
		//public synchronized StringBuffer delete(int paramInt1, int paramInt2)
		//public synchronized StringBuffer deleteCharAt(int paramInt)
		sb.delete(12, 18);
		System.out.println(sb);
		sb.deleteCharAt(5);
		System.out.println(sb);
输出:

helloworld, I'm erqing who are you ?
helloworld, nice! I'm erqing who are you ?
? uoy era ohw gniqre m'I !ecin ,dlrowolleh
helloworld, nice! I'm erqing who are you ?
helloworld, I'm erqing who are you ?
helloorld, I'm erqing who are you ?

-----------------public synchronized void trimToSize()---------------------

该方法用于将多余的缓冲区空间释放出来。

  1.               StringBuffer sb = new StringBuffer("hello erqing");  
  2. System.out.println("length:"+sb.length());  
  3. System.out.println("capacity:"+sb.capacity());  
  4. sb.trimToSize();  
  5. System.out.println("trimTosize:"+sb.capacity());  
                StringBuffer sb = new StringBuffer("hello erqing");
		System.out.println("length:"+sb.length());
		System.out.println("capacity:"+sb.capacity());
		sb.trimToSize();
		System.out.println("trimTosize:"+sb.capacity());
输出:

length:12
capacity:28
trimTosize:12

StringBuffer类还有很多方法,关于字符查找,截取,替换方面的方法,有兴趣的童鞋可以去研究研究源码,定会学到不少知识!








  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值