java学习——java基础(五)之集合类

来源:互联网 发布:巴塞尔协议 数据要求 编辑:程序博客网 时间:2024/06/05 15:22

写在前面:周六的日子过的有些清闲,下午和大学同学去聚了聚,发现在不知不觉中已和他们有了一定的差距。得到了很多的收获,初入社会,要不断自我积累,不能让自己停步不前。或许若干年后,再翻看这篇博客会觉得自己也年轻过。对自己说,加油!!!


在Java中集合类有着重要的意义,保存临时数据、管理对象、泛型、Web框架等,很多都大量用到了集合类。同时,在一般的面试中,集合类也是关注的焦点。因此,今天我们来复习下集合类的相关知识

1.集合的分类

集合类主要目的是提供一组接口尽量简单而且相同并且尽量高效,以便于开发人员按照场景选用,而不是自己重复实现的类。容器按接口可以分为两大类:Collection和Map,如下图所示:



那么,什么时候选择什么样的集合类呢?

Collection 我们需要保存若干个对象的时候使用集合。List如果我们需要保留存储顺序, 并且保留重复元素, 使用List.
如果查询较多, 那么使用ArrayList
如果存取较多, 那么使用LinkedList
如果需要线程安全, 那么使用VectorSet 如果我们不需要保留存储顺序, 并且需要去掉重复元素, 使用Set.如果我们需要将元素排序, 那么使用TreeSet
如果我们不需要排序, 使用HashSet, HashSet比TreeSet效率高.如果我们需要保留存储顺序, 又要过滤重复元素, 那么使用LinkedHashSetMap如果需要存储键映射到值的对象,则使用Map
如果要求线程安全,则选择Hashtable
如果要求对键进行排序,则选择TreeMap


使用集合类时注意事项

类类类说明Collection  单列集合 List 有存储顺序, 可重复  ArrayList数组实现, 查找快, 增删慢
由于是数组实现, 在增和删的时候会牵扯到数组增容, 以及拷
贝元素. 所以慢。数组是可以直接按索引查找, 所以查找时较快  LinkedList链表实现, 增删快, 查找慢
由于链表实现, 增加时只要让前一个元素记住自己就可以, 删除
时让前一个元素记住后一个元素, 后一个元素记住前一个元素。
这样的增删效率较高但查询时需要一个一个的遍历, 所以效率较低  Vector和ArrayList原理相同, 但线程安全, 效率略低
和ArrayList实现方式相同, 但考虑了线程安全问题, 所以效率略低 Set 无存储顺序, 不可重复  HashSet线程不安全,存取速度快。底层是以hash表实现的。  TreeSet红-黑树的数据结构,默认对元素进行自然排序(String)。如果在比较
的时候两个对象返回值为0,那么元素重复。  LinkedHashSet会保持插入顺序Map  键值对,将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 HashMap 底层是哈希表数据结构,线程是不同步的,可以存入null键,null值。
要保证键的唯一性,需要覆盖hashCode方法,和equals方法。  LinkedHashMap该子类基于哈希表又融入了链表。可以Map集合进行增删提高效率。 TreeMap 底层是二叉树数据结构。可以对map集合中的键进行排序。需要使用Comparable或者
Comparator 进行比较排序。return 0,来判断键的唯一性。 HashTable 底层是哈希表数据结构,线程是同步的,不可以存入null键,null值。效率较低,被HashMap 替代。

2.Collection接口

Collection接口并不是一个根接口,它的超级接口是Iterator,需要提供其遍历、移除元素(可选操作)的能力。Collection接口定义了基本的容器操作方法。

Set和List是由Collection派生的两个接口。


构造方法:

所有实现Collection接口的类都必须提供两个标准的构造函数:

(1)无参数的构造函数用于创建一个共的Collection。

(2)有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。


查看集合元素:

若要检查Collection中的元素,可以使用foreach进行遍历,也可以使用迭代器,Collection支持iterator()方法,通过该方法可以访问Collection中的每一个元素。具体如下:

Iterator it=collection.iterator();  while(it.hasNext()){     Object obj=it.next();  }  


Collection接口中共性方法:

增加:

add()  将指定对象存储到容器中,add 方法的参数类型是Object 便于接收任意对象

addAll() 将指定集合中的元素添加到调用该方法和集合中

