一.java集合的分类:
- Set : 代表无序,不可重复的集合
- List:代表有序,可重复的集合
- Queue:代表一种队列集合实现,具有先进先出(FIFO)的特性
- Map:代表具有映射关系的集合
java集合就像是一个容器,可把多个对象(实际上是对象的引用)“丢进”该容器中。
java集合主要有两个接口派生而出:Collection和Map,这两个接口是java集合框架的根接口,其中Set和Lis接口是Collection接口派生出的子接口,代表了无序和有序集合;Queue是java提供的队列实现,类似于List,Collection接口继承树如下图:
Map接口的继承树:
Set, List, Map 集合的示意图
由上图可以知道,可以根据元素的索引来访问List集合,Map中根据key值来访问该元素的value值,而若要访问Set集合中的元素,则只能根据元素本身来访问(这也是Set集合中元素不能重复的原因)
二.Collection
Collection接口是List,Set,和Queue接口的父接口,定义了以下常用操作集合元素的方法:
1.List集合:
List接口扩展自Collection,它可以定义一个允许重复的有序集合,从List接口中的方法来看,List接口主要是增加了面向位置的操作,允许在指定位置上操作元素,同时增加了一个能够双向遍历线性表的新列表迭代器ListIterator。AbstractList类提供了List接口的部分实现,AbstractSequentialList扩展自AbstractList,主要是提供对链表的支持。下面介绍List接口的两个重要的具体实现类,也是我们可能最常用的类,ArrayList和LinkedList。
List集合常用方法如下:
1.1ArrayList(线程不安全)
通过阅读ArrayList的源码,我们可以很清楚地看到里面的逻辑,它是用数组存储元素的,这个数组可以动态创建,如果元素个数超过了数组的容量,那么就创建一个更大的新数组,并将当前数组中的所有元素都复制到新数组中。假设第一次是集合没有任何元素,下面以插入一个元素为例看看源码的实现。
1、找到add()实现方法。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2、此方法主要是确定将要创建的数组大小。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3、最后是创建数组,可以明显的看到先是确定了添加元素后的大小之后将元素复制到新数组中。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
1.2LinkedList
同样,我们打开LinkedList的源文件,不难看到LinkedList是在一个链表中存储元素。
在学习数据结构的时候,我们知道链表和数组的最大区别在于它们对元素的存储方式的不同导致它们在对数据进行不同操作时的效率不同,同样,ArrayList与LinkedList也是如此,实际使用中我们需要根据特定的需求选用合适的类,如果除了在末尾外不能在其他位置插入或者删除元素,那么ArrayList效率更高,如果需要经常插入或者删除元素,就选择LinkedList。
2.Set集合(无序不可重复):
Set接口扩展自Collection,它与List的不同之处在于,规定Set的实例不包含重复的元素。在一个规则集内,一定不存在两个相等的元素。AbstractSet是一个实现Set接口的抽象类,Set接口有三个具体实现类,分别是散列集HashSet、链式散列集LinkedHashSet和树形集TreeSet。
无序不可重复:无序指的是Set集合的排列顺序并不会按照你放入元素的顺序来排列,而是通过某些标准进行排列,不可重复指的是Set集合是不允许放入相同的元素,也就是相同的对象,对象相同是怎么判断的呢?Set集合是通过两个方法来判断其元素是否一样,这两个方法就是hashCode()方法和equlas()方法,首先,hashCode()方法返回的是一个哈希值,这个哈希值是由对象在内存中的地址所形成的,如果两个对象的哈希值不一样,那么这两个对象肯定是不相同的,如果哈希值一样,那么这还不能肯定这两个对象是否一样,还需要通过equlas()方法比较一下两个对象是否一样,equals()返回true才能说明这两个对象是相同的,所以当你想把你自定义的类对象放入此集合,最好重写一下hashCode()方法和equals()方法来保证Set集合”无序不可重复”的特点。判断两个对象是否一样是通过以下图片当中的流程,图片会让你更加理解。
2.1 HashSet:
HashSet是按Hash算法来存储集合中的元素,具有很好的存取和查找性能;
底层:使用哈希表实现
特点:非线程安全的,放入对象后,其对象的位置与对象本身的hashCode()方法返回的哈希值有关。判断重不重复,是通过对象本身的hashCode()方法和equals()方法决定的,所以当你想把你自定义的类对象放入此集合,最好重写一下hashCode()方法和equals()方法。
阿当向HashSet集合存入一个元素时,HashSet会调用该对象的hashCode() 方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储文职,如果有两个元素通过equals()方法返回true,但它们的hashCode() 方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以存储成功。
也就是说HashSet集合判断连个元素的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等;
接下来,就是演示如何使用这个HashSet集合了,还是从4个方面着手:增删改查
//HashSet的操作
//生成HashSet的实例对象
HashSet<String> hashSet = new HashSet<String>();
//HashSet的增加
hashSet.add("1");
hashSet.add("2");
hashSet.add("3");
hashSet.add("4");
hashSet.add("5");
//HashSet的删除
hashSet.remove("2");
//HashSet的修改
//因为HashSet的元素不存在通过下标去访问,所以修改操作是没有的
//HashSet的查询
//foreach语句方式
for(String str:hashSet){
if(str.equals("a")){
System.out.println("该集合中存在a元素");
}
}
//迭代器方式:
Iterator<String> intertor1 = hashSet.iterator();
while(intertor1.hasNext()){
if(intertor1.next().equals("a")){
System.out.println("该集合当中存在a元素");
}
}
2.2LinkedHashSet集合:
LinkedHashSet集合跟HashSet一样是根据放入的对象的hashCode()方法返回的哈希值来决定元素的存储位置,但是它同时使用链表维护元素的次序。为什么这么做呢?因为这样做可以让放入的元素像是以插入顺序保存的,换句话说,当遍历LinkedHashSet集合的时候,LinkedHashSet将会以元素的放入顺序来依次访问的。所以LinkedHashSet在遍历或者迭代访问全部元素时,性能方面LinkedHashSet比HashSet好,但是插入时性能就比HashSet差些。
3.Queue集合:
4.HashMap集合:
JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
底层:数组和链表的结合体(在数据结构称“链表散列“)
特点:非线程安全,当你往HashMap中放入键值对的时候,如果你放入的键是自定义的类,那么其该键值对的位置与键对象本身的hashCode()方法返回的哈希值有关。判断重不重复,是通过键对象本身的hashCode()方法和equals()方法决定的,所以当你想把你自定义的类对象通过键来放入HashMap集合,最好重写一下这个自定义类的hashCode()方法和equals()方法。其实Map主要的特点都是通过键来完成的,所以你只要封装好你的自定义类,就可以保证键值的唯一性。
下面来看看代码,HashMap的增删改查
//HashMap的操作
HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
//HashMap的添加
hashMap.put("1", 1);
hashMap.put("2", 2);
hashMap.put("3", 3);
hashMap.put("4", 4);
//HashMap的删除
hashMap.remove("2");//移除键值为"2"的元素
//HashMap的修改
hashMap.put("1", 11);//将键值为"1"的元素的键值覆盖为"11",修改其实就覆盖
//HashMap的查询,这里需要遍历HashMap
//1.通过遍历键的Set集合来遍历整个Map集合
System.out.println("foreach遍历");
for(String str:hashMap.keySet()){
System.out.println(str+":"+hashMap.get(str));
}
System.out.println("迭代器遍历");
Iterator<String> intertor = hashMap.keySet().iterator();
while(intertor.hasNext()){
String key = intertor.next();
System.out.println(key+":"+hashMap.get(key));
}
//2.使用Map集合的关系遍历
System.out.println("Map关系遍历");
for(Map.Entry<String, Integer> entry:hashMap.entrySet()){
System.out.println(entry.getKey()+":"+entry.getValue());
}