一、List
list和下面要将的set共同实现Collection接口
并且list和set本身也是两个接口
list常用的实现类有 ArrayList 、LinkedList以及Vector
Arraylist和LinkedList
从实现原理上来说 ArrayList是由数组实现的,而LinkedList是基于双链表实现的。所以说,对于查询ArrayList快,时间复杂度为O(1),增删慢,最坏的情况下时间复杂度为O(n);LinkedList反之,增删快,O(1),查询慢。
ArrayList和Vector
Vector也是有数组实现的,但是里面很多的方法都用synchronized来修饰,也就是说,它是线程安全的,那么与之相比,ArrayList就是线程不安全的。所以Vector的效率要比ArrayList的效率低。
扩容:两者的初始大小均为默认值10。
ArrayList:容量不够时,会将旧的容量值用移位运算
int newCapacity = oldCapacity + (oldCapacity >> 1);
扩容,即增加到原来的1.5倍,如果1.5倍还不够,那么将直接扩容到我们所需要的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
Vector:Vector扩容则是直接用原容量加原容量,就是扩容到原来的2倍
int newCapacity = oldCapacity
+ ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);
那么如果2倍还不够,则跟ArrayList相同,直接扩容到我们所需要的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
二、Set
Set跟上面的List都实现了Collection接口,与List相比,set是无序的,list是有序的;set不允许重复,list可以重复。
Set也是一个接口,TreeSet和HashSet是它最常用的实现类。
1.TreeSet和HashSet的区别
Treeset中的数据是排好序的,不允许放入null,而HashSet并没有排序,且可以只有一个null;
TreeSet是通过TreeMap实现的,HashSet是通过HashMap实现的,我们可以看源码:
//treeset的无参构造
public TreeSet() {
this(new TreeMap<E,Object>());
}
//hashset的无参构造
public HashSet() {
map = new HashMap<>();
}
Set怎么去重
Set与ArrayList的区别之一就是Set不能有重复的元素
那么set怎么去重?
Treeset:Treeset与HashSet的区别有一条就是排序,TreeSet有两种排序方法,一种是实现Comparable接口,去重写接口中的CompareTo()方法(也是这个接口唯一的方法),即自然排序;另一个是创建一个比较器;
main:
Set<User> s=new TreeSet<>();
//name sex money
s.add(new User("a","a",2));
s.add(new User("a","a",1));
s.add(new User("c","c",3));
自然排序:
根据money的大小排序
public class User implements Comparable {
@Override
public void compareTo(Object o){
User user=(User)o;
if(this.money>user.money)
return 1;//放在红黑树左边
if(this.money<user.money)
return -1;//放在右边
return 0;//相等即查重,覆盖
}
}
比较器:
Set<User> s=new TreeSet<User>(new Comparator<User>() {//匿名内部类
@Override
public int compare(User o1, User o2) {
if( o1.getMoney()>o2.getMoney())
return 1;
if( o1.getMoney()<o2.getMoney())
return -1;
return 0;
}
});
s.add(new User("a","a",2));
s.add(new User("a","a",1));
s.add(new User("c","c",3));
System.out.println(s);
这是TreeSet的去重方法,即在排序的过程中相同的会被覆盖掉,注意这里的排序规则是比较money的大小,money若有重复,则整个user对象就会被覆盖,但是name、sex相同,则不会被覆盖,即以那个属性为排序规则,则那个属性不能相同,其他属性可以相同。
HashSet:HashSet与Treeset的区别之一就是没有排序。
首先,HashSet会计算出传入的值的hashcode,会首先比较hashcode是否相同,如果相同,再调用equals,若还相同,则重复,若不同,则不重复;若hashcode不同,则不执行equals,直接判定为不相同元素。如果要根据某个实例下的某个属性来判度是否重复,一定要重写hashcode和equals方法。(如果不重写,则会调用object的hashcode和equals)。
三、map
map是通过键值对的形式存储数据,它也是一个接口,常用的实现类有HashMap,TreeMap。上面说到,HashSet是基于HashMap实现的,TreeSet是基于TreeMap实现的。其实HashSet的查重就是基于HashMap的。
HashMap:
Map<String,String> map=new HashMap<>();
可以看到,HashMap是由键值对的形式保存的,要注意:键对象必须是唯一的 值对象可以重复,上面说的HashSet查重,就是将HashSet传入的值当作键对象来查重。所以说,HashSet和HashMap的查重是一样的方式。
HashMap介绍:
HashMap是由数组、链表结合组成。每个数组中都有一个节点来存放链表。这个数组的默认长度为16。
HashMap存储过程:
1 首先会根据key来得到hashcode
2 根据hashcode得到这个值对应的数组的下标
3 判断是否到了阈值
4 将当前对应的 hash,key,value封装成一个 Entry,去数组中查找当前位置有没有元素,如果没有,放在这个位置上;如果此位置上已经存在链表,那么遍历链表,当前key与每个key 进行 equals ,如果相同则把覆盖value,如果没有找到key 与当前 key equals相同,就把它放在链表的头部。
HashMap扩容:
HashMap的初始数组长度为默认值16,加载因子为0.75,也就是说,当前数组有(16*0.75)被占用时,数组长度就会扩大2倍,即32。
那么为什么时0.75呢?
如果我们的数组长度有100,
如果加载因子比0.75小,比如0.5,当我们存数据到50时,数组长度就会翻倍,浪费空间;如果是0.8比0.75大,则可能会出现hash冲突。
当然,当一个数组的链表过长时,达到默认值8的时候,就会生成红黑树,即TreeMap,这样做是因为链表的查询能力很差,当链表很长时,效率会很低,事件复杂度为O(n),所以生成红黑树,有效提高查询能力,时间复杂度为O(logn);