删除:

remove() 将指定的对象从集合中删除

removeAll() 将指定集合中的元素删除

修改:

clear() 清空集合中的所有元素

判断:

isEmpty() 判断集合是否为空

contains() 判断集合何中是否包含指定对象      

containsAll() 判断集合中是否包含指定集合,使用equals()判断两个对象是否相等  

获取:

int size()    返回集合容器的大小

转成数组:

toArray()   集合转换数组


3.List接口

List的元素有存储顺序,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引的位置来访问List中的元素,类似于Java数组。同时,List允许有相同的元素存在。除了具有Collection接口必备的的iterator()方法外,还提供了listIterator()方法,放回一个 ListIterator接口。
实现List接口的常用类有LinkedList、ArrayList、Vector和Stack。


List特有的方法:

增加
void add(int index, E element)    //指定位置添加元素            
boolean addAll(int index, Collection c)    //指定位置添加集合  
删除
E remove(int index)    //删除指定位置元素
修改
E set(int index, E element)    //返回的是需要替换的集合中的元素
查找:
E get(int index)    //注意: IndexOutOfBoundsException
int indexOf(Object o)    // 找不到返回-1
lastIndexOf(Object o) 
求子集合
List<E> subList(int fromIndex, int toIndex)    //不包含toIndex


3.1 ArrayList

ArrayList实现了可变大小的数组,底层由数组实现,查找快, 增删慢。它允许所有元素,包括null。ArrayList没有同步。

数组为什么是查询快?因为数组的内存空间地址是连续的。

原理:ArrayList底层维护了一个Object[] 用于存储对象,默认数组的长度是10。可以通过 new ArrayList(20)显式的指定用于存储对象的数组的长度。当默认的或者指定的容量不够存储对象的时候,容量自动增长为原来的容量的1.5倍。


3.2 LinkedList

LinkedList由链表实现, 增删快, 查找慢。允许null元素。

由于链表实现增加时只要让前一个元素记住自己就可以,删除时让前一个元素记住后一个元素,后一个元素记住前一个元素。这样的增删效率较高。但查询时需要一个一个的遍历, 所以效率较低。


由于LinkedList提供的额外的方法,这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。额外的方法如下:

增删改查:

addFirst(E e),addLast(E e),getFirst(),getLast(),removeFirst(),removeLast()

如果集合中没有元素,获取或者删除元素抛:NoSuchElementException

栈:

push() 

pop()

队列(双端队列):

offer()

poll()

返回逆序的迭代器对象:

descendingIterator()   返回逆序的迭代器对象


3.3 Vector

Vector描述的是一个线程安全的ArrayList。相对应的,ArrayList是单线程的所以效率高。

因为Vector是同步的,当一个iterator被创建而且这在被使用,另一个线程改变了Vector状态,这时调用iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。


Vector特有的方法

void addElement(E obj)  //在集合末尾添加元素

E elementAt( int index)  //返回指定角标的元素

Enumeration elements()  //返回集合中的所有元素,封装到Enumeration对象中

Enumeration 接口:

boolean hasMoreElements()//测试此枚举是否包含更多的元素。 

E nextElement()    //如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。


示例:

