Bootstrap

Java数组应用与字符串基础

数组

一、for-each 循环遍历数组:

for (int element : anOtherArray) {
    System.out.println(element);
}

二 、将数组添加到List中的方式:

1、方法一

List<Integer> aList = new ArrayList<>();
for (int element : anArray) {
    aList.add(element);
}

2、方法二

List<Integer> aList = Arrays.asList(anArray);

注意:Arrays.asList 方法返回的 ArrayList 并不是 java.util.ArrayList,它其实是 Arrays 类的一个内部类,如果需要添加元素或者删除元素的话,需要把它转成 java.util.ArrayList

new ArrayList<>(Arrays.asList(anArray));

3、方法三

List<Integer> aList = Arrays.stream(anArray).boxed().collect(Collectors.toList());

三、数组进行排序

1.Arrays 类提供的 sort() 方法

int[] anArray = new int[] {5, 2, 1, 4, 8};
Arrays.sort(anArray);
[1, 2, 4, 5, 8]

2.实现了 Comparable 接口的对象按照 compareTo() 的排序

String[] a = new String[] {"A", "E", "Z", "B", "C"};
Arrays.sort(a, 1, 3, Comparator.comparing(String::toString).reversed());
[A, Z, E, B, C]

3.Arrays.binarySearch() 方法,二分查找法

int[] anArray = new int[] {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(anArray, 4);

四、数组打印

1.stream 流打印 Java 数组

Arrays.asList(cmowers).stream().forEach(s -> System.out.println(s));
Stream.of(cmowers).forEach(System.out::println);
Arrays.stream(cmowers).forEach(System.out::println);

2.for 循环打印 Java 数组

3.Arrays 工具类打印 Java 数组

Arrays.toString() 方法来打印数组

String [] cmowers = {"c","b","a"};
System.out.println(Arrays.toString(cmowers));

4.Arrays工具类打印二维数组

Arrays.deepToString() 方法。

String[][] deepArray = new String[][] {{"a", "b"}, {"c"}};
System.out.println(Arrays.deepToString(deepArray));

字符串

学习二哥的Java进阶之路所得 https://javabetter.cn/

String 类

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
    private final byte coder;
    private int hash;
}
  1. String 类是 final 的,意味着它不能被子类继承。

  2. String 类实现了 Serializable 接口,意味着它可以序列化。

  3. String 类实现了 Comparable 接口,意味着最好不要用‘==’来比较两个字符串是否相等,而应该用 compareTo() 方法去比较。

  4. StringBuffer、StringBuilder 和 String 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候就可以考虑一下 String 的另外两个好兄弟,StringBuffer 和 StringBuilder,它俩是可变的。

  5. Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码。这样做的好处是在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。

  6. 每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合来作为 HashMap 的键值。

为什么String不可变

  1. 可以保证 String 对象的安全性,避免被篡改,毕竟像密码这种隐私信息一般就是用字符串存储的。

  2. 保证哈希值不会频繁变更。毕竟要经常作为哈希表的键值,经常变更的话,哈希表的性能就会很差劲。

  3. 可以实现字符串常量池。

Java的字符串常量池

img

在Java中,栈上存储的是基本数据类型的变量和对象的引用,而对象本身则存储在堆上。

创建了两个对象:一个是字符串对象 “二哥”,它被添加到了字符串常量池中,另一个是通过 new String() 构造函数创建的字符串对象 “二哥”,它被分配在堆内存中,同时引用变量 s 存储在栈上,它指向堆内存中的字符串对象 “二哥”。

String s = “三妹”;

img

String.intern()方法

  • 使用双引号声明的字符串对象会保存在字符串常量池中。

  • 使用 new 关键字创建的字符串对象会先从字符串常量池中找,如果没找到就创建一个,然后再在堆中创建字符串对象;如果找到了,就直接在堆中创建字符串对象。

  • 针对没有使用双引号声明的字符串对象来说,就像下面代码中的 s1 那样:

String s1 = new String("二哥") + new String("三妹");

如果想把 s1 的内容也放入字符串常量池的话,可以调用 intern() 方法来完成。

对比
String s1 = new String("二哥三妹");
String s2 = s1.intern();
System.out.println(s1 == s2);
### false

img

String s1 = new String("二哥") + new String("三妹");
String s2 = s1.intern();
System.out.println(s1 == s2);
### true

img

  1. 创建 “二哥” 字符串对象,存储在字符串常量池中。
  2. 创建 “三妹” 字符串对象,存储在字符串常量池中。
  3. 执行 new String("二哥"),在堆上创建一个字符串对象,内容为 “二哥”。
  4. 执行 new String("三妹"),在堆上创建一个字符串对象,内容为 “三妹”。
  5. 执行 new String("二哥") + new String("三妹"),会创建一个 StringBuilder 对象,并将 “二哥” 和 “三妹” 追加到其中,然后调用 StringBuilder 对象的 toString() 方法,将其转换为一个新的字符串对象,内容为 “二哥三妹”。这个新的字符串对象存储在堆上。

