您的位置:首页 > 编程语言 > Java开发

Java Notes: Containers

2016-07-23 18:04 351 查看
Containers aim to keep objects as groups. There are three main kinds of containers that we use in Java.

1) Array; 2) Collection; 3) Map

Array is a common tool. The main defect of array is it has a fixed size and cannot vary after being created. In order to overcome the defect of array, Java provides collection and map in java.util.* 

数组声明的时候需要明确指定类型,而collection和map声明时可以通过泛型指定类型,如果不指定类型,这时编译器会默认为object类型,所有的赋值对象将会以object类型的形式存储在容器中。在这种情况下,如果需要使用这些容器内对象的自定义方法时,需要在get到对象后进行类型转换,否则编译器会报错。假如一开始声明容器时已使用泛型说明存储对象的类型,则不需要考虑上述的类型转换问题。

容器的泛型支持向上转换。意味着声明为父类的容器,可以赋值子类的对象。但是容器内的这些元素只能使用父类的方法。

Collection与Map的区别:前者是存储单个对象,对象与对象间只有存储位置上的关系;后者则存储键值对(一对对象,存在映射关系的一对对象)。

Collection包括:List, Set 和 Queue. 

各自特点如下:

List: 以存入容器的先后顺序决定对象的保存顺序,它与数组最相似,然而它能自适应地调整size,比数组更优秀;典型的List实现类有:ArrayList(最基本、最可靠,最接近数组的List)和 LinkedList(List的升级版,具有List接口所不具备的额外方法)。LinkedList所具备的额外方法用以实现Stack和Queue。题外话一句,其实Java早版本曾实现java.util.Stack,但是那是一个失败的实现。所以,不提倡使用那个Stack实现,取而代之的是使用LinkedList的实现。

Set: 不能有重复元素; 典型的Set实现有:HashSet, TreeSet 和 LinkedHashSet. HashSet的存储顺序没有规律可循,这是为了加快查询速度而使用的散列所致。TreeSet是基于红-黑树的数据结构存储数据,数据是按排序的结果存储,默认的排序方式是基于字典排序(默认升序)。LinkedHashSet的存储顺序是数据的加入顺序,但同时,它也采用散列提高查询速度。之所以需要使用散列提高查询速度是由Set的特征所决定的,每次加入数据都需要查询待加入数据是否存在于Set现有的数据中。

Queue: 先进先出的顺序存储数据。但队列的顺序不一定是固定的先进先出,是可以根据重要性排序的(PriorityQueue)。因为队列具有FIFO的特点,经常被用作保证对象传输的容器,在并发编程中广泛应用。

下面讲述一个重要的编程思想:向上转型。用父类(抽象类)的引用(指针)指向实现类的对象(new出来的)。这样做有两个好处:

1) 例如Collection的引用指向List的实现类的对象,虽然限制了这个引用所能调用的方法和属性,但修改代码时可以轻易切换使用的实际容器类;

2)例如Queue和LinkedList,LinkedList不单只是Queue的实现类,其也是List或者Stack的实现类,所以具备属于List和Stack的方法,使用Queue引用则可以显式说明只使用属于Queue的方法与属性。

下面具体说明容器的各自为迎合自身特点所设计的方法接口:

List: 实质是数据结构中的线性表(有0个或者多个元素的有序序列)。ArrayList和LinkedList。提供了从中间添加、删除元素的方法add(int index, e element)和remove(int index)。ArrayList长于随机访问元素,但中间插入移除元素比较慢。而LinkedList的随机访问性能较差,但中间插入和删除元素的性能高。原因是ArrayList是顺序存储结构的线性表,而LinkedList是链式存储结构的线性表。除此之外,LinkedList的方法比ArrayList要多(是队列、Dequeue和Stack的实现类)。

方法说明:

remove(e element)和removeAll(container want_to_delete)这个方法。当确定一个元素是否属于某个List,从而下一步移除时都会调用equals() 方法。这个方法由根类Object就有定义。默认的相等的对象必须是在堆中的存储地址相同而非属于同一类的对象即可。但String类的相等定义则是内容完全一样即可。

contailsAll()方法中的列表元素顺序不会影响判断结果。retainAll()是一个产生交集的方法,container1.retainAll(container2)将container1变为两个container的交集。

set()用以replace某个索引处的值。它的功能是在索引处(第一个参数),用第二个参数的值去替代。

对于LinkedList: addFirst和add的作用都是相同的,在线性表尾端添加。getFirst = element; removeFirst = remove,分别是移除线性表头部(保留/ 不保留)。removeLast是移除线性表(队列)尾端的元素。

