Java中String相关总结

Java中String相关问题总结

一、String相关操作总结

一、获取字符串长度

调用String的length()方法:

public int length();

二、获取字符串的子串

1.从beginIndex位置起取出剩余字符串作为一个新的字符串返回

public String substring(int beginIndex);

2.从beginIndex位置起,取出到endIndex-1位置的字符串作为新的字符串返回

public String substring(int beginIndex, int endIndex);

三、String类型字符串首字母大写

注:不考虑首字符不是字母的情况

1、方法一:利用String类的substring()和toUpperCase方法
public static String toUpperFristChar(String string) {  
   return string.substring(0, 1).toUpperCase + string.substring(1);
}  
2、方法二:利用ASCII编码进行转换
public static String toUpperFristChar(String string) {  
    char[] charArray = string.toCharArray();  
    charArray[0] -= 32;  
    return String.valueOf(charArray);  
}  

注:toUpperCase()对应的转为小写的方法是:toLowerCase()

四.字符串的比较

1.对字符串内容按字典顺序进行大小比较,通过返回的整数值指明当前字符串与参数字符串的大小关系。
若当前对象比参数大则返回正整数,反之返回负整数,相等返回0。

public int compareTo(String string)

2.与compareTo方法相似,但忽略大小写

public int compareToIgnoreCase (String string)

五.去除两端空格

只能去除字符串首尾空格,中间的不变

String trim();

六.判断以指定字符串开始/结尾

测试此字符串是否以指定的前缀开始。

public boolean startsWith(String prefix)

测试此字符串从指定索引开始的子字符串是否以指定前缀开始。

public boolean startsWith(String prefix, int toffset)

测试此字符串是否以指定的后缀结束。

public boolean endsWith(String suffix)

七.字符串转为字符数组

将此字符串转换为一个新的字符数组。

public char[] toCharArray()

将字符从此字符串复制到目标字符数组。
要复制的第一个字符位于索引 srcBegin 处;要复制的最后一个字符位于索引 srcEnd-1 处(因此要复制的字符总数是 srcEnd-srcBegin)。要复制到 dst 子数组的字符从索引 dstBegin 处开始,并结束于索引 dstbegin + (srcEnd-srcBegin) - 1

public void getChars(int srcBegin, int srcEnd,  char[] dst,  int dstBegin)

八.其他类型转为字符串类型

以Object为例:返回 Object 参数的字符串表示形式。

public static String valueOf(Object obj)

九.字符串拼接

将指定字符串连接到此字符串的结尾

public String concat(String str)

十.判断字符串是否包含某个字符

当且仅当此字符串包含指定的 char 值序列时,返回 true。

public boolean contains(CharSequence s)

十一.判断两个字符串是否相等

将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true

public boolean equals(Object anObject)

与equals类似,但忽略大小写

public boolean equalsIgnoreCase(String anotherString)

十二.设置编码

使用给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

public byte[] getBytes()

十三.判断字符串是否为空

当且仅当 length() 为 0 时返回 true。

public boolean isEmpty()

十四.查询字符、字符串第一次出现的位置

在此对象表示的字符序列中第一次出现该字符的索引;如果未出现该字符,则返回 -1

public int indexOf(int ch)

如果字符串参数作为一个子字符串在此对象中出现,则返回第一个这种子字符串的第一个字符的索引;如果它不作为一个子字符串出现,则返回 -1

public int indexOf(String str)

十五.查询字符、字符串最后一次出现的位置

在此对象表示的字符序列中最后一次出现该字符的索引;如果未出现该字符,则返回 -1

public int lastIndexOf(int ch)

指定子字符串在此字符串中最后一次出现处的索引

public int indexOf(String str)

十六、字符串截取

1、substr(start, length): 字符串索引从零开始的,从索引为strat位截取长度为length的字符串

其中:参数length为可选,如果只有一个参数则默认从start截取到字符串末尾;
如果start为负,则表示倒数第几位开始,最后一位是-1

2、substring(fromto):从from位截取到to-1位(前闭后开区间)

其中:只有一个参数from,则从from截取到字符串末尾

二、String相关问题总结

public class StringDemo {

