这篇博客我好一阵子以前就写了一点儿了,一直放在草稿箱里都快忘了,这几天突然又看见了,索性加加点,写完了吧,这里面写了我对集合的一些理解啥的,还有一些专业概念“官话”啥的,自己也背的没有那么专业味儿十足,就在bing上翻的,还有一些地方就是jdk1.8帮助文档的东西了,都是手敲的,自己脑子里的东西,希望能让大家读懂集合这个玩意儿吧。
谈到集合,我们肯定会想到数组这个概念,大家应该都清楚,在Java中,数组是一种效率最高的储存和随机访问对象引用序列的方式,数组就是一个十分简单的线性序列,因此使得元素访问非常快速。但是,也正是因为他的高效率的优点,使得他的数组对象的大小被固定了,它的容量需要事先被定义好,不能随着需求的变化而扩容,这也使得在工作中,数组并不实用。也正是由于数组自身存在的这一种劣势,我们才需要一种更强大的、更灵活的、容量可改变的“集合(Collection)”来储存我们的对象。额外的也不多说了,直接开始吧。
Java集合框架主要包括两种类型的容器:Collection和Map,Collection主要存储元素集合,Map则用来存储键值对映射。
Collection接口中有两个我们常用的子类型接口List和Set,他们常用的实现类有ArrayList、LinkedList、HashSet和TreeSet等等。
List介绍:
注意:虽然列表允许将其自身作为元素,但建议您非常小心: equals和hashCode方法在这样的列表中不再被很好地定义。
某些列表实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,对于不完成不会导致将不合格元素插入到列表中的不合格元素的操作,可能会在执行选项时抛出异常或成功。 此异常在此接口的规范中标记为“可选”。
List实现类的特点:
List接口具有如下特点:
ArrayList和LinkedList优缺点分析:
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
Vector:是一种老的动态数组,线程同步,效率十分低,一般不建议使用。
因此,当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。
Set介绍:
注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。
一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。
HashSet:
HashSet实现过程中判别元素是否重复
为了保证元素不重复,我们应该为保存到HashSet中的对象覆盖hashCode()和equals(),因为再将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此哈希值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加。
注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时, 也要尽量使不同对象尽量返回不同的Hash码值。
判断过程中使用HashCode判断和使用Equals判断存在先后,先判断HashCode,如果返回的哈希值相同,才会继续判断Equals,如果返回的哈希值不同,则直接判断为不重复元素。
TreeSet:
TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排列需要在TreeSet初始化时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法。
注意:此实现不同步。
TreeSet的自然排序的特点:
存入TreeSet集合中的元素要具备比较性
比较性要实现Comparable接口,重写该接口的compareTo方法
TreeSet属于Set集合,该集合的元素是不能重复的,TreeSet如何保证元素的唯一性
通过compareTo或者compare方法中的来保证元素的唯一性。
添加的元素必须要实现Comparable接口。当compareTo()函数返回值为0时,说明两个对象相等,此时该对象不会添加进来。
TreeSet集合排序的两种方式:
一,让元素自身具备比较性。
也就是元素需要实现Comparable接口,覆盖compareTo 方法。
这种方式也作为元素的自然排序,也可称为默认排序。
年龄按照搜要条件,年龄相同再比姓名。
二,让容器自身具备比较性,自定义比较器。
需求:当元素自身不具备比较性,或者元素自身具备的比较性不是所需的。
那么这时只能让容器自身具备。
定义一个类实现Comparator 接口,覆盖compare方法。
并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。
当Comparable比较方式,及Comparator比较方式同时存在,以Comparator
比较方式为主。
Map用于保存具有映射关系的数据,Map中保存着两组数据:Key和Value,它们可以是任何引用类的数据,但是Key不能重复。所以,每个 key 只能映射一个 value ,所以可以通过键查找值。
Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。
Map接口不是一个Collection接口的继承,而是从自己的用于维护键值关系的接口层次结构入手。定义该接口描述了从不重复的键到值的映射。
Map.Entry接口:
Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中的每一个对象都是底层Map中的一个特定的键值对。
接口中的方法有:
HashMap:
基于哈希表的实现的Map接口。 此实现提供了所有可选的Map操作,并允许null的值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证键值对的顺序。
在Map 中插入、删除和定位元素,HashMap 是最好的选择。
用作Key的对象必须实现HashCode和Equals方法。
尽量不要使用可变对象作为它们的Key。
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
TreeMap:
基于红黑树对所有的Key进行排序;排序方式分为自然排序和定制排序;TreeMap的Key以TreeSet的形式存储,因此对Key的要求与TreeSet对元素的要求基本一致。
TreeSet存在以下集中特殊方法:
Map的遍历方式:
Map<Integer, String> map1=new HashMap<Integer, String>();
//1.通过map.keySet()获取值的方式遍历
for (Integer aa:map1.keySet()) { //使用map.keySet()返回所有的Key值
String cc = map.get(aa); //得到每个Key所对应的Value值
System.out.println(aa+" "+cc);
}
//2.通过Map.entrySet使用iterator遍历Key和Value
Iterator<Map.Entry<Integer, String>> bb=map1.entrySet().iterator();
while (bb.hasNext()) {
Map.Entry<Integer, String> entry = bb.next();
}
//3.使用for循环,通过Map.entrySet遍历Key和Value值 (推荐使用!!)
for (Map.Entry<Integer, String> entry:map1.entrySet()) {
//map.entrySet返回map1中键值对
System.out.println("Key="+entry.getKey()+" Value="+entry.getValue());
}
//4.通过Map.value遍历所有的value值,但是不能遍历key
for (String dd:map1.values()) {
System.out.println("Value="+dd);
}
其实这样看下来,集合这些东西都不难理解,主要是你得记得住这些乱七八糟的功能区别啥的,怎么说的,我也正在Java这条路上一步步往前走着,大家一起努力吧,加油!