Stack: Stack是与Queue相反的容器,LIFO。 用LinkedList的方法来构造一个Stack的代码如下:

import java.util.LinkedList; // do not use *, since there is a Stack in util
public class Stack<T> {
private LinkedList<T> storage_ = new LinkedList<T> ();
public void push(T instance) {
storage_.addFirst(instance);
}
public T peek() {
return storage_.getFirst();
}
public T pop {
return storage_.removeFirst();
}
}


或者:

public class Stack<T> {
private LinkedList<T> storage_ = new LinkedList<T> ();
public void push(T instance) {
storage_.addLast(instance);
}
public T peek() {
return storage_.getLast();
}
public T pop {
return storage_.removeLast();
}
}


Set: HashSet, TreeSet 和 LinkedHashSet。Set接口和Collection接口的方法完全相同,并没有添加任何额外功能。所以,Set就是Collection,只是行为不同。

Queue: 队列常用的实现类有两个,LinkedList和PriorityQueue。

LinkedList属于Queue的方法有:

offer() -- 将一个元素插入队尾,成功返回true,否则false。

peek()和element() -- 返回队头元素,但不从队列提取该元素。peek()在队列为空时返回null,element() 则抛出NoSuchElementException的异常。

poll()和remove() -- 移除队头元素并返回该元素。poll()在空时返回null,remove() 则抛出NoSuchElementException的异常。

对于LinkedList, 另外需要说明的是:getFirst = element; removeFirst = remove。

PriorityQueue的方法与LinkedList相似。但可以通过自定义Comparator来实现改变FIFO的规则。如果没有自定义Comparator(默认升序),默认为FIFO。

Map类下,有以下map的常用实现类:HashMap、ConcurrentHashMap和TreeMap。

HashMap v.s. HashTable:

HashTable是很早的Java用来实现哈希表的一个类。其继承的父类是Dictionary类,现在已经是不推荐使用的一个类。

两者之间的区别请见这篇文章:http://blog.csdn.net/u010983881/article/details/49762595 和 http://www.importnew.com/7010.html

这里稍微总结一下:

1)HashMap是非synchronized的,而HashTable是Synchronized的。sychronized意味着在一次仅有一个线程能够更改Hashta
4000
ble。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。HashMap的这一缺点,用ConcurrentHashMap去弥补了。

2)HashMap可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行。

3)HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

HashMap可以通过下面的语句进行同步:

Map m = Collections.synchronizeMap(hashMap);

HashMap v.s. TreeMap:

HashMap不确保插入的顺序保存,而TreeMap 可以。

补充知识:

1)字典排序

按照ASCII的编码顺序排序。这里说说常见的几个编码:48 - 数字0, 65 - A, 97 - a, 空格 - 32。在Java中不得不提一个字符串类自带的比较器产生方法。 String.CASE_INSENSITIVE_ORDER会返回Comparator<String> 。不区分大小写,利用字母顺序排序。

2)填充具体容器对象:

(1)使用Collection的方法:

Collection<Integer> c = new ArrayList<Integer>();

int eg = 1;

c.add(1); // autobox int to integer

Collection方法只有add, remove, equal,clear, contains这些基本方法,并不能在中间添加删除元素。但List补充了从中间添加删除的方法。

(2) 使用Arrays和Collections类的方法,注意类名包含“s”。这两个类通常针对整个数组或者集合进行操作。

Arrays.asList()接受一个数组或者是一个用逗号分隔的元素列表(使用可变参数),将参数的内容转变为List对象。 

e.g.: 

Arrays.asList(1,2,3,4,5);

Arrays.asList((1 2 3 4 5).split(" "));

Collections.addAll() 的第一个参数是需要添加元素的集合,之后的参数是被添加的元素(使用可变参数)。被添加的元素可以是一个数组或者一个用逗号分割的列表。

最优的办法是使用Collections.addAll()。因为asList返回的List不能使用add(), delete()等方法。根本原因是返回的List是以数组作为底层,不能改变数组的大小。

(3)使用Collection的构造函数,Collection的构造函数接受另一个Collection作为参数,直接复制。

(4)使用Collection.addAll(),注意不是Collections。但是Collection.addAll()只能接受另外一个Collection作为参数,没有可变参数。

3)打印效果

Collection打印出来的效果是方括号括住,元素由逗号分隔。而Map则用大括号括住,键与值用等号联系。

4)如何遍历一个字符串的每个字符

for (char c : string.toCharArray())

System.out.println(c);

5) 如何去除一个List的重复元素

Set<T> temp = new HashSet<T> (tempList); 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息