数组和集合的定义
Collection
|--List :元素是有序的,元素可以重复。因为该集合体系有索引。 |--ArrayList :底层的数据结构使用的是数组结构。特点:查询速度很快。但是增删稍慢。线程不同步。 百分之50增加 jdk1.2加入 多线程加锁让其同步 |--LinkedList :底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步。 |--Vector :底层是数组数据结构。线程同步。被ArrayList替代了。因为效率低。 百分之100增加 jdk1.0加入
|--Set:元素是无序,元素不可以重复。
数组
数组是java语言内置的数据类型,他是一个线性的序列,所有可以快速访问其他的元素,数组和其他语言不同,当你创建了一个数组时,他的容量是不变的,而且在生命周期也是不能改变的,还有JAVA数组会做边界检查,如果发现有越界现象,会报RuntimeException异常错误,当然检查边界会以效率为代价。
集合
JAVA还提供其他集合,list,map,set,他们处理对象的时候就好像这些对象没有自己的类型一样,而是直接归根于Object,这样只需要创建一个集合,把对象放进去,取出时转换成自己的类型就行了。
数组和集合的区别
数组声明了它容纳的元素的类型,而集合不声明。
数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
集合体系结构
Map
- 没有实现collection接口,key不能重复,value可以重复,一个key映射一个value
- Hashtable (实现Map接口,同步,不允许null作为key和value,用自定义的类当作key的话要复写hashCode和eques方法,)
- HashMap (实现Map接口,非同步,允许null作为key和value,用的多)
- WeakHashMap(实现Map接口)
Collection接口
List接口
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。
- 用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
- 和Set不同,List允许有相同的元素。
- 除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
- 实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
LinkedList
- LinkedList实现了List接口,允许null元素。LinkenList底层采用了双向链表来存储数据,每个节点都存储着上一个节点和下一个节点的地址以及本节点的数据。
- 此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
- 注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List
List list = Collections.synchronizedList(new LinkedList(...));
- ArrayList
- ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList底层采用动态数组的存储方式,便利效率非常高,ArrayList是线程不安全的。
- size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
- 每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。
- 当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
CopyOnWriteArrayList
**add()**在添加集合的时候加上了锁,保证了同步,避免了多线程写的时候会Copy出N个副本出来.
有这么一种情况,当一个线程刚好调用完**add()**方法,也就是刚好执行到上面1处的代码,也就是刚好将引用指向新数组,而此时有线程正在遍历呢?会不会报错呢?答案是不会的,因为你正在遍历的集合是旧的。
//在写的时候不对原集合进行修改,而是重新复制一份,修改完之后,再移动指针
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
ArrayList和LindedList的区别
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList 优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
- Vector类
- Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
- Stack 类
- Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
Set接口
- Set是一种无序的并且不包含重复的元素的Collection,即任意的两个元素e1和e2都有
e1.equals(e2)=false
,Set最多有一个null元素。 - 很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
- HashSet 类
- 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复
- 因为HashSet的底层实现是HashMap,但是HashSet只使用了HashMap的key来存取数据所以HashSet存的数据不能重复。
- HashSet要求放入的对象**必须实现HashCode()**方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。
- 但是同一个类的对象可以放入不同的实例 。
Map接口
Map
- Hashtable: 底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。jdk1.0.效率低。
- **HashMap:**底层是哈希表数据结构,允许使用 null 值和 null 键,该集合是不同步的。将hashtable替代,jdk1.2.效率高。
- **TreeMap:**底层是二叉树数据结构。线程不同步。可以用于给map集合中的键进行排序。
和Set很像。
- Set底层就是使用了Map集合。
/*
map集合的两种取出方式:
1,Set<k> keySet:将map中所有的键存入到Set集合。因为set具备迭代器。
所有可以迭代方式取出所有的键,在根据get方法。获取每一个键对应的值。
Map集合的取出原理:将map集合转成set集合。在通过迭代器取出。
2,Set<Map.Entry<k,v>> entrySet:将map集合中的映射关系存入到了set集合中,
而这个关系的数据类型就是:Map.Entry
Entry其实就是Map中的一个static内部接口。
为什么要定义在内部呢?
因为只有有了Map集合,有了键值对,才会有键值的映射关系。
关系属于Map集合中的一个内部事物。
而且该事物在直接访问Map集合中的元素。
*/
- Map没有继承Collection接口,Map提供key到value的映射。
- 一个Map中不能包含相同的key,每个key只能映射一个value。
- Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Hashtable类
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。
hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
- Hashtable是同步的。
- HashMap类
- HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key,但是将HashMap视为Collection时(values()方法可返回Collection)。 W
- eakHashMap类
- WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
HashMap实现原理:
- HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
- HashMap底层是一个Node类型数组+Node类型的链表+红黑树。即数组+链表+红黑树。HashMap是Node类型的结点,HashMap里面存的是Node,Node里面存的是键值对。
- HashMap默认初始化容量为16,负载因子为0.75
set
Set:无序,不可以重复元素。
元素是无序(存入和取出的顺序不一定一致),元素不可以重复。、
HashSet:底层数据结构是哈希表。是线程不安全的。不同步。 HashSet是如何保证元素唯一性的呢? 是通过元素的两个方法,hashCode和equals来完成。 如果元素的HashCode值相同,才会判断equals是否为true。 如果元素的hashcode值不同,不会调用equals。
注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法。
HashSet:
- 数据结构是哈希表。线程是非同步的。
- 保证元素唯一性的原理:判断元素的hashCode值是否相同。
- 如果相同,还会继续判断元素的equals方法,是否为true。
TreeSet:
- 可以对Set集合中的元素进行排序。
底层数据结构是二叉树。
保证元素唯一性的依据:
compareTo方法return 0.
TreeSet排序的第一种方式:让元素自身具备比较性。
元素需要实现Comparable接口,覆盖compareTo方法。
也种方式也成为元素的自然顺序,或者叫做默认顺序。
TreeSet的第二种排序方式。
当元素自身不具备比较性时,或者具备的比较性不是所需要的。
这时就需要让集合自身具备比较性。
在集合初始化时,就有了比较方式。
总结
- 如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
- 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
- 要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
- 尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。