集合按照其储存结构可分为两大类:即单列集合Collection和双列集合Map.
Collection:单列集合类的根接口,用于储存一系列复合某种规则的元素,它有两个重要的子接口,分别为List和Set。其中List的特点是元素有序,元素可重复。Set的特点是元素无序并且不可重复。List接口的主要实现类有ArrayList和LinkedList,Set接口的主要实现类有HashSet和TreeSet。
Map:双列集合类的根接口,用于储存具有键(Key),值(Value)映射关系的元素,每个元素都包含一对键值,在使用Map集合时可以通过指定的Key找到对应的Value。Map接口的主要实现类有HashMap和TreeMap。
一、Collection接口:
List接口继承了Collection接口,包含Collection接口的所有方法,List是列表类型,会提供一些适合于自己的方法。
1、List接口:
List接口中适合于自身的方法都与索引有关。由于List集合以线性方式储存对象,因此可以通过对象的索引来操作对象。
(1)List接口下实现ArrayList类
底层数据结构: 数组
初始化大小为 10
增长大小为当前大小的1.5倍(创建的 数组的长度的1.5倍)
该类实现了可变的数组,允许所有元素,包括null。可以根据索引位置对集合进行快速随机访问。缺点是向指定的索引位置插入对象或删除对象的速度较慢。
/**
* ArrayList类的交集,并集,去重代码实现
*/
public class ArrayListDemo {
public static void main(String[] args) {
/**
* ArrayList
*/
ArrayList<Integer> arraylist = new ArrayList<Integer>();
arraylist.add(13);
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(1);
list2.add(2);
arraylist.addAll(list2);
for(int i : arraylist){
System.out.println(i);
}
//查询这个数据是否存在在集合中
System.out.println(arraylist.contains(3));
//得到指定位置元素
Integer integer = arraylist.get(2);
System.out.println(integer);
//清除集合内元素
arraylist.clear();
for(int i : arraylist){
System.out.println(i);
}
ArrayList<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(2);
list2.add(3);
//求集合并集(去重和不去重)
ArrayList<Integer> list3 = new ArrayList<Integer>();
ArrayList<Integer> list4 = new ArrayList<Integer>();
//不去重
for(int i = 0;i < list1.size();i++){
list3.add(list1.get(i));
}
for(int i = 0;i < list2.size();i++){
list3.add(list2.get(i));
}
for(int i : list3){
System.out.printf(i+" ");
}
System.out.println("------------");
//去重
for(int i = 0;i < list1.size();i++){
list4.add(list1.get(i));
}
for(int i = 0;i < list2.size();i++){
if(!list4.contains(list2.get(i)))
{
list4.add(list2.get(i));
}
}
for(int i : list4){
System.out.printf(i+" ");
}
System.out.println("------------");
//求集合交集
//list1.containAll(list2);
for(int i = 0;i < list1.size();i++){
if(list2.contains(list1.get(i)) ){
System.out.printf(list1.get(i) + " ");
}
}
System.out.println("------------");
//求集合差集
//list1.removeAll(list2);
for(int i = 0;i < list1.size();i++){
if(!(list2.contains(list1.get(i))) ){
System.out.printf(list1.get(i) + " ");
}
}
}
(2)List接口下实现LinkedList类
LinkedList类的底层数据结构为双向链表,这种结构的优点是便于向集合中插入和删除对象,经常需要向集合中插入,删除对象时使用LinkedList类实现的List集合的效率较好,但对于随机访问集合中的对象使用LinkedList类实现的List集合的效率较慢。
2、Set接口:
Set接口继承了Collection接口,也具有Collection接口的所有方法。
由于Set集合中的对象是无序的,遍历Set集合的结果与插入Set集合的顺序并不相同。
Set接口下实现类常用的有HashSe类t和TreeSet类。
(1)HashSet类
-底层:哈希表
-键值段变单值
-内部元素不能重复
HashSet底层实现源码:
HashSet实现数据查重(仅限第一个出现重复的数据)
代码如下:
public static char findFirstRepeat(String A, int n) {
char[] a=A.toCharArray();
HashSet hs=new HashSet<>();
for(int i=0; i<n;i++) {
if (!hs.add(a[i])) {
return a[i];
}
}
return 0;
}
public static void main(String[] args) {
System.out.println(findFirstRepeat("dytkrzcvnjuekcn",12));
}
(2)TreeSet类
*TreeSet底层为键值段,继承于AbstractSet,并且实现了NavigableSet接口。
*TreeSet的本质是一个”有序的(自动排序),并且没有重复元素”的集合,它是通过TreeMap实现的
Map接口:
Map接口提供了将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
由于Map集合中的元素是通过key,value进行存储的,要获取指定的key值或value值,需要先通过相应的方法获取key集合或value集合,再遍历key集合或value集合获取指定值。
Map接口下实现类:
1、HashMap类
*HashMap类底层是一个hash表,即数组+链表。默认初始化大小是16(必须满足2的n次幂),默认加载因子是0.75f,最大容量是2的30次幂。在无参构造函数中使用的是默认大小和默认加载因子。
*特点:允许key为null。
*get方法:如果存在指定的键对象,则返回对象对应的值,否则返回null。
*HashMap通过哈希码对其内部的映射关系进行快速查找。不保证映射的顺序,尤其是不保证该顺序恒久不变。
*数组的扩容方式:2的n次幂。
*源码中的threshold:集合的阈值 容量乘以加载因子=阈值(16*0.75=12)
*HashMap扩容:原容量的2倍(当元素达到当前阈值时进行扩容)
*线程不安全导致并发问题:在扩容时
*Java7和Java8HashMap的区别:7中使用数组加链表,8中当数据大于8时会转换成红黑树。
class Entry{
/**
* 键
*/
int key;
/**
* 值
*/
String value;
/**
* 下一个节点
*/
Entry next;
/**
* 构造函数
* @param key 键
* @param value 值
*/
Entry(int key,String value){
this.key = key;
this.value = value;
this.next = null;
}
/**
* 构造函数
*/
Entry(){
this.key = 0;
this.value = null;
this.next = null;
}
}
class Hash{
/**
* 哈希数组表
*/
Entry[] table;
/**
* 构造函数
*
*/
Hash(){
/**
* 初始化这个table表
*/
table = new Entry[10];
for(int i = 0;i < table.length;i++){
table[i] = new Entry();
}
}
/**
* put方法
* @param key 键
* @param value 值
*/
public void put(int key,String value){
Entry entry = new Entry(key,value);
//临时key
int keys = key%10;
if(table[keys].next == null){
table[keys].next = entry;
entry.next = null;
}else{
/**
* 哈希冲突-->用链地址解决
*/
Entry tar = table[keys];
while(tar.next != null){
tar = tar.next;
}
tar.next = entry;
entry.next = null;
}
}
public String get(int key){
int keys = key%10;
Entry tar = table[keys];
while(tar.key != key && tar != null){
tar = tar.next;
}
if(tar == null){
return null;
}
return tar.value;
}
}
public class HashMap {
/**
* hashMap
* put()
* get()
* @param arg
*/
public static void main(String[] args) {
Hash h = new Hash();
h.put(3, "wangjie");
h.put(4, "haha");
h.put(13,"xixi");
System.out.println(h.get(13));
}
}
2、TreeMap类
*该类实现了Map接口,还实现了java.util.SortedMap接口,因此集合中的映射关系具有一定的顺序。
*在添加,删除和定位映射关系上,TreeMap类比HashMap类的性能差一些。
*由于TreeMap类实现的Map集合中的映射关系是根据键对象按照一定的顺序排序的,因此不允许键对象是null。
*可以通过HashMap类创建Map集合,当需要顺序输出时,再创建一个完成相同映射关系的TreeMap类实例。
3、LinkedHashMap类
*LinedHashMap继承自HashMap,具有HashMap的属性和方法。它也有自己特有的属性(双向列表头结点和迭代顺序)。
更为准确地说,LinedHashMap是一个将所有Entry节点炼入一个双向链表的HashMap。
*HashMap是无序的,也就是说,迭代HashMap所得到的元素顺序并不是它们最初放置到HashMap的顺序。HashMap的这一缺点往往会造成诸多不便,因为在有些场景中,我们确需要用到一个可以保持插入顺序的Map,此时有序的LinedHashMap就会解决这一问题。
LinkedHashMap的快速存取
LinkedHashMap 的存储实现 : put(key, vlaue)
LinkedHashMap没有对 put(key,vlaue) 方法进行任何直接的修改,完全继承了HashMap的 put(Key,Value) 方法,源码如下:
public V put(K key, V value) {
//当key为null时,调用putForNullKey方法,并将该键值对保存到table的第一个位置
if (key == null)
return putForNullKey(value);
//计算该键值对在数组中的存储位置(哪个桶)
int i = indexFor(hash, table.length);
//在table的第i个桶上进行迭代,寻找 key 保存的位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
e.value = value;
e.recordAccess(this); // LinkedHashMap重写了Entry中的recordAccess方法--- (1)
}
}
modCount++; //修改次数增加1,快速失败机制
addEntry(hash, key, value, i); // LinkedHashMap重写了HashMap中的createEntry方法 ---- (2)
return null;
}
4、.LinkedHashSet(有序)
*LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同。
LinkedHashSet 的实现上通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可
LinkedHashSet 的源代码如下:
public class LinkedHashSet extends HashSet implements Set, Cloneable, java.io.Serializable {
private static final long serialVersionUID = -2851667679971038690L;
/**
* 构造一个带有指定初始容量和加载因子的新空链接哈希set。
* 底层会调用父类的构造方法,构造一个有指定初始容量和加载因子的LinkedHashMap实例。
* @param initialCapacity 初始容量。
*/
public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); }
/**
* 构造一个带指定初始容量和默认加载因子0.75的新空链接哈希set。
* 底层会调用父类的构造方法,构造一个带指定初始容量和默认加载因子0.75的LinkedHashMap实例。
* @param initialCapacity 初始容量。
*/
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
/**
* 构造一个带默认初始容量16和加载因子0.75的新空链接哈希set。
* 底层会调用父类的构造方法,构造一个带默认初始容量16和加载因子0.75的LinkedHashMap实例。
*/
public LinkedHashSet() {
super(16, .75f, true);
}
/**
* 构造一个与指定collection中的元素相同的新链接哈希set。
* 底层会调用父类的构造方法,构造一个足以包含指定collection中所有元素的初始容量和加载因子为0.75的LinkedHashMap实例。
* @param c 其中的元素将存放在此set中的collection。
*/
public LinkedHashSet(Collection c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
}
三、哈希表
.哈希表:通过关键值找对应值的一个数据结构
常用哈希构造函数
|– 直接寻址法:f(key) = key
|– 除留余数法:f(p) = p % n,n < m(m是数组的大小)
解决哈希冲突的重要方法:
|– 链地址法:数组和单向链表的组合(尾插法)
|– 探测法:pos(n) = f(n) + p(n)
a:线性探测法:依次做加1探测,找到空位,即pos(n) = f(n) + 1,+2,+3……;
b:随机探测法:以随机数的方式探测空位,pos(n) = f(n) + random();(伪随机)
四、迭代器的创建和使用
利用Iterator接口创建迭代器,Iterator接口位于java.util包下。Iterator接口有三个方法:
*hashNext():如果仍有元素可以迭代,则返回true。
*next():返回迭代的下一个元素。
*remove():从迭代器指向的collection中移除迭代器返回的最后一个元素(可选操作)。
/**
* Iterator遍历ArrayList代码实现
*/
public class IteratorDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i < 10;
i++) {
List.add(i);
}
System.out.println("列表中的元素:");
for(Iterator<Integer> it = list.iterator();it.hashNext();) {
System.out.print(it.next()+" ");
}
}
}