    public static void main(String[] args) {

        String a = "abc";
        String b = new String("abc");

        String c = "a" + new String("bc");
        String d = "a" + "bc";

        System.out.println(a == b);// 1:false
        System.out.println(a.equals(b));// 2:true

        System.out.println(a == c);// 3:false
        System.out.println(a.equals(c));// 4:true

        System.out.println(a == d);// 5:true
        System.out.println(a.equals(d));// 6:true

        StringBuffer s1 = new StringBuffer(a);
        System.out.println(s1.equals(a));// 7:false

        // System.out.println(s1==a);
        // 上句会提示报错:Incompatible operand types StringBuffer and String(不兼容的操作数类型StringBuffer和字符串)

        StringBuffer s2 = new StringBuffer("abc");
        System.out.println(s1 == s2);// 8:false
        System.out.println(s1.equals(s2));// 9:false  原因:String 源码中 equals 方法有对参数进行 instance of String 判断语句,StringBuffer 的祖先为 CharSequence,所以不相等 

        System.out.println(s1.equals("abc"));//10:false 原因:StringBuffer 没有重写 Object 的 equals 方法,所以 Object 的 equals 方法实现是 == 判断,故为 false
        System.out.println(s1.toString().equals(a));// 11:true 因为 Object 的 toString 方法返回为 String 类型,String 重写了 equals 方法实现为值比较。
        System.out.println(s2.toString() == a);// 12:false
    }
}

从上述代码中产生的面试题:
1、截取其中一段代码,判断true或false

2、String s = new String(“abc”);产生了几个对象?
这个问题其实有歧义,但是很多公司还特爱在笔试题里面考察,遇到了就答两个吧(一个是 “xyz”,一个是指向 “xyz” 的引用对象 str);之所以说有歧义是因为该语句在运行期间只创建了一个对象(堆上的 “abc” 对象),而在类加载过程中在运行时常量池中先创建了一个 “abc” 对象,运行期和类加载又是有区别的,所以这个题目的问法是有些不严谨的。因此这个问题如果换成 String str = new String(“abc”); 涉及到几个 String 对象,则答案就是 2 个了。

3、String、StringBuffer、StringBuild的区别
String 是字符串常量,StringBuffer 和 StringBuilder 都是字符串变量,后两者的字符内容可变,而前者创建后内容不可变;StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,线程安全会带来额外的系统开销,所以 StringBuilder 的效率比 StringBuffer 高;String 的每次修改操作都是在内存中重新 new 一个对象出来,而 StringBuffer、StringBuilder 则不用,且提供了一定的缓存功能,默认 16 个字节数组的大小,超过默认的数组长度时扩容为原来字节数组的长度 * 2 + 2,所以使用 StringBuffer 和 StringBuilder 时可以适当考虑下初始化大小,以便通过减少扩容次数来提高代码的高效性。

适用场景分析:
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

4、String为什么是不可变的?
String 不可变是因为在 JDK 中 String 类被声明为一个 final 类,且类内部的 value 字节数组也是 final 的,只有当字符串是不可变时字符串池才有可能实现,字符串池的实现可以在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串;如果字符串是可变的则会引起很严重的安全问题,譬如数据库的用户名密码都是以字符串的形式传入来获得数据库的连接,或者在 socket 编程中主机名和端口都是以字符串的形式传入,因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子改变字符串指向的对象的值造成安全漏洞;因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步,字符串自己便是线程安全的;因为字符串是不可变的所以在它创建的时候 hashcode 就被缓存了,不变性也保证了 hash 码的唯一性,不需要重新计算,这就使得字符串很适合作为 Map 的键,字符串的处理速度要快过其它的键对象,这就是 HashMap 中的键往往都使用字符串的原因。

5、说说 String str = “abc”; 和 String str = new String(“abc”); 的区别?
在 java 的 class 文件中有专门的部分用来存储编译期间生成的字面常量和符号引用,这部分叫做 class 文件常量池,在运行期间对应着方法区的运行时常量池,所以 String str = “hello world”; 在编译期间生成了 字面常量和符号引用,运行期间字面常量 “hello world” 被存储在运行时常量池(只保存了一份),而通过 new 关键字来生成对象是在堆区进行的,堆区进行对象生成的过程是不会去检测该对象是否已经存在的,所以通过 new 来创建的一定是不同的对象,即使字符串的内容是相同的。

6、为什么针对安全保密高的信息,char[] 比 String 更好?
因为 String 是不可变的,一旦创建就不能更改,直到垃圾收集器将它回收才能消失,即使我们修改了原先的变量,实际上也是在内存中新建一个对象,原数据还是保留在内存中等待回收;而字符数组 char[] 中的元素是可以更改的,也就是说像密码等保密信息用完之后我们可以马上修改它的值而不留痕迹,从而相对于 String 有更好的安全性。Shiro中权限认证中password属性就是使用char[]来保存密码

7、用 java 代码实现字符串的反转?
1)使用 JDK 中 StringBuffer(并发安全)或者 StringBuilder 的反转方法,这是最好的办法,不仅速度快、效率高,代码如下:

public String reverse(String str) {
    if ((null == str) || (str.length() <= 1)) {
        return str;
    }
    return new StringBuffer(str).reverse().toString();
}

2)使用递归方案实现,代码如下:

public String reverse(String str) {
    if ((null == str) || (str.length() <= 1)) {
        return str;
    }
    return reverse(str.substring(1)) + str.charAt(0);
}

