线性表
线性表是一种线性结构,是由零个或者多个数据元素构成的有限序列。
特征:除了头尾元素,每个元素有且仅有一个直接前驱和一个直接后继;而头元素没有直接前驱,尾元素没有直
接后续
常见的线性结构有数组和链表结构
数组
存储区域是连续的,占用内存比较严重,所以空间复杂度较大;但是数组可以使用使用二分法进行查找元素,时
间复杂度比较小
特点:
- 寻址方便,时间复杂度 O(1) - 插入和删除比较困难,可能会引发元素移动,时间复杂度 O(n) - java 中的数组是定长的,如果需要变长则需要自行编程实现
使用数组实现一个变长存储的集合
System.arrayCopy 方法
链表
数组需要一块连续的内存空间存储数据,对内存要求较高。链表通过指针可以将一组零散的内存块串联起来使用。
数组擅长按照下标进行随机访问,链表擅长删除、插入操作
具体实现:在链表中使用节点存储数据,并使用引用串联各个元素。每个节点除了存储数据本身之外,还需要额
外存储下一个节点的地址
collection
它是一个接口,是高度抽象出来的集合,它包含了集合的基本操作:添加、删除、清空、遍历(读取)、是否为空、获取大小、是否保护某元素等等。
Collection接口的所有子类(直接子类和间接子类)都必须实现2种构造函数:不带参数的构造函数 和 参数为Collection的构造函数。带参数的构造函数,可以用来转换Collection的类型
List接口:
vector集合、ArrayList集合、LinkedList集合
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析:
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。
List中带索引的方法(特有方法):
- add(int index,E e);//将指定的元素添加到该集合指定的位置上
- get(int index);//返回集合中指定位置的元素
- set(int index,E e);//用指定元素替换集合中指定位置的元素,返回值更新前的元素
- remove(int index);//移除列表中指定位置的元素,返回的是被移除的元素
注意:操作索引的时候,一定要防止索引越界异常
LinkedList特有的方法(包含大量操作首位的方法):
addFirst(E e);//将指定元素插入列表的开头
addLast(E e);//将指定元素添加到列表的结尾
getFirst();//返回此列表的第一个元素
getLast();//返回此列表的最后一个元素
removeFirst();//移除并返回此列表的第一个元素
removeLast();//移除并返回此列表的最后一个元素
pop();//从此列表所表示的堆栈中弹出一个元素,相当于removeFirst
push(E e);//将元素推入此列表所表示的堆栈,相当于addfirst
isEmpty();//如果列表不包含元素则返回true
注意:LinkedList特有的方法不能使用多态
set接口:TreeSet接口、HashSet集合(包括:LinkedHashSet接口)
特点:
不允许存储重复的值
没有索引(不能使用普通的for循环遍历)
Set:
1)HashSet : 底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。
a、实现唯一性:
存储元素首先会使用hash()算法函数生成一个int类型hashCode散列值,然后已经的所存储的元素的hashCode值比较,如果hashCode不相等,则所存储的两个对象一定不相等,此时存储当前的新的hashCode值处的元素对象;如果hashCode相等,存储元素的对象还是不一定相等,此时会调用equals()方法判断两个对象的内容是否相等,如果内容相等,那么就是同一个对象,无需存储;如果比较的内容不相等,那么就是不同的对象,就该存储了,此时就要采用哈希的解决地址冲突算法,在当前hashCode值处类似一个新的链表, 在同一个hashCode值的后面存储存储不同的对象,这样就保证了元素的唯一性。
b、实现不重复
HashSet也一样他是使用了一种标识来确定元素的不重复,HashSet用一种算法来保证HashSet中的元素是不重复的, HashSet采用哈希算法,底层用数组存储数据。默认初始化容量16,加载因子0.75。
Object类中的hashCode()的方法是所有子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模, 模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会添加进去。
覆盖hashCode()方法的原则:
1、一定要让那些我们认为相同的对象返回相同的hashCode值
2、尽量让那些我们认为不同的对象返回不同的hashCode值,否则,就会增加冲突的概率。
3、尽量的让hashCode值散列开(两值用异或运算可使结果的范围更广)
c、HashSet的实现
HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,我们应该为保存到HashSet中的对象覆盖hashCode()和equals(),因为再将对象加入到HashSet中时:
首先调用hashCode方法计算出对象的hash值,接着根据此hash值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加;
注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时,也要尽量使不同对象尽量返回不同的Hash码值。
(2)、LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。
(3)、TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造)。
自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;
比较器排需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;
比较:
1、TreeSet 是二叉树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值;
2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束 ;
3、HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例;
适用场景分析:HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。