JAVA集合
由若干个确定的元素所构成的整体就是集合
数据就是集合
java中的数组就是符合这个条件的 多个确定的元素的一个整体
既然有了数组为什么还要提供其他的集合呢。PHP里就只有数组的概念
原因:数组有缺点如下: 简单说就是必须确定数组的长度,显然不是很实用,获取方式不方便
1. 数组初始化后大小不可变;
2. 数组只能按索引顺序存取。
为了解决这两个问题:要出现一个可变长的数组 和 一个利于存取的 并且提供更多特性的东西 所以出现了集合
Collection
集合在java标准库的java.util包下
Collection是除map外其他所有包的根接口
集合一共分为三种:
List 一种有序列表的集合
Set 一种保证没有重复元素的集合
Map 一种通过键值(key-value)查找的映射表集合
集合的JAVA设计原理:
1. 实现了接口和类分离 例如,有序表的接口是List,具体的实现类有ArrayList,LinkedList等,
2. 支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素
3. Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的
已经将被废弃的类,不要使用
Hashtable:一种线程安全的Map实现;
Vector:一种线程安全的List实现;
Stack:基于Vector实现的LIFO的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
Enumeration<E>:已被Iterator<E>取代。
List
list是一种有序列表.
List有两种实现:ArrayList 和 LinkedList
ArrayList LinkedList
获取指定元素 速度很快 需要从头开始查找元素
添加元素到末尾 速度很快 速度很快
在指定位置添加/删除 需要移动元素 不需要移动元素
内存占用 少 较大
内存状态:
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘
┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐
HEAD ──>│ A │ ●─┼──>│ B │ ●─┼──>│ C │ ●─┼──>│ D │ │
└───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘
通常情况下,我们总是优先使用ArrayList。
所以重点介绍ArrayList
在末尾添加一个元素:void add(E e)
在指定索引添加一个元素:void add(int index, E e)
删除指定索引的元素:int remove(int index)
删除某个元素:int remove(Object e)
获取指定索引的元素:E get(int index)
获取链表大小(包含元素的个数):int size()
创建List的第三种方式:
List<Integer> list = List.of(1, 2, 5);
遍历List
第一种是:for循环,不推荐这么使用。原因:1. 代码复杂 2. get(int)方法只有ArrayList的实现是高效的换成LinkedList后,索引越大,访问速度越慢
第二种: 我们要始终坚持使用迭代器Iterator来访问List。
Iterator本身也是一个对象,这个对象有两个方法hasNext() next()
这个对象由List的实例调用iterator()方法的时候创建的
Iterator it = list.iterator()
例如:
List<String> list = List.of("iterator", "for", "foreach");
for (Iterator<String> it = list.iterator(); it.hasNext();) {
System.out.println(it.next);
}
是不是这样代码也很啰嗦。没关系java提供了foreach
for(String s : list)
实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历,
Java编译器本身并不知道如何遍历集合对象,
但它会自动把for each循环变成Iterator的调用,
原因就在于Iterable接口定义了一个Iterator<E> iterator()方法,
强迫集合类必须返回一个Iterator实例
List和Array相互转换
List转Array三种方式一个方法toArray
1. toArray()直接返回一个Object[]数组
List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
for (Object s : array) {
System.out.println(s);
}
2. toArray(T[])传入一个类型相同的Array,List内部自动把元素复制到传入的Array中
Integer[] array = list.toArray(new Integer[list.size()]);
3. Integer[] array = list.toArray(Integer[]::new);
Array转变成List
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);
对于JDK 11之前的版本,可以使用Arrays.asList(T...)方法把数组转换成List。
Arrays.asList()
要注意的是,List不一定就是ArrayList或者LinkedList,
因为List只是一个接口,如果我们调用List.of(),它返回的是一个只读List:
对只读List调用add()、remove()方法会抛出UnsupportedOperationException。
编写equals方法
为什么要编写equals 当List调用contains,indexOf的时候可能找不到。是因为对象无法用==来判断是否相等,这个时候要覆写equals
在哪编写equals 在进行比较的对象里覆写。一般编辑器都有快捷键自动生成代码
例子:
List<String> list = List.of("A", "B", "C");
System.out.println(list.contains(new String("C"))); // true 因为String里已经覆写了
System.out.println(list.indexOf(new String("C"))); // 2 因为String里已经覆写了
List<Person> list = List.of(
new Person("Xiao Ming"),
new Person("Xiao Hong"),
new Person("Bob")
);
System.out.println(list.contains(new Person("Bob"))); // 如果没有覆写就是false
class Person {
String name;
public Person(String name) {
this.name = name;
}
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
Map这种键值(key-value)映射表的数据结构 作用就是能高效通过key快速查找value(元素)
实现是HashMap
boolean containsKey(K key)
get put
遍历Map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
for (String key : map.keySet()) {
注意:遍历时map是无序的
使用EnumMap
Map 属于key value格式的
当key是枚举类型是,java提供了一个EnumMap,
为什么要单独提供一个类,是为了是效率更高:
它在内部以一个非常紧凑的数组存储value
并且根据enum类型的key直接定位到内部数组的索引
并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费
Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
map.put(DayOfWeek.MONDAY, "星期一");
map.put(DayOfWeek.TUESDAY, "星期二");
map.put(DayOfWeek.WEDNESDAY, "星期三");
map.put(DayOfWeek.THURSDAY, "星期四");
map.put(DayOfWeek.FRIDAY, "星期五");
map.put(DayOfWeek.SATURDAY, "星期六");
map.put(DayOfWeek.SUNDAY, "星期日");
System.out.println(map);
System.out.println(map.get(DayOfWeek.MONDAY));
TreeMap
TreeMap的key是有序的,HashMap是无序的
┌───┐
│Map│
└───┘
▲
┌────┴─────┐
│ │
┌───────┐ ┌─────────┐
│HashMap│ │SortedMap│
└───────┘ └─────────┘
▲
│
┌─────────┐
│ TreeMap │
└─────────┘
SortedMap保证遍历时以Key的顺序来进行排序。
例如,放入的Key是"apple"、"pear"、"orange",
遍历的顺序一定是"apple"、"orange"、"pear",
因为String默认按字母排序:
Map<String, Integer> map = new TreeMap<>();
map.put("orange", 1);
map.put("apple", 2);
map.put("pear", 3);
for (String key : map.keySet()) {
System.out.println(key);
}
// apple, orange, pear
因为是有序的所以key必须实现Comparable接口
上边的例子是String类型的key已经实现了
如果是自己定义的类,
第一种方法就是就要实现Comparable接口
第二种方法就是
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
})
map.put(new Person("Tom"), 1);
map.put(new Person("Bob"), 2);
map.put(new Person("Lily"), 3);
for (Person key : map.keySet()) {
System.out.println(key);
}
// {Person: Bob}, {Person: Lily}, {Person: Tom}
System.out.println(map.get(new Person("Bob"))); // 2
看到了吗 返回的是2,说明找到了怎么找的呢,
不是HashMap那种equals和hashcode方式了
因为是有序的,所以是用过compare的比较name值找到的。如果compare不返回0的话就肯定找不到啊
使用Properties
读文件或读流
Properties props = new Properties();
//从文件读
String f = "setting.properties";
props.load(new java.io.FileInputStream(f));
//读流
props.load(getClass().getResourceAsStream("/common/setting.properties"));
//从内存读
String settings = "# test" + "\n" + "course=Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
props.load(input);
//如上可以多次使用load,读多个文件
写文件
Properties props = new Properties();
props.setProperty("url", "http://www.liaoxuefeng.com");
props.setProperty("language", "Java");
props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
编码
早期版本的Java规定.properties文件编码是ASCII编码(ISO8859-1),
如果涉及到中文就必须用name=\u4e2d\u6587来表示,非常别扭。
从JDK9开始,Java的.properties文件可以使用UTF-8编码了。
props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
Java集合库提供的Properties用于读写配置文件.properties。.properties文件可以使用UTF-8编码。
可以从文件系统、classpath或其他任何地方读取.properties文件。
读写Properties时,注意仅使用getProperty()和setProperty()方法,不要调用继承而来的get()和put()等方法。因为Hashtable已经废弃
set
我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set
将元素添加进Set<E>:boolean add(E e)
将元素从Set<E>删除:boolean remove(Object e)
判断是否包含元素:boolean contains(Object e)
Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素
HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
TreeSet是有序的,因为它实现了SortedSet接口。
使用TreeSet和使用TreeMap的要求一样,添加的元素必须正确实现Comparable接口,
如果没有实现Comparable接口,那么创建TreeSet时必须传入一个Comparator对象
Set<MyObj> set = new TreeSet<>(){new Comparator<MyObj>() {
public int compare(MyObj p1, MyObj p2) {
return p1.name.compareTo(p2.name);
}
}
queue
队列 跟超时排队买东西,后来的只能在队尾加进去
获取队列长度:size()
boolean add(E) (这个添加失败抛异常)/ boolean offer(E)(这个添加失败返回false):添加元素到队尾
E remove(这个娶不到抛出异常)/E poll() 这个返回为null: 获取并删除
E element()/E peek(): 获取但不删除
注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。
// 这是一个List:
List<String> list = new LinkedList<>();
// 这是一个Queue:
Queue<String> queue = new LinkedList<>();
PriorityQueue
PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,
对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素
Queue<User> q = new PriorityQueue<>(new UserComparator());
class UserComparator implements Comparator<User> {
public int compare(User u1, User u2) {
if (u1.number.charAt(0) == u2.number.charAt(0)) {
// 如果两人的号都是A开头或者都是V开头,比较号的大小:
return u1.number.compareTo(u2.number);
}
if (u1.number.charAt(0) == 'V') {
// u1的号码是V开头,优先级高:
return -1;
} else {
return 1;
}
}
}
Deque
允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue),学名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()
对于添加元素到队尾的操作,Queue提供了add()/offer()方法,而Deque提供了addLast()/offerLast()方法。
添加元素到对首、取队尾元素的操作在Queue中不存在,在Deque中由addFirst()/removeLast()等方法提供。
注意到Deque接口实际上扩展自Queue:
public interface Deque<E> extends Queue<E> {
...
}
Deque<String> d2 = new LinkedList<>();
d2.offerLast("z");
LinkedList全能小助手
Deque<String> d2 = new LinkedList<>();
Queue<String> d2 = new LinkedList<>();
List<String> d2 = new LinkedList<>();
Stack(栈)
栈(Stack)是一种后进先出(LIFO:Last In First Out)的数据结构
把元素压栈:push(E);
把栈顶的元素“弹出”:pop(E);
取栈顶元素但不弹出:peek(E)。
在Java中,我们用Deque可以实现Stack的功能:
当我们把Deque作为Stack使用时,注意只调用push()/pop()/peek()方法,
不要调用addFirst()/removeFirst()/peekFirst()方法,这样代码更加清晰。
使用Iterator
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
创建空集合
创建空List:List<T> emptyList()
创建空Map:Map<K, V> emptyMap()
创建空Set:Set<T> emptySet()
要注意到返回的空集合是不可变集合,无法向其中添加或删除元素。
List<String> list1 = List.of();
List<String> list2 = Collections.emptyList();
以上这两个空集合是等价的
创建单元素集合
Collections提供了一系列方法来创建一个单元素集合:
创建一个元素的List:List<T> singletonList(T o)
创建一个元素的Map:Map<K, V> singletonMap(K key, V value)
创建一个元素的Set:Set<T> singleton(T o)
要注意到返回的单元素集合也是不可变集合,无法向其中添加或删除元素。
排序
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("pear");
list.add("orange");
Collections.sort(list);
System.out.println(list);
}
}
洗牌
Collections.shuffle(list);
不可变集合
Collections还提供了一组方法把可变集合封装成不可变集合:
封装成不可变List:List<T> unmodifiableList(List<? extends T> list)
封装成不可变Set:Set<T> unmodifiableSet(Set<? extends T> set)
封装成不可变Map:Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> mutable = new ArrayList<>();
mutable.add("apple");
mutable.add("pear");
// 变为不可变集合:
List<String> immutable = Collections.unmodifiableList(mutable);
// 立刻扔掉mutable的引用:
mutable = null;
System.out.println(immutable);
}
}