赞
踩
在C语言中已经涉及到字符串了,但是在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想,而字符串应用又非常广泛,因此Java语言专门提供了String类。
String类提供的构造方式非常多,常用的就以下三种:
public static void main(String[] args) {
//使用常量串构造
String s1 = "hello world";
System.out.println(s1);
//直接new String 对象
String s2 = new String("hello world");
System.out.println(s2);
//使用字符数组进行构造
char[] array = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
String s3 = new String(array);
System.out.println(s3);
}
其他方法需要用到时,大家参考Java在线文档:String文档
【注意】
public static void main(String[] args) {
//s1和s2引用的不是同一对象
String s1 = new String("hello");
String s2 = new String("world");
//s1和s3引用的是同一对象
String s3 = s1;
System.out.println(s1.length());//获取字符串长度 -> 输出5
System.out.println(s1.isEmpty());//如果字符串长度为0,返回true,否则返回false
}
public static void main(String[] args) {
//打印"hello"字符串(String对象)的长度
System.out.println("hello".length());
}
字符串的比较是常见操作之一,比如:字符串排序。Java中总共提供了4中方式:
public class Test { public static void main(String[] args) { int a = 10; int b = 20; int c = 10; //对于基本数据类型,==比较的是两个变量中存储的值是否相同 System.out.println(a == b);//false System.out.println(a == c);//true //对于引用类型变量,==比较两个引用变量引用的时候为同一对象 String s1 = new String("hello"); String s2 = new String("hello"); String s3 = new String("world"); String s4 = s1; System.out.println(s1 == s2);//false System.out.println(s2 == s3);//false System.out.println(s1 == s4);//true } }
字典序:字符大小的顺序
String类重写了父类Object中equals方法,Object中equals默认按照==比较,String重写equals方法后,按照如下规则进行比较,比如: s1.equals(s2)
//库方法 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; }
//Test.java public static void main(String[] args) { String s1 = new String("hello"); String s2 = new String("hello"); String s3 = new String("Hello"); //s1、s2、s3引用的是三个不同的对象,因此==比较的结果全部是false System.out.println(s1 == s2);//false System.out.println(s1 == s3);//false /** * equals比较:String对象中逐个字符 * 虽然s1和s2引用的不是同一个对象,但是两个对象中放置的内容相同,因此输出的是true * s1和s3引用的不是同一个对象,而且两个对象中的内容也不同,因此输出false */ System.out.println(s1.equals(s2));//true System.out.println(s1.equals(s3));//false }
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型,具体比较方式:
public class Test {
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("abc");
String s4 = new String("abcs");
System.out.println(s1.compareTo(s2));//不同输出字符值为-1
System.out.println(s1.compareTo(s3));//相同输出0
System.out.println(s1.compareTo(s4));//前k个字符完全相同,输出长度差值-1
}
}
public class Test {
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("ABc");//忽略大小写 -> abc
String s4 = new String("abcs");
System.out.println(s1.compareToIgnoreCase(s2));//不同输出字符差值为-1
System.out.println(s1.compareToIgnoreCase(s3));//相同输出0
System.out.println(s1.compareToIgnoreCase(s4));//前k个字符完全相同,输出长度差值-1
}
}
字符串查找也是字符串中非常常见的操作,String类提供的常用查找的方法:
方法 | 功能 |
---|---|
char charAt(int index) | 返回index位置上字符,如果index为负数或者越界,抛出IndexOutOfBoundsException异常 |
int indexOf(int ch) | 返回ch第一次出现的位置,没有返回-1 |
int indexOf(int ch, int fromIndex) | 从fromIndex位置开始找ch第一次出现的位置,没有返回-1 |
int indexOf(String str) | 返回str第一次出现的位置,没有返回-1 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始找str第一次出现的位置,没有返回-1 |
int lastIndexOf(int ch) | 从后往前找,返回ch第一次出现的位置,没有返回-1 |
int lastIndexOf(int ch, int fromIndex) | 从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返回-1 |
int lastIndexOf(String str) | 从后往前找,返回str第一次出现的位置,没有返回-1 |
int lastIndexOf(String str, intfromIndex) | 从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返回-1 |
public class Test { public static void main(String[] args) { String s = "ababcabcd"; //char charAt(int index): System.out.println(s.charAt(3));//b //int indexOf(int ch): System.out.println(s.indexOf('c'));//4 //int indexOf(int ch, int fromIndex): System.out.println(s.indexOf('c', 3));//4 //int indexOf(String str): System.out.println(s.indexOf("abc"));//2 //int indexOf(String str, int fromIndex): System.out.println(s.indexOf("abc", 3));//5 //int lastIndexOf(int ch): System.out.println(s.lastIndexOf('c'));//7 //int lastIndexOf(int ch, int fromIndex): System.out.println(s.lastIndexOf('c', 3));//-1 //int lastIndexOf(String str): System.out.println(s.lastIndexOf("abc"));//5 //int lastIndexOf(String str, int fromIndex): System.out.println(s.lastIndexOf("abc", 3));//2 } }
注意:上述方法都是实例方法。
//Test.java class Student { public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
//Test.java public class Test { public static void main(String[] args) { //1.数字转字符串 String s1 = String.valueOf(1234); String s2 = String.valueOf(12.34); String s3 = String.valueOf(true); String s4 = String.valueOf(new Student("张三", 19)); System.out.println(s1); System.out.println(s2); System.out.println(s3); System.out.println(s4); System.out.println("==========================="); //2. 字符串转数字 //注意:Integer、Double等是Java中的包装类,后面会详细介绍 int data1 = Integer.parseInt("1234"); double data2 = Double.parseDouble("12.34"); System.out.println(data1); System.out.println(data2); System.out.println(data1 + data2); } }
public class Test { public static void main(String[] args) { String s1 = "hello"; String s2 = "HELLO"; //1. 小写转大写 System.out.println(s1.toUpperCase()); //2. 大写转小写 System.out.println(s2.toLowerCase()); } } /* 执行结果: HELLO hello */
public class Test { public static void main(String[] args) { String s = "hello"; //1. 字符串转数组 char[] ch = s.toCharArray();//{'h', 'e', 'l', 'l', 'o'}; for (int i = 0; i < ch.length; i++) { System.out.print(ch[i] + " "); } System.out.println(); //2. 数组转字符串 String s2 = new String(ch); System.out.println(s2); } } /* 执行结果: h e l l o hello */
public class Test {
public static void main(String[] args) {
String s = String.format("%d-%d-%d", 2024, 6, 20);
System.out.println(s);
}
}
/*
执行结果:
2024-6-20
*/
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:
方法 | 功能 |
---|---|
String replaceAll(String regex, String replacement) | 替换所有的指定内容 |
String replaceFirst(String regex, String replacement) | 替换收个内容 |
//Test.java public static void main(String[] args) { String str = "hello world hello mom"; String[] ret = str.split(" ");//按照空格拆分 for(String s : ret) { System.out.println(s); } } /* 1.执行结果: hello world hello mom */
public static void main(String[] args) {
String str = "hello world hello mom";
String[] ret = str.split(" ", 2);
for(String s : ret) {
System.out.println(s);
}
}
/*2.执行结果:
hello
world hello mom
*/
拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义
public static void main(String[] args) {
String str = "192.168.1.1" ;
String[] ret = str.split("\\.") ;
for(String s : ret) {
System.out.println(s);
}
}
/*
3.执行结果:
192
168
1
1
*/
注意事项:
1. 字符"|"、"*"、"+"、"."都得加上转义字符,前面加上"\\"。
2. 而如果是"\",那么就得写成"\\\\"。
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符。
public static void main(String[] args) {
String str = "name=zhangsan&age=18" ;
String[] ret = str.split("&") ;
for (int i = 0; i < ret.length; i++) {
String[] temp = ret[i].split("=") ;
System.out.println(temp[0]+" = "+temp[1]);
}
}
/*
4.执行结果:
name = zhangsan
age = 18
*/
这种代码在以后的开发之中会经常出现
从一个完整的字符串之中截取出部分内容。可用方法如下:
方法 | 功能 |
---|---|
String substring(int beginIndex) | 从指定索引截取到结尾 |
String substring(int beginIndex, int endIndex) | 截取部分内容 |
代码示例: 观察字符串截取
public class Test { public static void main(String[] args) { String str = "helloworld"; System.out.println(str); //String substring(int beginIndex): System.out.println(str.substring(3)); //String substring(int beginIndex, int endIndex): System.out.println(str.substring(0, 5));//[0,5) } } /* 执行结果: helloworld loworld hello */
注意事项:
方法 | 功能 |
---|---|
String trim() | 去掉字符串中的左右空格,保留中间空格 |
String toUpperCase() | 字符串转大写 |
String toLowerCase() | 字符串转小写 |
public static void main(String[] args) {
String str = " hello world ";
System.out.println("[" + str + "]");
System.out.println("[" + str.trim() + "]");
}
/*
1.执行结果:
[ hello world ]
[hello world]
*/
trim 会去掉字符串开头和结尾的空白字符(空格, 换行, 制表符等)。
public static void main(String[] args) {
String str = " hello%$$%@#$%WORLD 哈哈哈 " ;
System.out.println(str.toUpperCase());
System.out.println(str.toLowerCase());
}
/*
2.执行结果:
HELLO%$$%@#$%WORLD 哈哈哈
hello%$$%@#$%world 哈哈哈
*/
这两个函数只转换字母。
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
String类在设计时就是不可改变的,String类实现描述中已经说明了
以下来自JDK1.8中String类的部分实现:
String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:
所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
比如 replace 方法:
【纠正】 网上有些人说:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变。
这种说法是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。
final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。
public class Test {
public static void main1(String[] args) {
String str = "abc";
System.out.println(str.replace("a", "qq"));
}
public static void main(String[] args) {
final int[] array = {1, 2, 3, 4, 5};
array[0] = 100;
System.out.println(Arrays.toString(array));
//编译报错:java: 无法为最终变量array分配值
//array = new int[]{6, 7, 8};
}
}
为什么 String 要设计成不可变的?(不可变对象的好处是什么?)
注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。
public static void main1(String[] args) {
String s = "hello";
s += " world";
System.out.println(s);//hello world
}
但是这种方式不推荐使用,因为其效率非常低,中间创建了好多临时对象。
public class Test { public static void main(String[] args) { long start = System.currentTimeMillis();//毫秒 String s = ""; for(int i = 0; i < 10000; ++i){ s += i; } long end = System.currentTimeMillis(); System.out.println(end - start);//248 start = System.currentTimeMillis(); StringBuffer sbf = new StringBuffer(""); for(int i = 0; i < 10000; ++i){ sbf.append(i); } end = System.currentTimeMillis(); System.out.println(end - start);//1 start = System.currentTimeMillis(); StringBuilder sbd = new StringBuilder(); for(int i = 0; i < 10000; ++i){ sbd.append(i); } end = System.currentTimeMillis(); System.out.println(end - start);//0 } } /* 执行结果: 248 1 0 */
可以看待在对String类进行修改时,效率是非常慢的,因此:尽量避免对String的直接需要,如果要修改建议尽量使用StringBuffer或者StringBuilder。
借助StringBuffer 和 StringBuilder
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的,这里介绍 StringBuilder常用的一些方法,其它需要用到了大家可参阅StringBuilder官方文档
方法 | 功能 |
---|---|
StringBuff append(String str) | 在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、double、float、int、long、Object、String、StringBuff的变量 |
char charAt(int index) | 获取index位置的字符 |
int length() | 获取字符串的长度 |
int capacity() | 获取底层保存字符串空间总的大小 |
void ensureCapacity(int mininmumCapacity) | 扩容 |
void setCharAt(int index, char ch) | 将index位置的字符设置为ch |
int indexOf(String str) | 返回str第一次出现的位置 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始查找str第一次出现的位置 |
int lastIndexOf(String str) | 返回最后一次出现str的位置 |
int lastIndexOf(String str, int fromIndex) | 从fromIndex位置开始找str最后一次出现的位置 |
StringBuff insert(int offset, String str) | 在offset位置插入:八种基类类型 & String类型 & Object类型数据 |
StringBuffer deleteCharAt(int index) | 删除index位置字符 |
StringBuffer delete(int start, int end) | 删除[start, end)区间内的字符 |
StringBuffer replace(int start, int end, String str) | 将[start, end)位置的字符替换为str |
String substring(int start) | 从start开始一直到末尾的字符以String的方式返回 |
String substring(int start,int end) | 将[start, end)范围内的字符以String的方式返回 |
StringBuffer reverse() | 反转字符串 |
String toString() | 将所有字符按照String的方式返回 |
public static void main(String[] args) { StringBuilder sb1 = new StringBuilder("hello"); StringBuilder sb2 = sb1; //追加:即尾插 ->字符、字符串、整型数字 sb1.append(' ');//hello sb1.append("world");//hello world sb1.append(123);//hello world123 System.out.println(sb1);//hello world123 System.out.println(sb1 == sb2);//true System.out.println("==========================="); System.out.println(sb1.charAt(0));//获取0号位上的字符 h System.out.println(sb1.length());//获取字符串的有效长度14 System.out.println(sb1.capacity());//获取底层数组的总大小 System.out.println("==========================="); sb1.setCharAt(0, 'H');//设置任意位置的字符 Hello world123 sb1.insert(0, "Hello world!!!");//Hello world!!!Hello world123 System.out.println(sb1); System.out.println(sb1.indexOf("Hello"));//获取Hello第一次出现的位置 System.out.println(sb1.lastIndexOf("hello"));//获取hello最后一次出现的位置 System.out.println("==========================="); sb1.deleteCharAt(0);//删除首字符 sb1.delete(0,5);//删除[0, 5)范围内的字符 String str = sb1.substring(0, 5);//截取[0, 5)区间中的字符以String的方式返回 System.out.println(str); System.out.println("==========================="); sb1.reverse();//字符串逆转 str = sb1.toString();//将StringBuffer以String的方式返回 System.out.println(str); }
从上述例子可以看出:String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuilder
注意:String和StringBuilder不能直接转换。如果想要互相转换,可以采用如下原则:
public static void main(String[] args) {
String str = "hello";
StringBuilder sb = new StringBuilder(str);//使用构造方法
//或者
StringBuilder sb2 = new StringBuilder();
System.out.println(sb2.append(str));//使用append方法
}
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("hello");
String str = sb.toString();
System.out.println(str);
}
public class Test {
public static void main(String[] args) {
String str = new String("ab");//会创建多少个对象
String str1 = new String("a") + new String("b");//会创建多少个对象
}
}
在Java中,使用new关键字创建一个对象时,就会在堆内存中分配一个新的对象实例。现在,来分析一下代码,看看分别创建了多少个对象。
对于第一行代码:
String str = new String("ab");
这里会创建两个对象:
一个是在字符串常量池中创建的字符串字面量"ab"(如果池中尚未存在该字面量)。但请注意,如果之前已经有相同的字符串字面量存在于常量池中,则不会创建新的字面量对象。
另一个是通过 new String(“ab”) 在堆上创建的一个新的String对象,该对象的内容与字符串常量池中的"ab"相同,但是是堆上的一个独立副本。
所以,这行代码至少会创建一个堆上的String对象,可能还会在字符串常量池中创建一个对象(如果之前不存在的话)。
对于第二行代码:
String str1 = new String("a") + new String("b");
这里发生的事情稍微复杂一些:
首先,字符串字面量"a"和"b"会被放入字符串常量池(如果它们之前不存在的话)。
然后,通过 new String(“a”)和new String(“b”) 在堆上分别创建两个新的String对象。
当执行字符串连接操作时(使用+运算符),Java会创建一个新的StringBuilder对象(在某些情况下可能是StringBuffer,但在大多数情况下是StringBuilder),并使用其append方法来连接两个字符串。然后,通过调用StringBuilder的toString方法来创建一个表示连接结果的新String对象。
两个在堆上的String对象(通过new String(“a”)和new String(“b”)创建)。
- 一个StringBuilder对象(或类似的可变字符序列对象)。
- 一个表示连接结果的String对象(通过StringBuilder的toString方法创建)。
综上所述,str的初始化至少会创建一个对象(堆上的String),可能还会在字符串常量池中创建一个;而str1的初始化则会创建至少四个对象(两个堆上的String,一个StringBuilder,以及一个表示连接结果的String),同样,如果字符串常量池中之前没有相关字面量,还会在池中创建。不过,请注意,JVM的具体实现和版本可能会影响这些细节。
描述:
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1
示例 1:
输入: s = "leetcode"
输出: 0
示例 2:
输入: s = "loveleetcode"
输出: 2
示例 3:
输入: s = "aabb"
输出: -1
代码展示:
class Solution { public static int firstUniqChar(String s) { int[] count = new int[26];//这个数组拿来计数:字符出现的次数 for(int i = 0; i < s.length(); i++) { //从字符串中拿出字符: char ch = s.charAt(i);//length //放在计数的数组中 count[ch - 'a'] ++; } //计数完重新遍历一遍字符串,找到它的第一个不重复的字符 for(int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if(count[ch - 'a'] == 1) { return i; } } return -1; } } public class Test { public static void main(String[] args) { int ret = Solution.firstUniqChar("loveleetcode"); System.out.println(ret); } }
解析:
简而言之,这段代码通过两次遍历字符串:第一次统计字符出现次数,第二次找到第一个只出现一次的字符。
图文:
最后一个单词的长度
描述:
计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾)
输入描述:
输入一行,代表要计算的字符串,非空,长度小于5000。
输出描述:
输出一个整数,表示输入字符串最后一个单词的长度。
示例:
输入:hello nowcoder
输出:8
说明:最后一个单词为nowcoder,长度为8
代码展示:
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
//方法1:使用split方法 -> 切割字符串
public static void main1(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
//使用split切割字符串,装在新的数组中
String[] ss = s.split(" ");
//取出最后一个索引的内容
int len = ss[ss.length - 1].length();
System.out.println(len);
}
}
解析:
简而言之,这段代码就是读取一行文本,找出其中的最后一个单词,并输出这个单词的长度。
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Test { //方法2:使用lastIndexOf() 和 substring() public static void main(String[] args) { //lastIndexOf() : 从字符串的后面开始找: " ",找到就返回索引,否则就返回-1 //substring(int beginIndex) : 从beginIndex开始发生截断 Scanner scanner = new Scanner(System.in); String s = scanner.nextLine(); //找最后一个空格后面的字符串 int index = s.lastIndexOf(" "); //从index索引开始发生截断 -> 得到index+1后面的字符串 int len = s.substring(index + 1).length(); System.out.println(len); } }
解析:
简而言之,这段代码读取一行文本,找到最后一个空格后的字符串部分,并输出其长度。
检测字符串是否为回文
描述:
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 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 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。
代码展示:
class Solution { public boolean isPalindrome(String s) { //所有大写字符转换为小写字符: s = s.toLowerCase(); //开始判断:定义两个索引:一个从左边开始, 一个从右边开始,进行对比 int left = 0; int right = s.length() - 1; //开始对比: while(left < right) { //遇到非字母和非数字就跳过,不进行比较 while(left < right && !isNumberAndCharacter(s.charAt(left))) { left++; } while(left < right && !isNumberAndCharacter(s.charAt(right))) { right--; } //走到这,证明字符都是字母,可以进行对比 if(s.charAt(left) == s.charAt(right)) { left++; right--; //对比完,就往下走 }else { //不相同,说明不是回文串 return false; } } //走到这,证明是回文串 return true; } //字母和数字都属于字母数字字符。 -> 假如是非字母和非数字就跳过,不进行比较 private boolean isNumberAndCharacter(char ch) { //用isDigit() : 判断字符是否为数字 //用isLetter() : 判断字符是否为字母 if(Character.isDigit(ch) || Character.isLetter(ch)) { return true; }else { return false; } } } public class Test { public static void main(String[] args) { Solution solution = new Solution(); System.out.println(solution.isPalindrome("A man, a plan, a canal: Panama")); } }
解析:
代码定义了一个Solution类,其中包含两个方法:isPalindrome和isNumberAndCharacter。这个类的主要功能是检查一个字符串是否是一个“回文串”,即正读和反读都相同的字符串。
isPalindrome方法:
首先,该方法将输入字符串s转化为小写,以确保大小写不影响回文的判断。
定义了两个指针,left从字符串的开始位置出发,right从字符串的末尾位置出发。
使用一个while循环,当left小于right时执行循环体内的代码。
在循环体内,代码会跳过left指针位置上的非字母和非数字字符,直到找到第一个字母或数字字符。
如果left指针超过了right指针,说明已经检查完整个字符串,且字符串是回文的,所以返回true。
isNumberAndCharacter方法:
简而言之,这段代码通过双指针的方法,从字符串的两端向中间遍历,同时跳过非字母和非数字的字符,来判断一个字符串是否是回文串。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。