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)
: 转换为intDouble.parseDouble(s)
: 转换为double
StringBuffer 与 StringBuilder
java.lang.StringBuffer
与java.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中所有元素设为valArrays.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提供了HashSet
和TreeSet
两种实现.
Set<String> s = new HashSet<String>();
Set
接口声明了下列方法:
s.contians(e)
: 判断容器中是否包含元素e, 返回boolean值s.equals(c)
: 判断两个容器中包含的元素是否完全相同, 返回boolean值l.size()
: 返回容器中元素的个数l.add(e)
: 添加元素vall.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提供了HashMap
和TreeMap
两种实现.
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迭代器, 如ArrayList
和HashMap
并发容器通常使用fail-safe迭代器.
fail-fast迭代器会记录容器发生结构性改变(添加删除元素)的次数, 若迭代过程中容器发生了结构性改变fail-fast迭代器会抛出运行时异常终止迭代.
迭代过程中的结构性改变通常是由于其它线程修改了容器, 终止迭代可以避免出现不一致的错误.
CopyOnWriteArrayList
和ConcurrentHashMap
等并发容器则使用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()
: 将所有位设为falsebs.size()
: 返回总位数bs1.and(bs2)
: 进行位与运算, 返回BitSetbs1.andNot(bs2)
: 进行位与非运算, 返回BitSetbs1.or(bs2)
: 进行位或运算, 返回BitSetbs1.xor(bs2)
: 进行位异或运算, 返回BitSet