Java字符串和容器

时间:2022-02-19 19:29:41

String

Java.lang.String是Java的字符串类. Srting是一个不可变对象,所有对String修改的操作都需要构造新的String实例.

String可以由char数组或字符串字面值来构造:

char[] data = {'a', 'b', 'c'}
String s = new String(data)
String s = "Hello World";
String s = new String("Hello World");

使用+运算符可以连接两个字符串:

String s = "Hello" + "World";

比较字符串

Java中的==运算符用于比较两个引用是否指向了同一个对象:

String s1 = new String("Hello World");
String s2 = new String("Hello World");
System.out.println(s1 == s2);  // false

上述程序输出为false, 因为s1和s2指向了不同对象. 若要比较字符串内容是否相同, 请使用equals方法:

String s1 = new String("Hello World");
String s2 = new String("Hello World");
System.out.println(s1.equals(s2)); // true

结果为true没什么需要解释的. 下面这个代码的结果就比较有意思了:

String s1 = "Hello World";
String s2 = "Hello World";
System.out.println(s1 == s2);  // true

结果为true, 说明s1和s2指向了同一个对象. Java在存储字符串字面值时, 会先检查内存中是否存在相同对象. 若存在就直接将新的引用指向该对象.

方法

我们常用format方法以格式化的方式创建新字符串:

System.out.println(String.format("user:%s, age:%d", "abc", 12));

String提供了一系列方法:

  • s.charAt(int): 返回某个位置的字符, 下标从0开始

  • s.length(): 返回字符串的长度

  • s.matches(String regex): 检查字符串时候符合正则表达式regex定义的模式, 返回boolean.
    "A123".matches("[a-zA-Z][0-9]*");

  • s.substring(int start, int end): 返回[start, end)范围内的子串, 若省略end则返回剩余全部字符串.
    "HelloWorld".substring(5, 7); //Wo
    HelloWorld".substring(5); //World

类型转换

  • s.getBytes(): 转换为bytes[]

  • s.toCharArray(): 转换为char[]

  • Integer.parseInt(s): 转换为int

  • Double.parseDouble(s): 转换为double

StringBuffer 与 StringBuilder

java.lang.StringBufferjava.lang.StringBuilder是可变的字符串对象.

StringBuilder较快但是线程不安全的, 在对线程安全没有要求时我们通常使用StringBuilder.

它们拥有和String类似的方法, 并提供了动态操作的API:

  • sb.append(String): 将字符串追加到末尾.

  • sb.reverse(): 字符串反向

  • sb.delete(start, end): 删除[start, end)范围内的字符

  • sb.insert(i, seq): 将字符序列插入i位置

  • sb.replace(start, end, str): 用str替换[start, end)处的字串

Array

Java对所有数据类型都提供了内置的数组支持. 数组是定长的, 下标从0开始.

数组有两种声明方式:

int[] arr = new int[5];

不建议使用下面这种方式:

int arr[] = new int[5];

也可以直接使用数组字面值:

int[] arr = {1, 2, 3};

length()方法用于获取长度, []运算符可以访问或修改某个元素:

arr[0] = 2;
i = arr[1];

Java很容易实现多维数组:

int[][] mat = new int[5][5];
mat[2][2] = 1;

java.util.Arrays提供了一些操作数组的工具:

  • Arrays.fill(arr, val): 将数组arr中所有元素设为val

  • Arrays.binarySearch(arr, key): 在数组arr用二分法查找元素key, arr必须已经排好序. 找到返回索引, 否则返回-1.

  • Arrays.equals(arr1, arr2): 判断arr1和arr2中的内容是否相同

  • Arrays.sort(arr): 对数组进行升序排序.

foreach可以用于遍历数组:

int[] arr = {1, 2, 3};
for (int i : arr) {
    System.out.println(i);
}

List

Java Collection框架提供了各种容器的借口和实现, 所有容器类均采用泛型实现. 所有类均实现了iterable接口, 可以使用for-each遍历.

java.util.list是线性容器的interface, Java中提供了三种常用的实现:

  • java.util.ArrayList: 使用数组实现, 线程不安全

  • java.util.LinkedList: 使用链表实现, 线程不安全

  • java.util.vector: 线程安全实现

注意不能使用内置类型初始化, 必须使用封装类:

List<Integer> l = new ArrayList<Integer>(5);

list接口声明了以下方法:

  • l.get(i): 返回指定位置的元素, 下标从0开始

  • l.set(i, val): 设置指定位置的元素

  • l.size(): 返回容器中元素的个数

  • l.contains(e): 判断容器中是否包含指定元素val, 返回boolean值.

  • l.equals(c): 判断容器相同位置上的元素是否相同, 返回boolean值

  • l.add(i, e): 在指定位置插入元素val, 若省略i则在末尾插入

  • l.addAll(i, c): 在指定位置插入容器c中所有元素, 若省略i则在末尾插入

  • l.remove(i): 删除指定位置上的元素

  • l.clear(): 删除所有元素

  • l.clone(): 返回自身的浅拷贝副本

  • l.toArray(): 转换为相应类型的数组

list同样可以用foreach遍历:

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String s : list) {
    System.out.println(s);
}

Set

java.util.Set是无序的, 不允许重复的容器接口, 对应数学上的集合. Java提供了HashSetTreeSet两种实现.

Set<String> s = new HashSet<String>();

