1. 数组和集合
1.1 回顾数组
java是面向对象的语言。
我们可以使用一个java变量在JVM运行中保存一个值;也可以使用一个javabean对象在运行中保存一个更加复杂的值;当然也可以使用一个数组对象在运行中保存一堆类型相同的值。
也就是说,如果在程序运行中需要缓存一堆比较复杂的数据,那么我们就可以使用数组结合对象的方式,也就是对象数组。
下面我们来看一下:
package zeh.test.demo.com.jihe;
import java.util.Arrays;
// 使用对象数组缓存比较复杂的数据结构
public class TestArray {
public static void main(String[] args) {
// 使用简单类型,一个变量保存一份数据,即点性结构
int a = 10;
String str = "点性结构";
System.out.println("a = " + a);
System.out.println("str = " + str);
// 现在想保存一堆数据,比如想保存10个基本数值,那么就想到了数组,即线性结构
int[] ints = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println(Arrays.toString(ints));
// 我现在想保存一堆数据,里面每个数据的类型不同,比如我想直接保存一个名称和一个年龄的数据,那么我们想到了对象,java中的对象就是用来缓存多个类型不同的数据的
Data data = new Data();
data.setName("Eric");
data.setAge(19);
System.out.println("name = " + data.getName() + ";age = " + data.getAge());
// 如果我现在想保存的数据结构比较复杂,它像一张表一样,其中每一行表示一份数据,但是同时可以保存多行。我们自己想到了数组结合java对象,即对象数组
Data[] datas = new Data[]{new Data("zhagnsan", 12), new Data("lisi", 18), new Data("wangwu", 15)};
System.out.println(Arrays.toString(datas));
}
}
class Data {
private String name;
private int age;
public Data() {
}
public Data(String name, int age) {
this.setName(name);
this.setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "name = " + name + ";age = " + age;
}
}
使用对象数组,可以缓存比较复杂的数据结构,完全可以达到我们的要求。
然而数组有个比较蛋疼的问题,那就是数组在使用前必须指定长度。
这样就有缺陷了,我们使用数组去缓存数据时,需要考虑到长度的问题,而不能无限制的往数组里面塞数据。
并且,数组里面保存的都是相同类型的数据,不能同时保存几种完全不同的数据。
接下来我们使用java集合来试一下!
1.2 使用集合缓存一堆数据
package zeh.test.demo.com.jihe;
import java.util.ArrayList;
import java.util.List;
// 使用对象数组缓存比较复杂的数据结构
public class TestArray {
public static void main(String[] args) {
// 使用list列表来缓存任何数据类型的一堆数据,可以无限制的塞
List list = new ArrayList<>();
list.add(1);
list.add(true);
list.add("happy");
list.add(new Data("zhangsan", 22));
System.out.println(list);
}
}
class Data {
private String name;
private int age;
public Data() {
}
public Data(String name, int age) {
this.setName(name);
this.setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "name = " + name + ";age = " + age;
}
}
2. java集合
2.1 集合基础
- java集合的实现全部在java.util包中。
- java集合的出现拯救了对象数组长度有限制的缺陷。
- java集合的特征:高性能(底层数据结构很强大)、具备相同的方式和高度(意味着接口统一,建议集合内部保存的数据类型一致)。
- jdk 1.5之后为了操作集合更加安全,加入了泛型的支持,即防止我们随意放置任意类型的数据进去,避免了ClassCastException,保证类集中的数据类型高度统一。
- java中的类集主要有:Collection、List、Set、Map、Iterator等,这些都是jdk提供的类集接口。
- 一般调用集合中的add方法或者put方法向集合中存放元素;调用get方法获取缓存的元素。
- 遍历集合一般使用for循环或者使用标准迭代器Iterator进行遍历。
- foreach遍历输出集合元素时,不能同时对集合元素进行新增、修改和删除操作,只有满足这些条件才能使用foreach输出;换言之,foreach输出集合元素和数组时,只能以只读的方式进行输出,而不能一遍输出一遍修改。
- 如果想要一遍输出一遍修改集合元素,应该使用迭代器Iterator进行迭代输出。
2.2 类集框架主要接口
- Collection接口:存放一组单值数据的最大接口;单值是指集合中的每个元素都是一个单个对象。一般不直接使用此接口操作,而是使用其子接口,因为那样语义更加明确。
- List接口:Collection接口的子接口,最常用的单值集合接口。此接口对Collection接口进行了大量的扩充,里面的元素可以重复。元素先进先出,怎么放怎么取,本质上是个列表。
- Set接口:Collection接口的子接口。常用接口,没有对Collection接口进行扩充,里面不允许存放重复元素。元素按照某种规律排序,本质上是个集合。
- Map接口:存放一组复值的最大接口。即里面的每一个元素都是一个复值结构,就是我们常说的key-value元组结构(key-value键值对)。
- Iterator接口:集合对应的标准迭代接口,用于遍历输出集合中的元素,只能由前到后输出。
- ListIterator接口:Iterator接口的子接口,可以进行双向输出,只能输出List集合中的元素。
- Enumeration接口:早期的输出接口,只能输出Vector类中的元素。
- SortedSet接口:单值的排序接口,实现此接口的集合类,里面的元素可以按照比较器进行排序。
- SortedMap接口:存放一组复值的排序接口,实现此接口的集合类,里面元组中的key可以按照执行的比较器进行排序。
- Queue接口:队列接口,此接口的子类可以实现队列操作,比如LinkedList类就实现了Queue。Queue的主要特征是在先进先出的特性基础上,增加了操作队列头和队列尾的API。
- Map.Entry接口:Map接口的内部接口,每个Map.Entry对象都保存着一个元组(key-value键值对),每个Map接口对象中都保存着一堆Map.Entry的实例对象。
接口关系如下:
Collection接口的子接口:List、Set、Queue、SortedSet。
Map接口的子接口:SortedMap
2.3 常见的集合接口实现类
说白了学习java集合就是在学习根接口Collection和Map的各个API特性。
接口没法实例化,因此我们要直接定位到这俩接口的常见实现类上。
- Collection接口的实现类:
(1) AbstractCollection抽象类:实现了接口中的部分方法,不能直接实例化。
(2) AbstractList抽象类:继承于AbstractCollection 并且实现了大部分List接口。
(3) AbstractSequentialList抽象类:继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问,即包装成了链表。
(4) LinkedList实现类:该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:List list=Collections.synchronizedList(newLinkedList(…));LinkedList 查找效率低。
(5) ArrayList实现类:该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。
(6) AbstractSet抽象类:继承于AbstractCollection 并且实现了大部分Set接口。
(7) HashSet实现类:该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个。
(8) LinkedHashSet实现类:具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。
(9) TreeSet实现类:该类实现了Set接口,可以实现排序等功能。
(10)Vector实现类:最早的列表实现,实现了List接口。
(11)Stack实现类:Vector的子类,实现了栈。 - Collection接口的实现类:
(1) AbstractMap抽象类:实现了大部分的Map接口。
(2) HashMap实现类:HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。
(3) TreeMap实现类:继承了AbstractMap,并且使用一颗树。
(4) WeakHashMap实现类:继承AbstractMap类,使用弱密钥的哈希表。
(5) LinkedHashMap实现类:继承于HashMap,使用元素的自然顺序对元素进行排序.
(6) IdentityHashMap实现类:继承AbstractMap类,比较文档时使用引用相等。
(7) Hashtable实现类:实现了Map。
(8) Properties实现类:Properties 继承于 Hashtable,表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串。
3. Collection单值接口
3.1 Collection接口说明
- Collection接口是存放一组单值元素的根接口。List接口和Set接口是其最常见的两个子接口。
- 开发中一般很少直接使用Collection接口,而是使用其子接口List或者Set。
3.2 List接口和Set接口的区别是啥?
口诀: List接口可重复,Set接口决不能。 List接口大扩充,Set接口纯继承。 List接口不排序,Set接口有规律。说明: List接口中的元素可以重复保存,而Set接口中的元素不能重复保存。
List接口对Collection接口进行了大量的扩充操作,增加了很多自己的方法;而Set接口几乎只是继承了Collection接口中的方法。
List接口中的元素在输出时就是按照代码中放入的顺序进行输出的,即先进先出,按照什么顺序保存的就按照什么顺序输出;而Set接口是按照某种规律输出的。
备注:List接口底层是数组或者双向循环链表,而Set接口底层是数组+单向链表,所以List又叫做集合列表,Set才是真正的集合结构。
3.3 实现类ArrayList类和Vector类的区别?
- 推出时间:ArrayList是jdk1.2之后推出的,比较新;Vector类是jdk1.0就有,属于元老级别的集合类。
- 性能:ArrayList采用异步处理方式,里面的实现方法基本没有synchronized的,所以性能更高;Vector采用大量的同步操作,性能较低。
- 线程安全:ArrayList没有采用同步操作,因此非线程安全;Vector采用了同步操作,因此线程安全。
- 输出:ArrayList只能使用foreach、Iterator等输出;Vector可以使用foreach、Iterator、Enumeration等输出。
tips:什么叫线程安全的操作类?什么又是非线程安全的操作类?
线程安全,与多线程中的概念一致。意味着Vector类中实现的方法大量使用了同步操作,即使用了大量的Synchronized关键字进行了同步上锁,正因为这样,才影响了性能。
反之,ArrayList类的实现中没有使用Synchronized关键字进行上锁操作,所以性能较高;但同时又是非线程安全的。当多线程环境下,多个线程对象同时操作ArrayList对象时,将有可能出现线程安全问题。
必须要注意一点:尽管ArrayList类没有实现同步操作,但是也不能一味的认为操作ArrayList对象就一定出现线程安全问题。
还是那句话,是否出现线程安全问题,首先要看ArrayList在多线程环境下是否作为了“共享可变成员”。
4. Map复值接口
4.1 Map接口说明
- Map接口是存放一组复值的根接口。
- Map中存放的每个元素都是一个Map.Entry对象,Entry接口是Map接口的内部接口,专门用来存放一个键值对key-value元组对象。同时Entry使用static声明,因此直接使用Map调用即Map.Entry。
- Map.Entry对象一般在输出Map集合元素时会使用到。
- Map接口同样使用了泛型操作,保证了数据类型高度统一,避免了类转换异常。
- Map接口的常用子类:
(1)、HashMap实现类:无顺序存放的(怎么放怎么取),是新的操作子类,key不允许重复(如果key重复了则后来者居上,覆盖掉之前的key);key允许为null值;线程不安全,异步处理,效率高,推出时间晚。
(2)、Hashtable实现类:无顺序存放的,是旧的操作子类,key不允许重复;key不允许为null值;线程安全,同步处理,效率低,推出时间最早。
(3)、TreeMap实现类:可以排序的Map集合,按集合中的key排序(集合中的key需要实现比较器Comparable或者Comparator),key不允许重复;key不允许为null值;线程不安全,异步处理,效率高,推出去时间晚。
(4)、WeakHashMap实现类:弱引用的Map集合,当集合中的某些内容不再使用时清除掉无用的数据,使用gc进行回收;线程不安全,异步处理,效率高,推出时间晚。
(5)、IdentityHashMap实现类:key可以重复的map集合。
4.2 Map接口常用API
public void clear():清空map集合内容。
public boolean containsKey(Object key):判断目标key是否在当前map集合中存在。
public boolean containsValue(Object value):判断目标value是否在当前map集合中存在。
public Set<Map.Entry<k,v>> entrySet():将当前map对象转换为set集合,一般用于map集合的遍历输出。
public boolean equals(Object o):对象比较。map自己实现了equals()方法,比较的是当前map对象和指定的目标对象是否内容相同。更确切地讲,如果 m1.entrySet().equals(m2.entrySet()),则两个映射 m1 和 m2 表示相同的映射关系。
public int hashCode():返回当前map对象的hash码。当前map对象覆盖了该方法。
public V get(Object key):根据key获取对应的value值。
public boolean isEmpty():判断当前map集合是否为空集合。
public Set<K> keySet():将map对象中的所有key转换为Set集合。
public V put(K key,V value):将元组key-value加入到当前map集合。
public void putAll(Map<? extends K,? extends V> t):将一个map集合对象加入到当前map集合。
public V remove(Object key):根据key删除对应的key-value元组,并返回被删除的value;如果key不存在,则返回null值。
public int size():返回map集合的大小(即Map.Entry<K,V> 对象的个数)。
4.3 Map.Entry接口常用API
public boolean equals(Object o):比较指定的对象o是否与当前的entry对象相等。如果指定对象也是一个map.entry对象且表示完全相同的映射关系,则二者相同;否则不相同。
public K getKey():取得map.Entry对象的key。
public V getValue():取得map.Entry对象的value。
public int hashCode():取得map.Entry对象的hash码。
public V setValue(V value):为当前map.Entry对象设置value值。
4.4 Map接口案例
文档信息
- 本文作者:Marshall