数组
一维数组
- 定义
int[] anArray;
int anOtherArray[];
- 初始化
int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
- 访问
- anArray[0] = 10;
- 可变数组:
void varargsMethod(String... varargs) {}
该方法可以接收任意数量的字符串参数,可以是 0 个或者 N 个,本质上,可变参数就是通过数组实现的。
- 方法
- 数组排序
Arrays.sort()
- 基本数据类型按照升序排列
- 实现了 Comparable 接口的对象按照 compareTo() 的排序
- 数组复制:
System.arraycopy(原数组,从原数组复制开始的索引,新数组,复制到新数组的开始索引,复制长度)
二维数组
-
定义
data_type[][] array_name;
int[][] oddNumbers = { {1, 3, 5, 7}, {9, 11, 13, 15} };
-
访问
oddNumbers[0][0]
-
打印数组
直接打印数组输出的是其地址,需要使用一些方法打印
- stream 流打印 Java 数组
第一种:
Arrays.asList(arr).stream().forEach(s -> System.out.println(s));
第二种:Stream.of(cmowers).forEach(System.out::println);
第三种:Arrays.stream(cmowers).forEach(System.out::println);
- for 循环打印 Java 数组
- Arrays 工具类打印 Java 数组
String [] arr= {“1”,“1”,“2”};
System.out.println(Arrays.toString(arr)); - Arrays工具类打印二维数组
Arrays.deepToString(arr)
字符串
- String类声明
- 是final的,不能被继承
- 实现了 Serializable 接口,可以序列化
- 实现了 Comparable 接口,意味着最好不要用‘==’来比较两个字符串是否相等,而应该用 compareTo() 方法去比较。
- String 和 StringBuffer、StringBuilder 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候就可以考虑一下 String 的另外两个好兄弟,StringBuffer 和 StringBuilder,它俩是可变的。
- String 类的 hashCode 方法
每一个字符串都会有一个 hash 值。 - String 类的 substring 方法
用来截取字符串的
- 提取一段
String str = "Hello, world!"; String subStr = str.substring(7, 12); // 从第7个字符(包括)提取到第12个字符(不包括) System.out.println(subStr); // 输出 "world"
- 提取前缀,后缀
String str = "Hello, world!"; String prefix = str.substring(0, 5); // 提取前5个字符,即 "Hello" String suffix = str.substring(7); // 提取从第7个字符开始的所有字符,即 "world!"
- 处理字符串中的空格,分隔符
String str = " Hello, world! "; String trimmed = str.trim(); // 去除字符串开头和结尾的空格 String[] words = trimmed.split("\\s+"); // 将字符串按照空格分隔成单词数组 String firstWord = words[0].substring(0, 1); // 提取第一个单词的首字母 System.out.println(firstWord); // 输出 "H"
- 处理字符串中的数字,符号
String str = "1234-5678-9012-3456"; String[] parts = str.split("-"); // 将字符串按照连字符分隔成四个部分 String last4Digits = parts[3].substring(1); // 提取最后一个部分的后三位数字 System.out.println(last4Digits); // 输出 "456"
- String 类的 indexOf 方法
indexOf 方法用于查找一个子字符串在原字符串中第一次出现的位置,并返回该位置的索引。
- 查找某个字符的位置
String str = "Hello, world!"; int index = str.indexOf(","); // 查找逗号在 str 中第一次出现的位置 System.out.println(index); // 输出 5
- 从指定位置查找子串位置
String str = "Hello, world!"; int index = str.indexOf("l", 3); // 从索引为3的位置开始查找 "l" 子字符串在 str 中第一次出现的位置 System.out.println(index); // 输出 3
- 其他方法
- length() 用于返回字符串长度。
- isEmpty() 用于判断字符串是否为空。
- charAt() 用于返回指定索引处的字符。
- valueOf() 用于将其他类型的数据转换为字符串。
- trim() 用于去除字符串两侧的空白字符
- getBytes() 用于返回字符串的字节数组
- toCharArray() 用于将字符串转换为字符数组。
- concat() 方法用于拼接字符串
String 对象一旦被创建后就固定不变了,对 String 对象的任何修改都不会影响到原来的字符串对象,都会生成新的字符串对象。
字符串常量池
String s = new String("王二");
这行代码在创建String时,JVM会先在字符串常量池中找“王二”,如果找到了就直接在堆中创建一个对象。如果没找到,就先在字符串常量池中创建一个对象保存“王二”,然后再在堆中创建一个对象。
如此一来是多此一举吗?
注意,上面我们创建字符串使用的是new对象的方式,但是通常我们都是直接赋值String s = "张三";
,这样的方法创建的字符串,JVM会先去字符串常量池里找,如果找到了就不创建对象了,如果没找到就在字符串常量池中创建一个对象,这样一来就能节省堆内存。
- 使用双引号声明的字符串对象会保存在字符串常量池中。
- 使用 new 关键字创建的字符串对象会先从字符串常量池中找,如果没找到就创建一个,然后再在堆中创建字符串对象;如果找到了,就直接在堆中创建字符串对象。
- 调用
intern()
方法可以将new出来的字符串放入字符串常量池中
Java7之后,由于字符串常量池放在了堆中,执行 String.intern() 方法的时候,如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间。
StringBuilder和StringBuffer
由于字符串是不可变的,因此我们对于字符串的操作,都需要创建很多新的字符串,这样一来造成了过多的空间浪费,尤其是字符串拼接时,因此StringBuffer和StringBuilder应运而生。
由于 StringBuffer 操作字符串的方法加了 synchronized 关键字进行了同步,主要是考虑到多线程环境下的安全问题,所以如果在非多线程环境下,执行效率就会比较低,因为加了没必要的锁。
于是只用于单线程的StringBuilder出现了,其功能与StringBuffer功能类似。
实际开发中,StringBuilder 的使用频率也是远高于 StringBuffer,甚至可以这么说,StringBuilder 完全取代了 StringBuffer。
public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
public StringBuffer() {
super(16);
}
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
public synchronized String toString() {
return new String(value, 0, count);
}
// 其他方法
}
-
Java对于字符串拼接的操作
当编译器遇到 + 号这个操作符的时候,会将 new String(“二哥”) + new String(“三妹”) 这行代码解释为以下代码:new StringBuilder().append("二哥").append("三妹").toString();
-
StringBuilder的内部实现 toString()方法
public String toString() { return new String(value, 0, count); }
value 是一个 char 类型的数组,在 StringBuilder 对象创建时,会为 value 分配一定的内存空间(初始容量 16),用于存储字符串。随着字符串的拼接,value 数组的长度会不断增加,因此在 StringBuilder 对象的实现中,value 数组的长度是可以动态扩展的,就像ArrayList那样。
count 是一个 int 类型的变量,表示字符序列的长度。toString() 方法会调用 new String(value, 0, count),使用 value 数组中从 0 开始的前 count 个元素创建一个新的字符串对象,并将其返回。
-
StringBuilder的内部实现 toString()方法
append(String str) 方法将指定字符串追加到当前字符序列中。如果指定字符串为 null,则追加字符串 “null”;否则会检查指定字符串的长度,然后根据当前字符序列中的字符数和指定字符串的长度来判断是否需要扩容。public StringBuilder append(String str) { super.append(str); return this; }
如果需要扩容,则会调用 ensureCapacityInternal(int minimumCapacity) 方法。扩容之后,将指定字符串的字符拷贝到字符序列中。
private void ensureCapacityInternal(int minimumCapacity) { // 不够用了,扩容 if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity); } void expandCapacity(int minimumCapacity) { // 扩容策略:新容量为旧容量的两倍加上 2 int newCapacity = value.length * 2 + 2; // 如果新容量小于指定的最小容量,则新容量为指定的最小容量 if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; // 如果新容量小于 0,则新容量为 Integer.MAX_VALUE if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } // 将字符序列的容量扩容到新容量的大小 value = Arrays.copyOf(value, newCapacity); }
-
StringBuilder的 reverse 方法
StringBuilder 还提供了一个 reverse 方法,用于反转当前字符序列中的字符。
判断字符串是否相等
- equals()方法与 ==
s1.equals(s2)判断的是两个字符串内容是否相等, ==判断的是两个字符串是否是同一个对象
使用equals()之前要先判空,否则可能会出现空指针异常问题a != null && a.equals(b)
- String 类的 .contentEquals()
.contentEquals() 的优势在于可以将字符串与任何的字符序列(StringBuffer、StringBuilder、String、CharSequence)进行比较。
拼接字符串
-
String.append拼接字符串
+
操作符的本质:’ + ’ 号操作符其实被 Java 在编译的时候重新解释了,换一种说法就是,+ 号操作符是一种语法糖,让字符串的拼接变得更简便了
循环体内,拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 号操作符。原因就在于循环体内如果用 + 号操作符的话,就会产生大量的 StringBuilder 对象,不仅占用了更多的内存空间,还会让 Java 虚拟机不停的进行垃圾回收,从而降低了程序的性能。
-
String.concat 拼接字符串
String chenmo = "沉默"; String wanger = "王二"; System.out.println(chenmo.concat(wanger));
和 + 号操作符相比,concat() 方法在遇到字符串为 null 的时候,会抛出 NullPointerException,而“+”号操作符会把 null 当做是“null”字符串来处理。
如果拼接的字符串是一个空字符串(“”),那么 concat 的效率要更高一点,毕竟不需要 new StringBuilder 对象。
-
String.join 拼接字符串
第一个参数为字符串连接符String chenmo = "沉默"; String wanger = "王二"; String cmower = String.join("", chenmo, wanger); System.out.println(cmower);
-
StringUtils.join 拼接字符串
String chenmo = "沉默"; String wanger = "王二"; StringUtils.join(chenmo, wanger);
该方法不用担心 NullPointerException,其内部使用的仍然是 StringBuilder。
如何拆分字符串
拆分字符串使用split()方法
首先,需要判断一下拆分符号是否存在字符串中,否则会抛异常
其次,对于位置的字符串,无法确定拆分符号,需要使用正则表达式,下面的文档是正则表达式的学习文档
https://github.com/cdoco/learn-regex-zh
https://github.com/cdoco/common-regex
public class Test {
public static void main(String[] args) {
String cmower = "沉默王二,一枚有趣的程序员";
if (cmower.contains(",")) {
String [] parts = cmower.split(",");
System.out.println("第一部分:" + parts[0] +" 第二部分:" + parts[1]);
} else {
throw new IllegalArgumentException("当前字符串没有包含逗号");
}
}
}
- 第一种:使用
\\.
表示.
String cmower = "沉默王二.一枚有趣的程序员"; if (cmower.contains(".")) { String [] parts = cmower.split("\\."); System.out.println("第一部分:" + parts[0] +" 第二部分:" + parts[1]); }
- 第二种:使用
[.]
cmower.split("[.]");
[]
也是一个正则表达式,用来匹配方括号中包含的任意字符。 - 第三种:使用 Pattern 类的 quote() 方法来包裹英文逗点
.
当 split() 方法的参数是正则表达式的时候,方法最终会执行下面这行代码:String [] parts = cmower.split(Pattern.quote("."));
return Pattern.compile(regex).split(this, limit);
也就意味着,拆分字符串有了新的选择,可以不使用 String 类的 split() 方法。public class TestPatternSplit { private static Pattern twopart = Pattern.compile("\\."); public static void main(String[] args) { String [] parts = twopart.split("沉默王二.一枚有趣的程序员"); System.out.println("第一部分:" + parts[0] +" 第二部分:" + parts[1]); } }
split() 方法可以传递 2 个参数,第一个为分隔符,第二个为拆分的字符串个数。
注:本文章来源自博主沉默王二的二哥的Java进阶之路学习内容。