Set接口声明了下列方法:

  • s.contians(e): 判断容器中是否包含元素e, 返回boolean值

  • s.equals(c): 判断两个容器中包含的元素是否完全相同, 返回boolean值

  • l.size(): 返回容器中元素的个数

  • l.add(e): 添加元素val

  • l.addAll(c): 添加容器c中所有元素

  • l.remove(e): 删除指定的元素

  • l.clear(): 删除所有元素

  • l.clone(): 返回自身的浅拷贝副本

  • l.toArray(): 转换为相应类型的数组

foreach遍历集合:

Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
for (String s : set) {
    System.out.println(s);
}

Map

java.util.Map是保存键值对的容器, 键不允许重复. Java提供了HashMapTreeMap两种实现.

Map<String, Integer> m = new HashMap<String, Integer>();

Map接口声明了下列方法:

  • m.containsKey(key): 判断所有键中是否包含key, 返回boolean值

  • m.containsValue(val): 判断所有值中是否包含val, 返回boolean值

  • m.get(key): 返回key对应的值

  • m.put(key, val): 设置key对应的值为val, 若不存则新建

  • m.putAll(map): 加入map中所有键值对, 重复的进行覆盖

  • m.remove(key): 删除指定键值对

  • m.clear(): 删除所有键值对

  • m.keySet(): 返回所有键组成的java.util.Set

  • m.values(): 返回所有值组成的java.util.Collection

遍历Map需要Entry的帮助:

Map<String, String> map = new HashMap<>();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

Iterator

java.util.Iterator定义了迭代器接口, 在使用迭代器遍历容器时不需要了解容器的具体数据结构.

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
    String str = iter.next();
    System.out.println(str);
}

iter.next()方法会返回游标指向的元素, 并将游标后移一位. iter.hasNext()用于检测是否完成了遍历.

iter.remove()方法可以删除迭代器上一次返回的元素, 通常迭代器无法添加元素.

迭代器由ArrayList等实现类提供, 上文提及的foreach遍历实际上是通过容器的迭代器实现的.

Set容器的迭代器示例:

Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
Iterator<String> iter = set.iterator();
while(iter.hasNext()) {
    String str = iter.next();
    System.out.println(str);
}

Map容器的迭代器同样依赖于EntrySet:

Map<String, String> map = new HashMap<>();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
    Map.Entry<String, String>  entry = iter.next();
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

ListIterator

List接口除了iterator()外还提供了功能更强的listIterator(). listIterator()拥有iterator()的全部功能, 并且可以双向遍历和添加元素.

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
ListIterator<String> iter = list.listIterator();
while(iter.hasNext()) {
    String str = iter.next();
    System.out.println(str);
}
iter.add("d");
while(iter.hasPrevious()) {
    String str = iter.previous();
    System.out.println(str);
}

fail-fast && fail-safe

Collections框架中的常规容器使用fail-fast迭代器, 如ArrayListHashMap并发容器通常使用fail-safe迭代器.

fail-fast迭代器会记录容器发生结构性改变(添加删除元素)的次数, 若迭代过程中容器发生了结构性改变fail-fast迭代器会抛出运行时异常终止迭代.

迭代过程中的结构性改变通常是由于其它线程修改了容器, 终止迭代可以避免出现不一致的错误.

CopyOnWriteArrayListConcurrentHashMap等并发容器则使用fail-safe迭代器, fail-safe迭代器在容器的快照(snapshot)上进行遍历. 容器发生结构性改变时快照不受影响, fail-safe迭代器可以正常完成遍历.

相对于只提供访问接口不存储数据的fail-fast迭代器, fail-safe迭代器需要复制集合快照,产生较大开销. fail-safe迭代器基于快照的访问方式虽然可以在并发环境下正常遍历, 但无法保证访问到最新数据.

本文只对fail-fast与fail-safe迭代器进行简单介绍, 在各容器的源码解析中将进一步介绍迭代器的实现.

enum

Java使用enum关键字来定义枚举:

enum Season {
    Spring, Summer, Autum, Winter;
}

public class Main {

    public static void main(String[] args) {
        System.out.println(Season.Spring);  // print: Spring
    }
}

enum关键字与class关键字的地位相同, enum也可以定义其它属性和方法.

enum中的每个枚举值类似于一个实例, 我们可以在enum中为其定义构造方法.

enum Season {
    Spring(2, 4), Summer(5, 7), Autum(8, 10), Winter(11, 1);

    public int start;
    public int stop;

    private Season(int start, int stop) {
        this.start = start;
        this.stop = stop;
    }

}

public class Main {

    public static void main(String[] args) {
        System.out.println(Season.Spring);  // Spring
        System.out.println(Season.Spring.start); // 2
    }
}

注意, enum的构造器只能是私有的(private).

BitSet

java.util.BitSet是位的容器, 我们可以用它保存标志位等数据:

BitSet bs = new BitSet(5);

BitSet提供了一些方法用于操作:

  • bs.get(i): 以boolean的形式返回第i位, 下标从0开始

  • bs.get(start, end): 以BitSet的形式返回[start, end)中的字节.

  • bs.set(i, b): 将第i位设为布尔值b, 省略b时设为true.

  • bs.equals(c): 判断与容器c内容是否相同

  • bs.clear(): 将所有位设为false

  • bs.size(): 返回总位数

  • bs1.and(bs2): 进行位与运算, 返回BitSet

  • bs1.andNot(bs2): 进行位与非运算, 返回BitSet

  • bs1.or(bs2): 进行位或运算, 返回BitSet

  • bs1.xor(bs2): 进行位异或运算, 返回BitSet