文章

java重拾-数组&字符串

java重拾-数组&字符串

数组

基于 C/C++ 语言实现

int[] anArray;
int anOtherArray[];
int[] anArray = new int[10];
int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
for (int element : anOtherArray) {
    System.out.println(element);
}

数组属于一种基本的操作类型,本质是一个指针,指向一段具有相同排列的连续内存的起始位置,并没有封装任何高级方法

若是希望使用类似std的方法,将数组转为List,List是一个接口,任一实现类都实现了许多有用且高效的方法

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

看不懂的流

Arrays.sort

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

Arrays.binarySearch

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

Arrays.copyOfRange

底层调用的是 System.arraycopy() 方法,这个方法是一个 native 方法,它是用 C/C++ 实现的,效率非常高

int[] array1 = {1, 2, 3};
int[] array2 = {4, 5, 6};

// 创建一个新数组,长度为两个数组长度之和
int[] mergedArray = new int[array1.length + array2.length];

// 复制第一个数组到新数组
System.arraycopy(array1, 0, mergedArray, 0, array1.length);
System.out.println(Arrays.toString(mergedArray));

// 复制第二个数组到新数组
System.arraycopy(array2, 0, mergedArray, array1.length, array2.length);
System.out.println(Arrays.toString(mergedArray));

ArrayIndexOutOfBoundsException

数组越界

二维数组

即指针的数组

A->B[]

B[i]->C[]

打印数组

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

Arrays.toString()
Arrays.deepToString()

字符串

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
}
  • final 创建即不变, 在生命周期中只会指向同一个内存地址
  • serializable 可序列化
  • comparable 建议使用compareTo, equals进行==比较
  • CharSequence可以拼接, 但是因为string是final的, 每次拼接都会产生一个新的, 内存开销大, 建议使用 StringBuffer 和 StringBuilder

底层实现

private final char value[];
private final byte value[];

使用

private final byte coder;

来区分编码方式, 若满足 ASCII则使用byte存储, 如果有中文登UTF8才支持的字符则使用 char

HashCode

每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的

private int hash; // 缓存字符串的哈希码

public int hashCode() {
    int h = hash; // 从缓存中获取哈希码
    // 如果哈希码未被计算过(即为 0)且字符串不为空,则计算哈希码
    if (h == 0 && value.length > 0) {
        char val[] = value; // 获取字符串的字符数组

        // 遍历字符串的每个字符来计算哈希码
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i]; // 使用 31 作为乘法因子
        }
        hash = h; // 缓存计算后的哈希码
    }
    return h; // 返回哈希码
}

hashCode 方法首先检查是否已经计算过哈希码,如果已经计算过,则直接返回缓存的哈希码。否则,方法将使用一个循环遍历字符串的所有字符,并使用一个乘法和加法的组合计算哈希码。

这种计算方法被称为“31 倍哈希法”

H(s) = (s[0] * 31^(n-1)) + (s[1] * 31^(n-2)) + ... + (s[n-1] * 31^0)

用于给长string 标一个id, 复用时直接返回hashcode相同的指针即可

字符串常量池

String s = new String("二哥");

创建了两个对象

  • 常量池的对象
  • 堆中的对象

返回堆中的引用

因为双引号不存在常量池的字符串, 都会在常量池注册一次, 同时使用new关键字一定会产生一个新对象

因此产生了两个对象

String s = "三妹";

则不会硬性地产生新对象, 而是查找常量池后直接返回常量池的引用

又因为string是final的, 这样做并不会对常量池的对象产生影响

若是通过字符串操作产生的字符串就不会在常量池中注册,, 正所谓"常量"嘛

但是若我们希望动态地加入一些"常量"可以使用String.intern()方法

此时会在常量池中保存堆的对象的引用, 并不会产生一个新的对象, 因为java7之后字符串常量池放在了堆中

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

不过需要注意的是,尽管 intern 可以确保所有具有相同内容的字符串共享相同的内存空间,但也不要烂用 intern,因为任何的缓存池都是有大小限制的,不能无缘无故就占用了相对稀缺的缓存空间,导致其他字符串没有坑位可占。

StringBuffer&StringBuilder

Buffer有同步锁, Builder无同步锁, 在同步场景可以使用ThreadLocal避免一部分冲突

Builder效率高, 主要使用他

java编译器会自动将字符串拼接优化为Builder的拼接

StringBuilder的实现

使用原生char[], count, capacity实现操作, 不依赖String

append会检查count与capacity的大小, 若是不够大会调用ensureCapacityInternal扩容

扩容策略是n=2n+2

判断相等

  • == 用于判断基本数据类型的值相等
  • .equals() 方法用于比较两个对象的内容是否相等
String alita = new String("小萝莉");
String luolita = new String("小萝莉");

System.out.println(alita.equals(luolita)); // true
System.out.println(alita == luolita); // false
  • Objects.equals()

Objects.equals() 这个静态方法的优势在于不需要在调用之前判空。

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
  • String 类的 .contentEquals()

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

public boolean contentEquals(CharSequence cs) {
    // Argument is a StringBuffer, StringBuilder
    if (cs instanceof AbstractStringBuilder) {
        if (cs instanceof StringBuffer) {
            synchronized(cs) {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        } else {
            return nonSyncContentEquals((AbstractStringBuilder)cs);
        }
    }
    // Argument is a String
    if (cs instanceof String) {
        return equals(cs);
    }
    // Argument is a generic CharSequence
    int n = cs.length();
    if (n != length()) {
        return false;
    }
    byte[] val = this.value;
    if (isLatin1()) {
        for (int i = 0; i < n; i++) {
            if ((val[i] & 0xff) != cs.charAt(i)) {
                return false;
            }
        }
    } else {
        if (!StringUTF16.contentEquals(val, cs, n)) {
            return false;
        }
    }
    return true;
}

拼接

编译器自动优化为双引号或builder append

  • concat

原理与builder类似, 但每次concat都会产生新string对象, 开销大

  • String.join

使用StringJoiner实现, 其本质仍是builder

License:  CC BY 4.0