使用List
boolean add(E e) 在末尾添加一个元素
boolean add(int index, E e) 在指定索引添加一个元素
int remove(int index) 删除指定索引的元素
int remove(Object e) 删除某个元素
E get(int index) 获取指定索引的元素:
int size() 获取链表大小(包含元素的个数)
boolean contains(Object o) 方法来判断List是否包含某个指定元素
int indexOf(Object o) 方法可以返回某个元素的索引,如果元素不存在,就返回-1
List还允许添加null
List<String> list = new ArrayList<>();
list.add("apple"); // size=1
list.add("pear"); // size=2
list.add("apple"); // 允许重复添加元素,size=3
System.out.println(list.size());
创建List
//java9
List<Integer> list = List.of(1, 2, 5);
//java8
List<Integer> list = Arrays.asList(1, 2, 5);
- Arrays.asList返回可变的list,而List.of返回的是不可变的list
- Arrays.asList支持null,而List.of不行
- Arrays.asList:数组的修改会影响原数组。
坚持使用迭代器Iterator来访问List
List<String> list = Arrays.asList("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
或者
List<String> list = Arrays.asList("apple", "pear", "banana");
for (String s : list) {
System.out.println(s);
}
List和Array转换
//Array ===>list
Number[] array = list.toArray(new Number[3]);
Integer[] array = list.toArray(new Integer[list.size()]);
Integer[] array = list.toArray(Integer[]::new);
//list ===>Array
Integer[] array = { 1, 2, 3 };
List<Integer> list = Arrays.asList(array);
// 洗牌算法shuffle可以随机交换List中的元素位置:
Collections.shuffle(list);
list.contains(integer)//检查list集合是否包含integer元素
编写equals方法
- 自反性(Reflexive):对于非null的x来说,x.equals(x)必须返回true;
- 对称性(Symmetric):对于非null的x和y来说,如果x.equals(y)为true,则y.equals(x)也必须为true;
- 传递性(Transitive):对于非null的x、y和z来说,如果x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)也必须为true;
- 一致性(Consistent):对于非null的x和y来说,只要x和y状态不变,则x.equals(y)总是一致地返回true或者false;
- 对null的比较:即x.equals(null)永远返回false。
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
return Objects.equals(this.name, p.name) && this.age == p.age;
}
return false;
}
使用Map
put(K key, V value)
V get(K key)
boolean containsKey(K key). 只是想查询某个key是否存在
//遍历Map
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
或者
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。
遍历Map时,不可假设输出的key是有序的!
编写equals和hashCode
正确使用Map必须保证:
- 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true;
- 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:
- 如果两个对象相等,则两个对象的hashCode()必须相等;
- 如果两个对象不相等,则两个对象的hashCode()尽量不要相等。
@Override
int hashCode() {
return Objects.hash(firstName, lastName, age);
}
或者
@Override
int hashCode() {
int h = 0;
h = 31 * h + firstName.hashCode();
h = 31 * h + lastName.hashCode();
h = 31 * h + age;
return h;
}
实际上HashMap初始化时默认的数组大小只有16,加超过一定数量的key-value时,HashMap会在内部自动扩容,每次扩容一倍,即长度为16的数组扩展为长度32
更好的方式是创建HashMap时就指定容量
Map<String, Integer> map = new HashMap<>(10000);
我们把不同的key具有相同的hashCode()的情况称之为哈希冲突。在冲突的时候,一种最简单的解决办法是用List存储hashCode()相同的key-value。显然,如果冲突的概率越大,这个List就越长,Map的get()方法效率就越低。
如果两个对象不相等,则两个对象的hashCode()尽量不要相等。
使用EnumMap
如果作为key的对象是enum类型,那么,还可以使用Java集合库提供的一种EnumMap,它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。
使用TreeMap
使用TreeMap时,放入的Key必须实现Comparable接口。String、Integer这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。
如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
});
使用Properties
用Properties读取配置文件,一共有三步:
创建Properties实例;
调用load()读取文件;
调用getProperty()获取配置。
调用setProperty()写入配置。
String f = "setting.properties";
Properties props = new Properties();
props.load(new java.io.FileInputStream(f));
String filepath = props.getProperty("last_open_file");
String interval = props.getProperty("auto_save_interval", "120");
//也可以从classpath读取.properties
Properties props = new Properties();
props.load(getClass().getResourceAsStream("/common/setting.properties"));
## 写入
Properties props = new Properties();
props.setProperty("url", "http://www.liaoxuefeng.com");
props.setProperty("language", "Java");
props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
使用Set
Set用于存储不重复的元素集合,它主要提供以下几个方法:
- 将元素添加进Set:boolean add(E e)
- 将元素从Set删除:boolean remove(Object e)
- 判断是否包含元素:boolean contains(Object e)
Set接口并不保证有序,而SortedSet接口则保证元素是有序的:
- HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
- TreeSet是有序的,因为它实现了SortedSet接口。
使用Queue
队列(Queue)是一种经常使用的集合。Queue实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。它和List的区别在于,List可以在任意位置添加和删除元素,而Queue只有两个操作:
- 把元素添加到队列末尾;
- 从队列头部取出元素。
int size():获取队列长度;
boolean add(E)/boolean offer(E):添加元素到队尾;
E remove()/E poll():获取队首元素并从队列中删除;
E element()/E peek():获取队首元素但并不从队列中删除。
throw Exception | 返回false或null | |
---|---|---|
添加元素到队尾 | add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除 | E element() | E peek() |
要避免把null添加到队列。
优先队列PriorityQueue
放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。
//eg
public class Main {
public static void main(String[] args) {
Queue<User> q = new PriorityQueue<>(new UserComparator());
// 添加3个元素到队列:
q.offer(new User("Bob", "A1"));
q.offer(new User("Alice", "A2"));
q.offer(new User("Boss", "V1"));
System.out.println(q.poll()); // Boss/V1
System.out.println(q.poll()); // Bob/A1
System.out.println(q.poll()); // Alice/A2
System.out.println(q.poll()); // null,因为队列为空
}
}
class User {
public final String name;
public final String number;
public User(String name, String number) {
this.name = name;
this.number = number;
}
public String toString() {
return name + "/" + number;
}
}
class UserComparator implements Comparator<User> {
public int compare(User u1, User u2) {
if (u1.number.charAt(0) == u2.number.charAt(0)) {
// 如果两人的号都是A开头或者都是V开头,比较号的大小:
if(u1.number.length() < u2.number.length()) {return -1;} // 比较number长度,短的优先
if(u1.number.length() > u2.number.length()) {return 1;} // 比较number长度,短的优先
return u1.number.compareTo(u2.number); // 长度一致时,再用compareTo比较字符串
}
if (u1.number.charAt(0) == 'V') {
// u1的号码是V开头,优先级高:
return -1;
} else {
return 1;
}
}
}
使用Deque双端队列
Queue | Deque | |
---|---|---|
添加元素到队尾 | add(E e)/offer(E e) | addLast(E e) / offerLast(E e) |
取队首元素并删除 | E remove() /E poll() | E removeFirst() / E pollFirst() |
取队首元素但不删除 | E element()/E peek() | E getFirst() / E peekFirst() |
添加元素到队首 | addFirst(E e) / offerFirst(E e) | |
取队尾元素并删除 | E removeLast() / E pollLast() | |
取队尾元素但不删除 | E getLast() / E peekLast() |
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
deque.offerLast("A"); // A
deque.offerLast("B"); // B -> A
deque.offerFirst("C"); // B -> A -> C
System.out.println(deque.pollFirst()); // C, 剩下B -> A
System.out.println(deque.pollLast()); // B
System.out.println(deque.pollFirst()); // A
System.out.println(deque.pollFirst()); // null
}
使用Stack
栈(Stack)是一种后进先出(LIFO:Last In First Out)的数据结构。
Stack只有入栈和出栈的操作:
把元素压栈:push(E);
把栈顶的元素“弹出”:pop(E);
取栈顶元素但不弹出:peek(E)。
在Java中,我们用Deque可以实现Stack的功能:
把元素压栈:push(E)/addFirst(E);
把栈顶的元素“弹出”:pop(E)/removeFirst();
取栈顶元素但不弹出:peek(E)/peekFirst()。
public static String toHexString(int i)转化为16进位
boolean isEmpty(); 判断集合是否为空
// 转十六进制
public static void main(String[] args) {
String hex = toHex(12500);
if (hex.equalsIgnoreCase("30D4")) {
System.out.println("测试通过");
} else {
System.out.println("测试失败");
}
}
static String toHex(int a) {
Deque<String> dd = new LinkedList<>();
int x = 0;
for(int k=0;k<10;k++){
x = a%16;
a = a/16;
if(x < 10){
dd.push(String.valueOf(x));
}else{
switch(x){
case 10:
dd.push("a");
continue;
case 11:
dd.push("b");
continue;
case 12:
dd.push("c");
continue;
case 13:
dd.push("d");
continue;
case 14:
dd.push("e");
continue;
case 15:
dd.push("f");
continue;
}
}
if(a==0)break;
}
return dd.pop() + dd.pop() + dd.pop() + dd.pop();
}
//请利用Stack把字符串中缀表达式编译为后缀表达式,然后再利用栈执行后缀表达式获得计算结果:
public class Main {
public static void main(String[] args) {
// --------- stack 中缀表达式编译为后缀表达式 ---------------------
String exp = "1 + 2 * (9 - 5)";
SuffixExpression se = compile(exp);
Float result = se.execute();
System.out.println(exp + " = " + result + " " + (result == 1 + 2 * (9 - 5) ? "✓" : "✗"));
}
static SuffixExpression compile(String exp) {
// TODO:中缀转后缀
// 1.按次序读取中缀表达式的字符。
// 2.读到一个操作数的时候,立即放入到输出中。
// 3.读到操作符“+”,“-”,“*”,“/”,则从栈中弹出栈元素并输出,直到遇到优先级更低或者“(”的为止操作符为止(该元素不出栈)。
// 4.读到操作符“(”,则直接把“(”压入栈中。
// 5.读到操作符“)”,则从栈中弹出栈元素并输出,直到遇到第一个“(”为止。其中“(”不再添加到输出中,而是直接舍弃。
// 6.当输入为空时,把栈里的操作符全部依次弹出并输出。
Deque<Character> rs= new LinkedList<>();
char[] cexp = exp.toCharArray();
String out = "";
for(int i = 0; i < cexp.length; i++) {
char ch = cexp[i];
if (ch == ' ') continue;
if (ch >= '0' && ch <= '9') {
out += ch;
continue;
}
if (ch == '(') {
rs.push(ch);
}
if (ch == '+' || ch =='-') {
while(!rs.isEmpty() && (rs.peek() != '(')) {
out += rs.pop();
}
rs.push(ch);
continue;
}
if (ch == '*' || ch =='/') {
while(!rs.isEmpty() && (rs.peek() == '*' || rs.peek() == '/')) {
out += rs.pop();
}
rs.push(ch);
continue;
}
if (ch == ')') {
while(!rs.isEmpty() && rs.peek() != '(') {
out += rs.pop();
}
rs.pop();
continue;
}
}
while(!rs.isEmpty()) out += rs.pop();
System.out.println(out);
return new SuffixExpression(out);
}
}
class SuffixExpression {
String exp;
public SuffixExpression(String exp) {
this.exp = exp;
}
public float execute() {
//TODO:根据后缀表达式 计算结果
// 1.按次序读取后缀表达式的每一个字符。
// 2.读取到操作数时,把操作数压入栈中。
// 3.读取到操作符时,对栈顶的2个操作数做相应运算,要注意操作数的前后顺序。结果压入栈中。
// 4.读取完所有的字符后,弹出栈。得到的值就是所求结果。
Deque<Float> cs= new LinkedList<>();
char[] cexp = exp.toCharArray();
for (int i = 0; i < cexp.length; i++) {
char ch = cexp[i];
if (ch>='0' && ch<='9') {
cs.push(Float.valueOf(ch - '0'));
continue;
} else {
cs.push(calculate(ch, cs.pop(), cs.pop()));
}
}
System.out.println(exp);
return cs.pop();
}
public float calculate(char op, Float f1, Float f2) {
if (op == '+') return f1 + f2;
else if (op == '-') return f2 -f1;
else if (op == '*') return f1 * f2;
else if (op == '/') return f2/f1;
else return Float.valueOf(-0);
}
}
使用Iterator
//把这种通过Iterator对象遍历集合的模式称为迭代器。
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有:
- 对任何集合都采用同一种访问模型;
- 调用者对集合内部结构一无所知;
- 集合类返回的Iterator对象知道如何迭代。
//一个简单的Iterator示例如下,它总是以倒序遍历集合:
import java.util.*;
public class Main {
public static void main(String[] args) {
ReverseList<String> rlist = new ReverseList<>();
rlist.add("Apple");
rlist.add("Orange");
rlist.add("Pear");
for (String s : rlist) {
System.out.println(s);
}
}
}
class ReverseList<T> implements Iterable<T> {
private List<T> list = new ArrayList<>();
public void add(T t) {
list.add(t);
}
@Override
public Iterator<T> iterator() {
return new ReverseIterator(list.size());
}
class ReverseIterator implements Iterator<T> {
int index;
ReverseIterator(int index) {
this.index = index;
}
@Override
public boolean hasNext() {
return index > 0;
}
@Override
public T next() {
index--;
return ReverseList.this.list.get(index);
}
}
}
使用Collections
创建空集合
collections提供了一系列方法来创建空集合:
创建空List:List emptyList()
创建空Map:Map<K, V> emptyMap()
创建空Set:Set emptySet()
用各个集合接口提供的of(T...)方法创建空集合。例如,以下创建空List的两个方法是等价的:
List<String> list1 = Arrays.asList();
List<String> list2 = Collections.emptyList();
创建单元素集合
Collections提供了一系列方法来创建一个单元素集合:
创建一个元素的List:List singletonList(T o)
创建一个元素的Map:Map<K, V> singletonMap(K key, V value)
创建一个元素的Set:Set singleton(T o)
List<String> list1 = Arrays.asList("apple");
List<String> list2 = Collections.singletonList("apple");
Collections.sort(list);排序
Collections.shuffle(list); 洗牌算法
不可变集合
Collections还提供了一组方法把可变集合封装成不可变集合:
- 封装成不可变List:List unmodifiableList(List<? extends T> list)
- 封装成不可变Set:Set unmodifiableSet(Set<? extends T> set)
- 封装成不可变Map:Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)
这种封装实际上是通过创建一个代理对象,拦截掉所有修改方法实现的。
线程安全集合
Collections还提供了一组方法,可以把线程不安全的集合变为线程安全的集合:
- 变为线程安全的List:List synchronizedList(List list)
- 变为线程安全的Set:Set synchronizedSet(Set s)
- 变为线程安全的Map:Map<K,V> synchronizedMap(Map<K,V> m)