Java基础——集合类 左Collection,右Map。
集合类简介
为什么出现集合类?
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就要对对象进行存储,集合就是存储对象最常用的一种方式。
集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象。
集合与数组
数组虽然也可以存储对象,但长度是固定的;集合长度是可变的。数组中可以存储任意数据类型,集合只能存储对象。
集合类简单结构图
集合类总的来说可以分类2大类,一类是Collection,另外一类是Map。
jhl.pngCollection 接口是最基本的集合接口,它不提供直接的实现,Java SDK提供的类都是继承自 Collection 的“子接口”如 List 和 Set。
在 Java 中所有实现了 Collection 接口的类都必须提供两套标准的构造函数,
一个是无参,用于创建一个空的 Collection,
一个是带有 Collection 参数的有参构造函数,用于创建一个新的 Collection,这个新的 Collection 与传入进来的 Collection 具备相同的元素。
实现了Collection接口的List接口
List 接口为 Collection 直接接口。List 所代表的是有序的 Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
实现 List 接口的集合主要有:ArrayList
、LinkedList
、Vector
、Stack
。
实现了Collection接口的Set接口
Set 是一种不包括重复元素的 Collection。它维持它自己的内部排序,所以随机访问没有任何意义。
与 List 一样,它同样运行 null 的存在但是仅有一个。由于 Set 接口的特殊性,所有传入 Set 集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。实现了 Set 接口的集合有:EnumSet
、HashSet
、TreeSet
。
关于Collection的子接口List和Set后文会详细描述,现在我们先来看一下 Collection 接口里面有那些基本方法
Collection的基本方法
-
boolean
add(Object o)
:该方法用于向集合里面添加一个元素,若集合对象被添加操作改变了,返回true. -
boolean
addAll(Collection c)
:把集合c里面的所有元素添加到指定集合里面去,如果集合对象被添加操作改变了返回true. -
void
clear()
:清除集合里面的所有元素,将集合长度变为0。 -
boolean
contains(Object o)
:返回集合里是否包含指定的元素。 -
boolean
containsAll(Collection c)
:返回集合里是否包含集合c内所有的元素。 -
boolean
isEmpty()
:返回集合是否为空(长度是否为0)。 -
Iterator
iterator()
:返回一个Iterator对象,用于遍历集合里的元素。 -
boolean
remove(Object o)
:删除集合中指定元素o。 -
boolean
removeAll(Collection c)
:从集合中删除集合c里面的元素。若删除一个或以上返回true。 -
boolean
retainAll(Collection c)
:从集合中删除集合c里不包含的元素。 -
int
size()
:得到集合元素的个数。 -
Object[]
toArray()
:把集合转成一个数组,所有集合元素编程数组元素。
.
.
.
二、Colletion —— List
二.1 List 接口 (有序可重复)
List 是有序
的 Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在 List 中的位置,类似于数组下标)来访问 List 中的元素,这类似于 Java 的数组。
和下面要提到的 Set 不同,List 允许有相同的
元素。
除了具有 Collection 接口必备的 iterator()方法外,List 还提供一个 listIterator()方法,返回一个 ListIterator 接口,和标准的 Iterator 接口相比,ListIterator 多了一些 add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。
实现 List 接口的常用类有 LinkedList,ArrayList,Vector 和 Stack。
.
.
.
二.1.1、ArrayList
- 查询速度快,适合随机访问
- 不同步
ArrayList 是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至·包括 null。每一个 ArrayL
st 都有一个初始容量(10)
,该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率
。
size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n) 时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间开销那样简单)。
ArrayList 擅长于随机访问。同时 ArrayList 是非同步的。
ArrayList一些自己的方法
- void add(int index,Object e):将元素e添加到List集合中的index处;
- boolean addAll(int index,Collection c):将集合c所包含的所有元素都插入在List集合的index处;
- Object get(int index):返回集合index索引处的元素;
- int indexOf(Object o):返回对象o在List集合中第一次出现位置的索引;
- int lastIndexOf(object o):返回对象o在List集合中最后一次出现的位置索引;
- Object remove(int index):删除并返回index索引处的元素;
- Object set(int index,Object e):把集合index处的元素替换为e对象,返回以前在指定位置的元素;
- List subList(int fromIndex,int toIndex):返回从所有fromIndex(包括)到toIndex(不包括)处所有集合元素的子集合。
.
.
.
二.1.2、LinkedList
- 增删速度快,涉及增删频繁的数据
- 非同步
LinkedList 实现了 List 接口,允许 null 元素。
ArrayList是一个动态数组,
而 LinkedList 是一个双向链表
。
LinkedList 除了有 ArrayList 的基本操作方法外还额外提供了 get,remove,insert 方法
在 LinkedList 的首部或尾部。
由于实现的方式不同,LinkedList 不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在 List 中进行插入和删除操作。
与 ArrayList 一样,LinkedList 也是非同步的。如果多个线程同时访问一个 List,则必须自己实现访问同步。一种解决方法是在创建 List 时构造一个同步的 List:
List list = Collections.synchronizedList(new LinkedList(…));
.
.
.
二.1.3、Vector
- 线程安全
与 ArrayList 相似,但是 Vector 是同步的。所以说 Vector 是线程安全的动态数组。它的操作与 ArrayList 几乎一样。
这个用的少,基本都是ArrayList多
.
.
.
二.1.4、Stack
Stack 继承自 Vector,实现一个后进先出的堆栈。Stack 提供 5 个额外的方法使得 Vector 得以被当作堆栈使用。基本的 push 和 pop 方法,还有 peek 方法得到栈顶的元素,empty 方法测试堆栈是否为空,search 方法检测一个元素在堆栈中的位置。Stack 刚创建后是空栈。
三、Collection —— Set
1、Set 接口
Set最大的特性就是不允许在其中存放的元素是重复的。Set 可以被用来过滤在其他集合中存放的元素,从而得到一个没有包含重复新的集合。
Set 是一种不包含重复的元素
的 Collection,即任意的两个元素 e1 和 e2 都有 e1.equals(e2)=false,Set 最多有一个 null 元素
。
很明显,Set 的构造函数有一个约束条件,传入的 Collection 参数不能包含重复的元素。
Set的实现原理是基于Map的。Set中很多实现类和Map中的一些实现类的使用上非常的相似。Map中的“键值对”,其中的 “键”是不能重复的。这个和Set中的元素不能重复一致,其实Set利用的就是Map中“键”不能重复的特性来实现的。
请注意:必须小心操作可变对象(Mutable Object)。如果一个 Set 中的可变元素改变了自身状态导致 Object.equals(Object)=true 将导致一些问题
。
Set的两个重要方法
-
public boolean add(Object o) :如果set中不存在指定元素,则向set加入 ;
-
public boolean addAll(Collection c) :过滤集合中的重复元素,向set加入 【将c中所有元素添加到Set中,如果Set中已有某一元素,则不添加,因Set不允许有重复值】;
三.1.1 HashSet
对于 HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素,所以如果对 HashMap 比较熟悉了,那么学习 HashSet 也是很轻松的。
HashSet的元素存放顺序和添加进去时候的顺序没有任何关系【HashSet类按照哈希算法来存取集合中的对象,存取速度比较快】【允许包含值为null的元素,但最多只能有一个null元素】;
HashSet的巧妙实现:就是建立一个“键值对”,“键”就是我们要存入的对象,“值”则是一个常量。这样可以确保, 我们所需要的存储的信息之是“键”。而“键”在Map中是不能重复的,这就保证了我们存入Set中的所有的元素都不重复。而判断是否添加元素成功,则是通 过判断我们向Map中存入的“键值对”是否已经存在,如果存在的话,那么返回值肯定是常量:PRESENT ,表示添加失败。如果不存在,返回值就为null 表示添加成功。
利用Set去重Demo
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
public class TestClass {
public static void main(String[] args) {
Set<String> nameSet = new HashSet<String>();
nameSet.add("张三");
nameSet.add("李四");
nameSet.add("王五");
nameSet.add("张三");
// 输出结果 张三李四王五
for(String name : nameSet){
System.out.print(name + "\t");
}
// List集合去除重复基础数据
List<String> nameList = new ArrayList<String>();
nameList.add("张三");
nameList.add("李四");
nameList.add("王五");
nameList.add("赵六");
nameSet.addAll(nameList);
// 输出结果 张三李四王五赵六
for(String name : nameSet){
System.out.print(name + "\t");
}
// 去除编号和用户名一样的 对象,需要重写 equals 方法 和 hashCode方法
User admin = new User(1, "admin");
User user = new User(2, "user");
User user1 = new User(2, "user");
User admin1 = new User(3, "admin");
Set<User> userSet = new HashSet<User>();
userSet.add(admin);
userSet.add(user);
userSet.add(admin1);
userSet.add(user1);
// 输入结果 admin1admin3user2
for(User u : userSet){
System.out.print(u.username + u.id + "\t");
}
System.out.println(user.equals(null));
}
}
class User{
protected Integer id;
protected String username;
public User(Integer id, String username){
this.id = id;
this.username = username;
}
/**
* 如果对象类型是User 的话 则返回true 去比较hashCode值
*/
@Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(this == obj) return true;
if(obj instanceof User){
User user =(User)obj;
//if(user.id = this.id) return true; // 只比较id
// 比较id和username 一致时才返回true 之后再去比较 hashCode
if(user.id == this.id && user.username.equals(this.username)) return true;
}
return false;
}
/**
* 重写hashcode 方法,返回的hashCode 不一样才认定为不同的对象
*/
@Override
public int hashCode() {
//return id.hashCode(); // 只比较id,id一样就不添加进集合
return id.hashCode() * username.hashCode();
}
}
三1.2 TreeSet
以红黑树的形式存储集合元素,使用元素的自然顺序对元素进行排序。整体性能比HashSet低
HashSet 和 TreeSet ,HashSet的整体性能总比TreeSet好,特别是添加和查询操作,只有当一个保持排序的Set时才使用TreeSet。
TreeSet则是对Set中的元素进行排序存放(TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序)。
三1.3 LinkedHashSet
保持元素的添加顺序;
增删快
四、Queue
队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。
(队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素)
Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口。
demo
public class Main {
public static void main(String[] args) {
//add()和remove()方法在失败的时候会抛出异常(不推荐)
Queue<String> queue = new LinkedList<String>();
//添加元素
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.offer("e");
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("element="+queue.element()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("peek="+queue.peek()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
}
}
输出
a
b
c
d
e
===
poll=a
b
c
d
e
===
element=b
b
c
d
e
===
peek=b
b
c
d
e
.
.
五、Map
Map 提供了一个更通用的元素存储方法。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。
Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false
如果使用自定义的类作为Map的key,应重新该类的equals方法和compareTo方法时应有一致的返回结果:即两个key通过equals方法比较返回true时,它们通过compareTo方法比较应该返回0。
Map集合与Set集合元素的存储形式很像,如Set接口下有HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet等实现类和子接口,而Map接口下则有HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap等实现类和子接口。
Map有时也称为字典,或关联数组。
Map中包括一个内部类:Entry。该类封装了一个key-value对,Entry包含三个方法:
Object getkey():返回该Entry里包含的key值。
Object getValue():返回该Entry里包含的value值。
Object setValue():设置该Entry里包含的value值,并返回新设置的value值。
可以把Map理解成一个特殊的Set,只是该Set里包含的集合元素是Entry对象,而不是普通对象。
- HashMap:非线程安全,速度快,无序,适用于在Map中插入、删除和定位元素。
- TreeMap: 线程安全,速度慢,有序,适用于按自然顺序或自定义顺序遍历键(key)。
五.1 Hashtable实现类
HashMap和Hashtable都是Map接口的实现类,Hashtable是一个古老的Map实现类,它从JDK1.0起就有,它包含两个烦琐的方法:elements()(类似于Map接口定义的values()方法)和keys()(类似于Map接口定义的keySet()方法),现在很少使用这两种方法。
- 同步,线程安全,如果多线程访问同一个Map对象,使用Hashtable实现类更好。
- 不允许null值,key和value都不可以
HashMap、Hashtable判断两个key相等的标准是:两个key通过equasl方法比较返回ture,两个key的hashCode值相等。
五.2 HashMap实现类
- 线程不安全,速度快
- HashMap可以使用null作为key或value。
(由于HashMap里的可以不能重复,所以HashMap里最多只有一对key-value值为null,但可以有无数多项key-value对的value为null。) - 适用于在Map中插入、删除和定位元素
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()]
LinkedHashMap (Linked了,当然就有序了)
HashMap有一个子类:LinkedHashMap,它也是双向链表来维护key-value对的次序,该链表定义了迭代顺序,该迭代顺序与key-value对的插入顺序保持一致。
LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可)。同时又可避免使用TreeMap所增加的成本。
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,但在迭代访问Map里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
五.3 TreeMap实现类
- 线程安全,速度慢
- 有序
- 适用于按自然顺序或自定义顺序遍历键(key)。
Map接口派生了一个SortedMap子接口,TreeMap为其实现类
。类似TreeSet排序,TreeMap也是基于红黑树对TreeMap中所有key进行排序,从而保证TreeMap中所有key-value对处于有序状态。TreeMap两种排序方法:
- 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有key应该是同一个类的对象,否则将会抛出ClassCastExcepiton异常。
- 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序。采用定制排序时不要求Map的key实现Comparable接口。
TreeMap中判断两个key相等的标准也是两个key通过equals比较返回true,而通过compareTo方法返回0,TreeMap即认为这两个key是相等的。
参考