数据结构、集合框架整合

一.数据结构

是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。

1.集合:数据结构中的元素之间除了“同属一个集合”的相互关系

2.线性结构:数据结构中的元素存在一对一的相互关系

3.树形结构:数据结构中的元素存在一对多的相互关系

4.图形结构:数据结构中的元素存在多对多的相互关系

常用的数据结构:数组、栈、队列、链表、树、图、堆、散列表

二.各类数据结构

1.数组

数组:将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组。

数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像一排出租屋,有固定的100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

数组的特点:元素类型是固定的,长度是固定的,通过角标查询,查询快、增删慢。

数据结构、集合框架整合_第1张图片

2.栈

线性结构。只有一个口,有顺序的进出,并且只能一端进与出,称为LIFO,先进后出或者后进先出。类似于坐电梯,先进去的人给不断挤到后面,此举称之为压栈。底层是用LinkedList实现的。

数据结构、集合框架整合_第2张图片

3.队列

线性结构。有顺序的进出,并且只能一端进另一端出,称为FIFO,先进先出,像一条水管。底层是用LinkedList实现的。

数据结构、集合框架整合_第3张图片

4链表

链表的类型有多种:单链表、双链表和有序链表

链表:元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针。如果访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单,只要修改元素中的指针。如果需要经常插入和删除元素就需要使用链表。

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针链接次序实现的。链表由一系列结点组成(链表中每一个元素称为结点),结点可以在运行时动态生成。

数据结构、集合框架整合_第4张图片

5.哈希表

哈希表,是根据关键码值(Key Value)而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

HashMap先在内存中创建一个数组,然后每个元素都会被计算Hash值,然后通过Hash值再计算一层,得到其在数组中位置,由于hash值的特殊性,可能会存在两个元素被安排在数组中重复的位置,这时候就会创建一条链表,来维护重复的数据。

数据结构、集合框架整合_第5张图片

三.数组与链表的优缺点和区别

1.内存存储区别

数组从栈中分配空间,自由度小。

链表从堆中分配空间,自由度大但申请管理麻烦。

2.逻辑结构区别

数组必须事先定义固定的长度,不能适应数据动态地增减的情况。当数据增加时,超出原先定义的元素个数;

链表动态地进行存储分配,可以适应数据动态地增减,可以方便插入、删除数据项。

3.总结

(1)存取方式,数组可以顺序存取或者随机存取,而链表只能顺序存取。

(2)存取位置,数组逻辑上相邻的元素在物理存储位置上相邻。

(3)按序号(下标)查找时,数组可以随机访问,时间复杂度为O(1),链表的时间复杂度为O(n)

(4)按值查找时,若数组无序,数组和链表的时间复杂度为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(log n)

(5)插入和删除元素时,数组平均需要移动N/2个元素,而链表只需修改指针即可。

四.集合框架

整体上两大类接口,一类是Collection接口,另一类是Map接口。

Collection接口包含两大类:List接口和Set接口。

List接口:存储一组不唯一且按插入顺序排序的对象,可以操作索引。

Set接口:存储一组唯一且无序的对象。

Map接口:以键值对的形式存储元素,键(key)是唯一的。

1.如果元素可以重复,实现List接口

(1)需要查询快

        (a)线程安全

                Vector:由数组实现,访问元素的效率比较高,删除和添加元素效率低。

         (b)线程不安全

                 ArrayList:由数组实现,访问元素的效率比较高,删除和添加元素效率低。

(2)需要增删快

         (a)线程不安全

                 LinkedList:由链表实现,插入、删除元素效率比较高,访问效率比较低。

2.如果元素需要唯一不重复,实现Set接口

(1)需要查询快

     (a)线程不安全

             HashSet:由哈希表实现,使用了HashTable。添加、查询、删除元素的效率都很高,缺点是元素无序。通过hashcode与equals方法确保元素的唯一。

             TreeSet:由二叉树实现。查询效率高,且元素有序的。存放自定义类型的对象需要实现Comparable接口,重写compareTo方法,提供对象排序的方式。

             LinkedHashSet:由哈希表实现元素的存储,由链表实现元素的顺序。添加、查询、删除元素的效率都高,且元素都是有序的。

(2)需要排序

         (a)线程不安全 TreeSet