public static void main(String[] args) {Vector v = new Vector();v.addElement("aaa");v.addElement("bbb");v.addElement("ccc");System.out.println( v );System.out.println( v.elementAt(2) );   // ccc// 遍历Vector遍历Enumeration ens = v.elements();while ( ens.hasMoreElements() ){System.out.println( ens.nextElement() );}}

3.4 Stack

Stack继承自Vector,实现了一个后进先出的堆栈。

Stack提供了5个额外的方法使得Vector得以被当做堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,serach方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。


3.5 ArrayList和LinkedList比较

ArrayList 和 LinkedList的存储查找的优缺点:

(1) ArrayList 是采用动态数组来存储元素的,它允许直接用下标号来直接查找对应的元素。但是,但是插入元素要涉及数组元素移动及内存的操作。总结:查找速度快,插入操作慢。

(2) LinkedList 是采用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。


3.6 迭代器

Collection的父接口,实现了Iterable的类就是可迭代的,并且支持增强for循环。

Iterator iterator() 返回该集合的迭代器对象。


Iterator接口定义的方法

Iterator是集合的迭代器接口类,定义了常见的迭代方法

boolean hasNext()    //判断集合中是否有元素,如果有元素可以迭代,就返回true。
E next()    //返回迭代的下一个元素,注意: 如果没有下一个元素时,调用next元素会抛出NoSuchElementException
void remove()    //从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操作)。


示例:

Iterator it = list.iterator();while (it.hasNext()) {String next = (String) it.next();System.out.println(next);}for (Iterator it = list.iterator(); it.hasNext();) {//迭代器的next方法返回值类型是Object,所以要记得类型转换。String next = (String) it.next();System.out.println(next);}
注意:在对集合进行迭代过程中,不允许出现迭代器以外的对元素的操作,因为这样会产生安全隐患,java会抛出异常并发修改异常(ConcurrentModificationException),普通迭代器只支持在迭代过程中的删除动作。


List特有的ListIterator

ListIterator是List专属的迭代器。方法如下:

add(E e)    //将指定的元素插入列表(可选操作)。该元素直接插入到 next 返回的下一个元素的前面(如果有)

void set(E o)   //用指定元素替换 next 或 previous 返回的最后一个元素

hasPrevious()    //逆向遍历列表,列表迭代器有多个元素,则返回 true。

previous()    //返回列表中的前一个元素。

Iterator在迭代时,只能对元素进行获取(next())和删除(remove())的操作。对于 Iterator 的子接口ListIterator 在迭代list 集合时,还可以对元素进行添加(add(obj)),修改set(obj)的操作。


4.Set接口

Set是一种不包含重复元素的Collection,该集合可以知道某元素是否已存在于集合中,用于存储无序(存入和取出的顺序不一定相同)元素。Set最多有一个null元素。


如果想要让两个不同的Person对象视为相等的,就必须覆盖Object继下来的hashCode方法和equals方法,因为Object  hashCode方法返回的是该对象的内存地址,所以必须重写hashCode方法,才能保证两个不同的对象具有相同的hashCode,同时也需要两个不同对象比较equals方法会返回true。


4.1 HashSet

调用原理:先判断hashCode 方法的值,如果相同才会去判断equals。如果不相同,是不会调用equals方法的。

所以重载equals方法同时也要重载hashCode,具体示例如下:

@Overridepublic int hashCode() {System.out.println("hashCode:" + this.name);return this.name.hashCode() + age * 37;}@Overridepublic boolean equals(Object obj) {System.out.println(this + "---equals---" + obj);if (obj instanceof Person) {Person p = (Person) obj;return this.name.equals(p.name) && this.age == p.age;} else {return false;}}

4.2 TreeSet

TreeSet继承SortedSet接口,能够对集合中对象排序。默认排序方式是自然排序,但该方式只能对实现了Comparable接口的对象排序,java中对Integer、Byte、Double、Character、String等数值型和字符型对象都实现了该接口。


问题:为什么使用TreeSet存入字符串,字符串默认输出是按升序排列的?

因为字符串实现了一个接口,叫做Comparable接口。字符串重写了该接口的compareTo方法,所以String对象具备了比较性。那么同样道理,自定义元素(例如Person类、Book类)想要存入TreeSet集合,就需要实现该接口,也就是要让自定义对象具备比较性。存入TreeSet集合中的元素要具备比较性。


TreeSet如何保证元素的唯一性?

通过compareTo或者compare方法中的来保证元素的唯一性。

添加的元素必须要实现Comparable接口。当compareTo()函数返回值为0时,说明两个对象相等,此时该对象不会添加进来。


TreeSet集合排序的两种方式:

----| Comparable(接口)

compareTo(Object o)     //元素自身具备比较性

----| Comparator(接口)

compare( Object o1, Object o2 )//给容器传入比较器

当Comparable比较方式,及Comparator比较方式同时存在,以Comparator比较方式为主。


两种方式的示例如下:

public int compareTo(Object obj) {Person p = (Person) obj;System.out.println(this+" compareTo:"+p);if (this.age > p.age) {return 1;}if (this.age < p.age) {return -1;}return this.name.compareTo(p.name);}class MyComparator implements Comparator {public int compare(Object o1, Object o2) {Book b1 = (Book) o1;Book b2 = (Book) o2;System.out.println(b1+" comparator "+b2);if (b1.getPrice() > b2.getPrice()) {return 1;}if (b1.getPrice() < b2.getPrice()) {return -1;}return b1.getName().compareTo(b2.getName());}}


5.Map接口

Map中的元素是两个对象,一个对象作为键,一个对象作为值。键不可以重复,但是值可以重复。


和Collection相比,区别如下:

(1) Map存储的是键值对

(2) Map存储元素使用put方法,Collection使用add方法

(3) Map集合没有直接取出元素的方法,而是先转成Set集合,在通过迭代获取元素

(4) Map集合中键要保证唯一性

(5) Collection是单列集合, Map 是双列集合。

(6) Map一次存一对元素, Collection 一次存一个。Map 的键不能重复,保证唯一。

(7) Map 一次存入一对元素,是以键值对的形式存在,键与值存在映射关系,一定要保证键的唯一性。


常用方法有

添加:

V put(K key, V value)    //可以相同的key值,但是添加的value值会覆盖前面的,返回值是前一个,如果没有就返回null。

putAll(Map<? extends K,? extends V> m)    //从指定映射中将所有映射关系复制到此映射中(可选操作)。

删除

remove()    //删除关联对象,指定key对象

clear()    //清空集合对象

获取

value get(key)    //可以用于判断键是否存在的情况。当指定的键不存在的时候,返回的是null。

判断:

boolean isEmpty()    //长度为0返回true否则false

boolean containsKey(Object key)   //判断集合中是否包含指定的key

boolean containsValue(Object value)    //判断集合中是否包含指定的value

长度:

Int size()


5.1 HashTable

HashTable继承Map接口,实现了一个key--value映射的哈希表。任何非空的对象都可作为key或者value。HashTable通过initial caoacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。

底层是哈希表数据结构,线程是同步的,不可以存入null键,null值。


5.2 HashMap

HashMap底层实现是哈希表数据结构,与HashTable不同之处在于,线程是不同步的,可以存入null键,null值。要保证键的唯一性,需要覆盖hashCode方法,和equals方法。

将HashMap视为集合时,其迭子操作时间开销和HahMap的容量成比例。因此,如果迭代操作的性能为先的话,不要将HashMap的初始化容量设的过高,或者load factor过低。

LinkedHashMap是HashMap的子类,该子类基于哈希表又融入了链表,提高了Map集合增删效率。


5.3 TreeMap

底层是二叉树数据结构。可以对map集合中的键进行排序,需要使用Comparable或者Comparator 进行比较排序。通过返回值是否为0来判断键的唯一性。


5.4 遍历Map的方式

(1)将map集合中所有的键取出存入set集合。

Set<K> keySet();    //返回所有的key对象的Set集合,再通过get方法获取键对应的值。Set<Integer> ks = map.keySet();Iterator<Integer> it = ks.iterator();while (it.hasNext()) {Integer key = it.next();String value = map.get(key);System.out.println("key=" + key + " value=" + value);}

(2)values() 获取所有的值

Collection<V> values();    //不能获取到key对象Collection<String> vs = map.values();Iterator<String> it = vs.iterator();while (it.hasNext()) {String value = it.next();System.out.println(" value=" + value);}

(3)Map.Entry对象,推荐使用

Set<Map.Entry<k,v>> entrySet()//将map集合中的键值映射关系打包成一个对象//Map.Entry对象通过Map.Entry 对象的getKey,getValue获取其键和值。//返回的Map.Entry对象的Set集合 Map.Entry包含了key和value对象Set<Map.Entry<Integer, String>> es = map.entrySet();Iterator<Map.Entry<Integer, String>> it = es.iterator();while (it.hasNext()) {// 返回的是封装了key和value对象的Map.Entry对象Map.Entry<Integer, String> en = it.next();// 获取Map.Entry对象中封装的key和value对象Integer key = en.getKey();String value = en.getValue();System.out.println("key=" + key + " value=" + value);}

6.总结

1.Set的元素不可重复,Map的键不可重复,如果存入重复元素如何处理:

(1)Set元素重复元素不能存入add方法返回false

(2)Map的重复健将覆盖旧键,将旧值返回。

2.看到array,就要想到角标。

看到link,就要想到first,last。

看到hash,就要想到hashCode,equals。

看到tree,就要想到两个接口:Comparable,Comparator。