3)字符串转换为数组,逆序遍历
4)利用栈先进后出的特点实现逆序

8、equals和==的区别
1)当参与==比较的两个元素中有一个是值类型的,那么就按照值类型来比较。而引用类型按照值来比较的时候使用的是它们的hashCode的返回结果。只有当参与比较的两个元素都是引用类型的,那么才按照引用类型来比较,即比较它们的hashCode的返回结果。
2)当使用equals方法进行比较的时候,实际的比较结果取决于equals方法的具体实现,在Object的默认实现中,是使用==来实现的。也就是说使用了按引用来比较的方式。不过在Integer和String等类中,重写了equals方法,而它们实现该方法的时候是按照值来进行比较的。

9、两个对象值相同 (tmp1.equals(tmp2) == true) 但却可有不同的 HashCode 值,这句话有问题吗?
不对的。两个对象 tmp1 和 tmp2 满足 tmp1.equals(tmp2) == true 时它们的 HashCode 应当相同,因为 Java 对于 eqauls 方法和 hashCode 方法的规定是:如果两个对象 equals 方法相等则它们的 hashCode 值一定要相同,如果两个对象的 hashCode 相同则它们的 equals 方法并不一定相同。
实际上我们也可以不按照要求的原则去做,但是如果违背了上述原则就会发现在使用容器时相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

10、可以直接根据 hashCode() 方法产生的值判断两个对象是否相等吗?
不能。因为 hashCode()方法的由来是根据这个对象内存储的数据及对象的一些特征来做散列并返回一个有符号的 32 位哈希值,所以 hashCode() 方法返回的是一个散列值,而对于一个散列来说不同的内容也是可能会出现相同的散列值,所以即使两个对象的 hashCode() 返回值一样也并不能代表两个对象是相等的,要判断两个对象是否相等还是需要使用 equals() 方法。不过要注意:两个对象的 hashCode() 返回值相等不能判断这两个对象是相等的,但是两个对象的 hashCode() 返回值不相等则可以判断这两个对象一定不相等(原因如上题指导原则)。

11、说说 hashCode() 的返回值和 == 的关系?
若 == 返回 true 则两边对象的 hashCode() 返回值必须相等,若 == 返回 false 则两边对象的 hashCode() 返回值有可能相等,也有可能不等;因为在 Java 中对象默认的 equals 方法实现就是 == 比较,而 Java 对于 eqauls 方法和 hashCode 方法的规定是如果两个对象 equals 方法相等则它们的 hashCode 值一定要相同,如果两个对象的 hashCode 相同则它们的 equals 方法并不一定相同,所以可得出上面结论。

12、Java 中 hashCode() 的作用是什么?
hashCode() 的作用是为了提高在散列结构存储中查找的效率,在线性表中没有作用;只有每个对象的 hash 码尽可能不同才能保证散列的存取性能,事实上 Object 类提供的默认实现确实保证每个对象的 hash 码不同(在对象的内存地址基础上经过特定算法返回一个 hash 码)。
在 Java 有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的 equals 方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了,也就是说如果集合中现在已经有 3000 个元素则第 3001 个元素加入集合时就要调用 3000 次 equals 方法,这显然会大大降低效率,于是 Java 采用了哈希表的原理,这样当集合要添加新的元素时会先调用这个元素的 hashCode 方法就一下子能定位到它应该放置的物理位置上(实际可能并不是),如果这个位置上没有元素则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了则就调用它的 equals 方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址,这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次,而 hashCode 的值对于每个对象实例来说是一个固定值。

13、Java 中为什么重写 equals(str) 方法时尽量要重写 hashCode() 方法?
这是一个检测实际项目踩坑经验的题目,因为我们经常会犯的一个很常见而又低级的错误根源在于重写 equals 方法时没有重写 hashCode 方法。自定义类重写 equals 方法是用来进行等值比较,重写 compareTo 方法是用来进行不同对象大小比较,而重写 hashCode 方法是为了将数据存入 HashSet、HashMap、Hashtable 等基于哈西表的集合类时进行高效比较。
当 equals 方法被重写时通常有必要重写 hashCode 方法来维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码,如果不这样做的话就会违反 hashCode 方法的常规约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括 HashMap、HashSet、Hashtable 等。
hashCode 方法的常规约定如下:
程序执行期间只要对象 equals 方法比较操作所用到的信息没有被修改,则对这同一个对象无论调用多次 hashCode 方法都必须返回同一个整数。
如果两个对象根据 equals 方法比较是相等的则调用这两个对象中任意一个对象的 hashCode 方法都必须产生同样的整数结果。
如果两个对象根据 equals 方法比较是不相等的,则调用这两个对象中任意一个对象的 hashCode 方法不一定要产生相同的整数结果(尽量保证不相等的对象产生截然不同的整数结果是可以提高散列表性能的)。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值