3.通过键值对存取,实现map接口

(1)需要查询快

         (a)线程不安全

                 HashMap:由hash表实现,底层是Hashtable,添加、查询、删除元素的效率都很高。

                 LinkedHashMap:由哈希表实现元素的存储,由链表实现元素的顺序。添加、查询、删除元素的效率都高,且元素都是有序的。

                 TreeMap:由二叉树实现。查询效率高,且元素有序的。

                Hashtable:任何非null对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现hashCode和equals方法。

ArrayList和LinkedList的区别。

(1)ArryaList和LinkedList都是线程不安全的。

(2)Arraylist底层是由数组实现的,数组的优点是通过下标访问,查询效率高,时间复杂度为O(1);增删因为牵扯到数组复制操作,所以增删效率不高。

(3)LinkedList的底层是由链表实现的,链表的优点是每个结点都有指向下条数据的指针,所以增删时只需要修改指针地址,查询时因为链表的数据结构在内存空间内是不连续的的且没有索引,所以查询效率低,时间复杂度为O(n)。

五.HashMap实现原理

HashMap先在内存中创建一个数组,然后每个元素都会被计算hash值,然后通过hash值再计算,得到其在数组中的位置,由于hash值的特殊性,可能会存在两个元素被安排在数组中重复的位置,这时候就创建一个链表,来维护重复的数据。

搞清楚HashMap,首先需要知道HashMap是什么,即它的存储结构-字段;其次弄明白它能干什么,即它的功能实现-方法。

1.从结构实现来讲,HashMap是数组+链表+红黑树实现的。

(1)从源码可知,HashMap类中有一个非常重要的字段,就是Node[]table,即哈希桶数组,是一个Node的数组。

static class Node implements Map.Entry {
        final int hash;    //用来定位数组索引位置
        final K key;
        V value;
        Node next;   //链表的下一个node

        Node(int hash, K key, V value, Node next) { ... }
        public final K getKey(){ ... }
        public final V getValue() { ... }
        public final String toString() { ... }
        public final int hashCode() { ... }
        public final V setValue(V newValue) { ... }
        public final boolean equals(Object o) { ... }
}

Node是HashMap的一个内部类,实现了Map.Entry接口,本质上就是一个映射(键值对)。 

(2)HashMap使用哈希表存储,使用哈希表来解决哈希冲突。Java中的HashMap采用了链地址法。数组加链表的结合。在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表。例如程序执行下面代码:

map.put("美团","小美");

系统将调用"美团"这个key的hashCode()方法得到其hashCode()值,然后通过Hash算法的后两步运算(高位运算和取模运算)来定位该键值对的存储位置,有时两个key会定位到相同的位置,表示发生了Hash碰撞。当然Hash算法计算结构越分散均匀,Hash碰撞的概率就越小,map的存取效率就越高。好的Hash算法和扩容机制来控制map使得Hash碰撞的概率变小,哈希桶数组Node[]table占用空间又少。

HashMap的默认构造函数源码,

int threshold;                                  //所能容纳的key-value的极限

final float loadFactor;                    //负载因子

int modCount;

int size;

Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值为0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold=length*Load factor。

在HashMap中,哈希桶数组table的长度length大小必须为2的n次方。

2.功能实现—方法

(1)确定哈希桶数组索引位置

使用hash算法避免了遍历链表,大大优化了查询的效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能。

方法一:

static final int hash(Object key){

int h;

//h = key.hashCode();取hashcode值

//h^(h>>>16); 高位参与运算

return (key==null)?0:(h=key.hashCode())^(h>>>16);

}

方法二:

static int indexFor(int h,int length){

return h&(length-1);

}

Hash算法本质:取key的hashCode、高位运算、取模运算。

(2)put方法的详细执行

(a)判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;——判断数组是否为空

(b)根据键值key计算hash值得到插入的数组索引i,如果table[i]=null,直接新建节点添加,转向(f),如果table[i]不为空,转向(c)——计算hash值判断索引i的位置

(c)判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向(d),相同指的是hashcode以及equals;——判断table[i]的首个元素是否和key一样

(d)判断table[i]是否为treeNode,判断是否是红黑树

(e)遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树

(f)插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过,进行扩容。

(3)扩容过程



你可能感兴趣的:(Java)