String、StringBuilder、StringBuffer

  • StringBuffer 操作字符串的方法加了 synchronized 关键字进行了同步,主要是考虑到多线程环境下的安全问题,所以执行效率会比较低。于是 Java 就给 StringBuffer “生了个兄弟”,名叫 StringBuilder,说,“你别管线程安全了,你就在单线程环境下使用,这样效率会高得多,如果要在多线程环境下修改字符串,你到时候可以使用 ThreadLocal 来避免多线程冲突。”除了类名不同,方法没有加 synchronized,基本上完全一样。

  • append(String str) 方法将指定字符串追加到当前字符序列中。如果指定字符串为 null,则追加字符串 “null”;否则会检查指定字符串的长度,然后根据当前字符序列中的字符数和指定字符串的长度来判断是否需要扩容。

  • 如果需要扩容,则会调用 ensureCapacityInternal(int minimumCapacity) 方法进行扩容。扩容之后,将指定字符串的字符拷贝到字符序列中。

  • ensureCapacityInternal(int minimumCapacity) 方法用于确保当前字符序列的容量至少等于指定的最小容量 minimumCapacity。如果当前容量小于指定的容量,就会为字符序列分配一个新的内部数组。新容量的计算方式如下:

    • 如果指定的最小容量大于当前容量,则新容量为两倍的旧容量加上 2;
    • 如果指定的最小容量小于等于当前容量,则不会进行扩容,直接返回当前对象。
  • 在进行扩容之前,ensureCapacityInternal(int minimumCapacity) 方法会先检查当前字符序列的容量是否足够,如果不足就会调用 expandCapacity(int minimumCapacity) 方法进行扩容。expandCapacity(int minimumCapacity) 方法首先计算出新容量,然后使用 Arrays.copyOf(char[] original, int newLength) 方法将原字符数组扩容到新容量的大小

判断两个字符串是否相等

.equals() 和 ‘==’ 操作符有什么区别

Object 类的 .equals() 方法默认采用的是“”操作符进行比较。假如子类没有重写该方法的话,那么“”操作符和 .equals() 方法的功效就完全一样——比较两个对象的内存地址是否相等。

"小萝莉" == "小" + "萝莉"

由于‘小’和‘萝莉’都在字符串常量池,所以编译器在遇到‘+’操作符的时候将其自动优化为“小萝莉”,所以返回 true。

new String("小萝莉").intern() == "小萝莉"

new String("小萝莉") 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象;执行 intern() 方法的时候发现字符串常量池中已经有了‘小萝莉’这个对象,所以就直接返回字符串常量池中的对象引用了,那再与字符串常量池中的‘小萝莉’比较,当然会返回 true 了。

除了 .equals() 方法,还有其他两个可选的方案

  1. Objects.equals() 这个静态方法的优势在于不需要在调用之前判空。
public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
Objects.equals("小萝莉", new String("小" + "萝莉")) // --> true
Objects.equals(null, new String("小" + "萝莉")); // --> false
Objects.equals(null, null) // --> true
String a = null;
a.equals(new String("小" + "萝莉")); // throw exception
  1. String 类的 .contentEquals()

.contentEquals() 的优势在于可以将字符串与任何的字符序列(StringBuffer、StringBuilder、String、CharSequence)进行比较

字符串拼接

String.concat 拼接字符串

String 类的 concat() 方法,有点像 StringBuilder 类的 append() 方法。

String chenmo = "沉默";
String wanger = "王二";
System.out.println(chenmo.concat(wanger));
  • “和 + 号操作符相比,concat() 方法在遇到字符串为 null 的时候,会抛出 NullPointerException,而“+”号操作符会把 null 当做是“null”字符串来处理。”

  • 如果拼接的字符串是一个空字符串(“”),那么 concat 的效率要更高一点,毕竟不需要 new StringBuilder 对象。

  • 如果拼接的字符串非常多,concat() 的效率就会下降,因为创建的字符串对象越来越多。

String.join 拼接字符串
String chenmo = "沉默";
String wanger = "王二";
String cmower = String.join("", chenmo, wanger);
System.out.println(cmower);
//第一个参数为字符串连接符
String message = String.join("-", "王二", "太特么", "有趣了");
王二-太特么-有趣了

分割String字符串

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]);
}

使用 split() 方法的时候,就需要使用正则表达式\\.

也可以使用 [] 来包裹住英文逗点“.”,[] 也是一个正则表达式,用来匹配方括号中包含的任意字符。

cmower.split("[.]");

还可以使用 Pattern 类的 quote() 方法来包裹英文逗点“.”,该方法会返回一个使用 \Q\E 包裹的字符串

如何在Java中优雅地分割String字符串? | Java进阶之路 (tobebetterjavaer.com)

Java 9将String的底层实现由char数组改成了byte数组

Java 9为什么要将String的底层实现由char数组改成了byte数组? | Java进阶之路 (tobebetterjavaer.com)

;