初始Java篇(JavaSE基础语法)(8)认识String类(下)
字符串截取、其他操作方法、字符串的不可变性、字符串的修改、StringBuffer和StringBuilder、字符串的相关刷题练习
找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程(ಥ_ಥ)-CSDN博客
所属专栏:JavaSE
接上文 初始Java篇(JavaSE基础语法)(8)认识String类(上)-CSDN博客
目录
字符串截取
从一个完整的字符串之中截取出部分内容。可用方法如下:
方法 | 功能 |
String substring(int beginIndex) | 从指定索引的位置截取到结尾 |
String substring(int beginIndex, int endIndex) | 截取部分内容(从beginIndex位置到endIndex位置) |
注意:
1. 索引从0下标开始。
2. 注意前闭后开区间的写法。substring(0, 5) 表示从0下标开始截取,一直到4下标,即[0,5) 。
示例:
其他操作方法
方法 | 功能 |
String trim() | 去掉字符串中的左右空格,保留中间空格 |
示例:
字符串的不可变性
String是一种不可变对象。字符串中的内容是不可改变。字符串不可被修改。下面就是String的源码:
这里可能会有小伙伴会说是因为value数组被 final 修饰了,因此这个数组的内容就不可变了,也就是说这个数组里面存放的字符就不可修改了。
其实不然,这个final修饰的数组,只是让这个数组名,也就是数组对象的引用不能被修改了,并不是说这个数组的内容不能被修改了。
例如:
final修饰引用类型表明该引用变量不能去引用其他对象了,但是其引用对象中的内容是可以修改的。
那既然如此,是什么不能让我们修改String的内容呢?其实是 private 修饰的整个数组。如果我们想要修改这个数组内容首先得拿到这个数组吧,但是 private 修饰就直接导致我们拿不到这个数组。因此我们就根本没有机会去修改这个数组。这也就是 String 不可修改的原因。
那可能小伙伴又有疑惑了:既然不能修改String,那前面我们学习的拆分字符串,字符串大小写转换……这些不都改变了字符串本身吗?这只是我们看到的表面现象。所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象 。这样我们每一次涉及修改字符串的操作都是创建一个新的对象。
为什么 String 要设计成不可变的?(不可变对象的好处是什么?) (目前简单了解)
1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了。
2. 不可变对象是线程安全的。
3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中. 那如果想要修改字符串中内容,该如何操作呢?
字符串修改
注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。例如:
public class Test {
public static void main(String[] args) {
String s = "hello";
System.out.println(s);
s += " world";
System.out.println(s);
}
}
那么怎样才能使效率变得更高呢?
借助StringBuffer 和 StringBuilder。下面就来介绍一下。
StringBuilder和StringBuffer
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的。
看到这里,可能有小伙伴要问了:String不是不可修改吗?怎么又说为了方便修改提供了这两个类呢?这不就前后矛盾了吗?
不不不,并不矛盾。正是因为String不可修改,所以当我们想要修改String时,做不到。那么java开发人员也想到了这种情况,因此就设计出了StringBuffer和StringBuilder这两个类,来让我们想要修改时,可以做到。做法是通过StringBuffer和StringBuilder,修改传入的字符串,再把修改后的结果转换为字符串。这就是字符串的修改方式。
这里介绍 StringBuffer常用的一些方法。
StringBuff append(String str) 在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、 double、float、int、long、Object、String、StringBuff的变量。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
sb.append(" world");
System.out.println(sb);
}
}
char charAt(int index) 获取index位置的字符。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.charAt(0));
}
}
int length() 获取字符串的长度 。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.length());
}
}
int capacity() 获取底层保存字符串空间总的大小 。
这里的底层是指用 c/c++ 保存时的大小。不必关心,
void ensureCapacity(int mininmumCapacity) 扩容。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
// 把空间扩充到原来的两倍
sb.ensureCapacity(sb.length()*2);
}
}
void setCharAt(int index, char ch) 将index位置的字符设置为ch 。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
sb.setCharAt(4, 'O');
System.out.println(sb);
}
}
int indexOf(String str) 返回str第一次出现的位置。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
// 这里要是字符串
System.out.println(sb.indexOf("o"));
}
}
int indexOf(String str, int fromIndex) 从fromIndex位置开始查找str第一次出现的位置 。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.indexOf("o", 2));
}
}
StringBuff insert(int offset, String str) 在offset位置之前插入:八种基类类型 & String类型 & Object类型数据 。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
// 在4位置之前,插入world
System.out.println(sb.insert(4, " world"));
}
}
StringBuffer deleteCharAt(int index) 删除index位置字符。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.deleteCharAt(0));
}
}
StringBuffer delete(int start, int end) 删除[start, end)区间内的字符。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
// 删除的范围是[0,4)
System.out.println(sb.delete(0, 4));
}
}
StringBuffer replace(int start, int end, String str) 将[start, end)位置的字符替换为str。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
// 把[0,4)内的字符串内容替换成空,相当于删除操作
System.out.println(sb.replace(0, 4, ""));
}
}
String substring(int start) 从start开始一直到末尾的字符以String的方式返回。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.substring(0));
}
}
String substring(int start,int end) 将[start, end)范围内的字符以String的方式返回。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
// 返回的是[0,4)之间的字符串
System.out.println(sb.substring(0, 4));
}
}
StringBuffer reverse() 反转字符串。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.reverse());
}
}
String toString() 将所有字符按照String的方式返回。
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.toString());
}
}
StringBuilder 的用法也和上面的类似。
StringBuffer 、StringBuilder 和String的区别:显而易见,前面两个是可以直接修改的,而String不行,只能通过创建出新的对象。而这个修改可变不可变主要是看是否可以在原对象上进行修改。其次,两者的方法也有些差异。
刷题练习
387.字符串中第一个唯一字符
给定一个字符串
s
,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回-1
。示例 1:
输入: s = "leetcode" 输出: 0示例 2:
输入: s = "loveleetcode" 输出: 2示例 3:
输入: s = "aabb" 输出: -1提示:
1 <= s.length <= 105
s
只包含小写字母
思路一:直接遍历取出字符串下标对应的字符,再拿这个字符去遍历整个字符串,遇到相等的字符,计数器就加1,当走完一轮之后,就开始看看计数器是否为1,如果为1,那就说明只有自己一个想同,符合,返回这个下标就行了,如果不为1,就说明还有除了自身之外,还有与其相等字符,就继续遍历,直至遍历完成。如果还没有找到就返回-1。
代码:
class Solution {
public int firstUniqChar(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
int count = 0;
for (int j = 0; j < s.length(); j++) {
if (c == s.charAt(j)) {
count++;
}
}
if (count == 1) {
return i;
}
}
return -1;
}
}
这个方法看似可行,实际上耗费的时间非常多。当唯一的字符一个都没有时,它就要遍历n*n次(n为字符串的长度) ,而如果给出的字符串长度非常长,那么这个就肯定会超时。
思路二:既然是找这个出现唯一一次的字符,那么我们就可以采用计数数组来计数。当这个字符出现时,对应的下标就加1,遍历完这个字符串后,就开始第二次遍历这个数组,其中下标对应的数组值为1,也就是这个字符只出现了一次,符合要求,直接返回就行,如果没找到就返回-1。这个方法最坏的情况就是:遍历了完完整整的两次字符串,也就是2*n。
注意:第二次去遍历这个数组时,不能按照下标的顺序来遍历,得按照字符串中每个字符的顺序来遍历。
代码:
class Solution {
public int firstUniqChar(String s) {
// 根据提示可知只会出现26个字母
int[] count = new int[26];
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
count[c-97]++; // c-'a'也是可以的,很方便我们写代码
}
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (count[c-97] == 1) {
return i;
}
}
return -1;
}
}
HJ1 字符串最后一个单词的长度
描述
计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾)
输入描述:
输入一行,代表要计算的字符串,非空,长度小于5000。
输出描述:
输出一个整数,表示输入字符串最后一个单词的长度。
示例1
输入:
hello nowcoder输出:
8说明:
最后一个单词为nowcoder,长度为8
思路一: 这个题目就是在于确定其最后一个单词的位置。我们可以通过最后一个空格来来确定。确定最后一个字符串的位置之后,就可以把这个字符串从那个位置开始截取,最后再求其长度即可。
代码:
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str = in.nextLine();
// 找到字符串中最后一个空格的位置
int x = str.lastIndexOf(" ");
// 从空格的后一个位置开始截取原字符串
str = str.substring(x+1);
// 直接输出该字符串的长度
System.out.println(str.length());
}
}
思路二:既然要找最后一个单词,那我就直接把最后一个单词放到最前面来就行了。直接把原字符串逆序,再遍历找到空格位置即可,中间的长度就是最后一个单词的长度。
但是有小伙伴可能要问了:JavaAPI中没有提供逆置字符串的方法呀,难道让我自己实现一个吗?
当然不是,java虽然没有为String提供,但是为StringBuffer提供了,并且我们刚刚也学过了。
先把String转换为StringBuffer,再用reverse方法逆置(最后再把StringBuffer转换为String即可,可以不用转换,直接求)。
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str = in.nextLine();
StringBuffer sb = new StringBuffer(str);
StringBuffer sb2 = sb.reverse();
int count = 0;
for (int i = 0; i<sb2.length(); i++) {
if (sb2.charAt(i) == ' ') {
break;
}
count++;
}
System.out.println(count);
}
}
125.验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串
s
,如果它是 回文串 ,返回true
;否则,返回false
。示例 1:
输入: s = "A man, a plan, a canal: Panama" 输出:true 解释:"amanaplanacanalpanama" 是回文串。示例 2:
输入:s = "race a car" 输出:false 解释:"raceacar" 不是回文串。示例 3:
输入:s = " " 输出:true 解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。 由于空字符串正着反着读都一样,所以是回文串。
思路: 首先,用库方法,将大写字母转换成小写字母。接着遍历来比较看二者是否一致,如果不一致就直接返回;如果一致的话,就继续直至遍历两者的下标相遇即可,返回true。
代码:
class Solution {
public boolean isPalindrome(String s) {
s = s.toLowerCase(); // 大写转小写
int i = 0;
int j = s.length()-1;
while(i < j) {
// 找到数字字母字符
while (i < j && !isNumberCharacter(s.charAt(i))) {
i++;
}
// 找到数字字母字符
while (i < j && !isNumberCharacter(s.charAt(j))) {
j--;
}
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
}
return true;
}
public boolean isNumberCharacter(char c) {
if (c >= '0' && c <= '9' || c >= 'a' && c <= 'z') {
return true;
}
return false;
}
}
好啦!本期 初始Java篇(JavaSE基础语法)(8)认识String类(下)的学习之旅就到此结束了!相信通过这两篇文章的学习,你对Java中String类的了解将会更进一步!我们下一期再一起学习吧!
更多推荐
所有评论(0)