java容器之一_概述
来源:互联网 发布:比较好的程序员论坛 编辑:程序博客网 时间:2024/05/18 22:40
1 数组
数组( Array )是一种数据结构,用来存储同一类型值的集合,该类型的值可以是基本类型也可以是对象类型,通过一个整型下标可以访问数据的每一个值,(ps:通过反射模式也可以访问数组元素,即可以是 Array.get()操作,具体方法请查看相关API)
数组的基本使用方法就不需要具体介绍了,主要是特点介绍和一般容器类的对比。不同的书本或博客可能对比的方面具体不同
1.1 基本情况:
唯一:
- 访问方法唯一 [] (补充:可以使用反射模式Array)
- 属性或方法唯一 length
不同:
- 对象数组 保留的是引用
- 基本类型数组 直接保存基本类型的值
不确定:
- 只能知道数组大小,无法确切知道有多少元素
1.2 对比:
- 效率 数组是一种效率最高的存储和随机访问对象引用序列的方法
- 类型 数组可以处理具体类型数据
- 通用的容器类 不处理具体类型,所有对象都按Object类型处理。不能保存基本类型。
- 特点 数组:大小固定 生命周期不可改变
- 容器:灵活性强 全object类型处理 相互间可以转换
1.3 扩展了解:
Java标准库:
- System.arraycopy() 比for要高效多的复制数组
- 对象是采用的浅复制引用。
1.4 基本方法:
四个基本方法(都被基本类型和Object重载过):
- equals() 比较两个数组是否相等
- fill() 用某个值填充整个数组
- sort() 对数组进行排序 Comparable 接口
- binarySearch() 在已经排序的数组中查找元素
asList () 将数组 转换为List容器
- sort() 比较接口也可以使用策略模式(strategy)
1.5 详细解释:
效率和类型。对于Java来说,为保存和访问一系列对象(实际是对象的句柄)数组,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的访问速度非常快,但我们却要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可在那个数组对象的"存在时间"内发生改变。可创建特定大小的一个数组,然后假如用光了存储空间,就再创建一个新数组,将所有句柄从旧数组移到新数组。这属于"矢量"(Vector)类的行为。然而,由于为这种大小的灵活性要付出较大的代价,所以我们认为矢量的效率并没有数组高。
C++的矢量类知道自己容纳的是什么类型的对象,但同Java的数组相比,它却有一个明显的缺点:C++矢量类的operator[]不能进行范围检查,所以很容易超出边界(然而,它可以查询vector有多大,而且at()方法确实能进行范围检查)。在Java中,无论使用的是数组还是集合,都会进行范围检查,若超过边界,就会获得一个RuntimeException(运行期违例)错误。这类违例指出的是一个程序员错误,所以不需要在代码中检查它。在另一方面,由于C++的vector不进行范围检查,所以访问速度较快,在Java中,由于对数组和集合都要进行范围检查,所以对性能有一定的影响。
接下来还要学习另外几种常见的集合类:List(队列)、Stack(堆栈)以及Hashtable(散列表)。这些类都涉及对对象的处理,-好象它们没有特定的类型。换言之,它们将其当作Object类型处理(Object类型是Java中所有类的"根"类)。从某个角度看,这种处理方法是非常合理的:我们仅需构建一个集合,然后任何Java对象都可以进入那个集合(除基本数据类型外,-可用Java的基本类型封装类将其作为常数置入集合,或者将其封装到自己的类内,作为可以变化的值使用)。这再一次反映了数组优于常规集合:创建一个数组时,可令其容纳一种特定的类型。这意味着可进行编译期类型检查,预防自己设置了错误的类型,或者错误指定了准备提取的类型。当然,在编译期或者运行期,Java会防止我们将不当的消息发给一个对象。所以我们不必考虑自己的哪种做法更加危险,只要编译器能及时地指出错误,同时在运行期间加快速度,目的也就达到了。此外,用户很少会对一次违例事件感到非常惊讶的。
考虑到执行效率和类型检查,应尽可能地采用数组。然而,当我们试图解决一个更常规的问题时,数组的局限也可能显得非常明显。(也不推荐使用Vector类 属于比较老的方法)
数组的这种局限性也是相对的,下面提供一个对数组使用比较深入的博文地址:
http://cping1982.blog.51cto.com/601635/130062/
2 容器简介和接口图
2.1 接口关系
(图 1.1)
(图 2.2)
2.2 容器简介
容器( Collection )是最基本的集合接口,一个容器( Collection )保存一组对象(Object ),即对象是容器的元素(Elements )。一些 Collection 允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK 不提供直接继承自Collection 的类, Java SDK 提供的类都是继承自Collection的 " 子接口 " 如List 和 Set 。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的 Collection,有一个 Collection参数的构造函数用于创建一个新的 Collection ,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个 Collection。
如何遍历Collection 中的每一个元素?不论 Collection的实际类型如何,它都支持一个 iterator() 的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子 while(it.hasNext()) { Object obj = it.next(); // 得到下一个元素 }
由Collection接口派生的两个接口是List 和 Set 。 List 按对象进入的顺序保存对象,不做排序或编辑操作。Set 对每个对象只接受一次,并使用自己内部的排序方法 ( 通常,你只关心某个元素是否属于 Set, 而不关心它的顺序 -- 否则应该使用List) 。
3 基本特征介绍
3.1 接口 list set map介绍与简单对比
List:关心的是顺序,它保证维护元素特定的顺序(允许有相同元素),使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在 List 中的位置,类似于数组下标)来访问 List 中的元素。即可以通过下标 (1,2..) 来取得值,值可以重复。
Set:仅接受一次,并做内部排序。只关心某元素是否属于 Set (不允许有相同元素),而不关心它的顺序。只能通过游标来取值,并且值是不能重复的。
Map: 最大的特点是键值映射,且为一一映射,键不能重复,值可以,所以是用键来索引值。 方法 put(Object key, Object value) 添 加一个"值" ( 想要得东西 ) 和与"值"相关联的"键" (key) ( 使用它来查找 ) 。方法 get(Object key) 返回与给定"键"相关联的"值" Map同样对每个元素保存一份,但这是基于 " 键 " 的, Map 也有内置的排序,因而不关心元素添加的顺序。
简单来说 如果是注重顺序,优选实现了list接口的容器。注重键值管理,优选实现了map接口的容器,如果是单纯集合处理 就选实现了set接口的容器。
3.2 list set map实现类的介绍与简单对比
(1)简单对比
没有同步线程不安全
4 常用类的详细说明
4.1 继承list或list子类的常用类
List(interface): 次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。
ArrayList: 由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。 LinkedList: 由列表实现的List。对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
4.2 继承Set接口的常用类
Set(interface): 存入Set的每个元素必须是唯一的,这也是与List不同的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet:HashSet能快速定位一个元素,存入HashSet的对象必须定义hashCode()。
TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
HashSet采用散列函数对元素进行排序,这是专门为快速查询而设计的;TreeSet采用红黑树的数据结构进行排序元素。
LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法
4.3 继承Map接口或Map子类常用类
请注意,Map没有继承Collection接口,Map提供key到 value的映射,你可以通过"键"查找"值"。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3 种集合的视图,Map的内容可以被当作一组key集合、一组value集合或者一组key-value映射。
方法put(Object key, Object value)添加一个"值"(想要得东西)和与"值"相关联的"键" (key) (使用它来查找) 。方法get(Object key)返回与给定"键"相关联的"值"。可以用 containsKey()和containsValue()测试Map中是否包含某个"键"或"值"。标准的Java 类库中包含了几种不同的 Map:HashMap、TreeMap、 LinkedHashMap、WeakHashMap和IdentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定"键"等价的策略等各不相同。Map同样对每个元素保存一份,但这是基于 "键"的,Map也有内置的排序,因而不关心元素添加的顺序。如果添加元素的顺序对你很重要,应该使用LinkedHashSet或者LinkedHashMap。
执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索"键"是相当慢的,而这正是HashMap提高速度的地方。HashMap使用了特殊的值,称为"散列码"(hash code),来取代对键的缓慢搜索。"散列码"是"相对唯一"用以代表对象的int 值,它是通过将该对象的某些信息进行转换而生成的(在下面总结二:需要的注意的地方有更进一步探讨)。所有Java对象都能产生散列码,因为hashCode()是定义在基类Object中的方法。HashMap就是使用对象的hashCode()进行快速查询的,此方法能够显著提高性能。Hashtable类 Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null )的对象都可作为key或者value。添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。 Hashtable通过初始化容量(initial capacity)和负载因子(load factor)两个参数调整性能。通常缺省的load factor 0.75 较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1、2、3放到Hashtable中,他们的key分别是"one"、"two"、"three": Hashtable numbers = new Hashtable(); numbers.put("one", new Integer(1)); numbers.put("two", new Integer(2)); numbers.put("three", new Integer(3)); 要取出一个数,比如2,用相应的key: Integer n = (Integer)numbers.get("two"); System.out.println("two = " + n); 由于作为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),插入和查询"键值对"的开销是固定的,但其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量(initial capacity)设得过高,或者负载因子(load factor)过低。 LinkedHashMap类:类似于HashMap,但是迭代遍历它时,取得"键值对"的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时反而更快,因为它使用链表维护内部次序。
WeakHashMap 类:弱键(weak key)Map是一种改进的 HashMap,它是为解决特殊问题设计的,对key 实行"弱引用 ",如果一个key不再被外部所引用(没有map之外的引用),那么该key可以被垃圾收集器(GC)回收。
TreeMap类
基于红黑树数据结构的实现。查看"键"或"键值对"时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于:你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
IdentityHashMap类
使用"=="代替equals()对"键"作比较的hashmap。专为解决特殊问题而设计。
5 小结
5.1 比较
数组 (Array),数组类(Arrays)
Java所有"存储及随机访问一连串对象"的做法,array是最有效率的一种。但缺点是容量固定且无法动态改变。array还有一个缺点是,无法判断其中实际存有多少元素, length只是告诉我们array的容量。Java中有一个数组类(Arrays),专门用来操作array。数组类(arrays)中拥有一组 static函数。
equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
fill():将值填入array中。
sort():用来对array进行排序。
binarySearch():在排好序的array中寻找元素。
System.arraycopy():array的复制。
若编写程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,则array不适用。
- 容器类与数组的区别
容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至数列某位置。一旦将对象置入容器内,便损失了该对象的型别信息。
- 容器 (Collection)与Map的联系与区别
Collection类型,每个位置只有一个元素。
Map类型,持有key-value对(pair),像个小型数据库。
Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。相当于对Array进行类似操作的类------Arrays。
如,Collections.max(Collection coll); 取coll中最大的元素
Collections.sort(List list); 对list中元素排序
List,Set,Map将持有对象一律视为Object 型别。Collection、List、Set、Map都是接口,不能实例化。继承自它们的ArrayList, Vector, HashTable, HashMap是具象class,这些才可被实例化。
vector容器确切知道它所持有的对象隶属什么型别。vector不进行边界检查。
5.2 需要注意的地方
1、Collection只能通过iterator()遍历元素,没有get()方法来取得某个元素。
2、Set和Collection拥有一模一样的接口。但排除掉传入的Collection参数重复的元素。
3、List可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)
4、Map用put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value 。
HashMap会利用对象的hashCode来快速找到key。哈希码 (hashing) 就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。发生碰撞时,让array指向多个values。即,数组每个位置上又生成一个梿表。
5、Map中元素,可以将key序列、value序列单独抽取出来。
使用keySet()抽取key序列,将map中的所有keys生成一个Set。
使用values()抽取value序列,将map中的所有values生成一个Collection。
为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。
5.3 如何选择
从效率角度:
在各种Lists中,对于需要快速插入,删除元素,应该使用LinkedList(可用LinkedList构造堆栈stack、队列queue),如果需要快速随机访问元素,应该使用ArrayList。最好的做法是以ArrayList作为缺省选择。Vector总是比ArrayList慢,所以要尽量避免使用。
在各种Sets中,HashSet通常优于TreeSet(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。TreeSet存在的唯一理由:能够维护其内元素的排序状态。
在各种Maps中HashMap用于快速查找。HashMap:适用于在Map中插入、删除和定位元素。 TreeMap:适用于按自然顺序或自定义顺序遍历键(key)。HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap。
最后,当元素个数固定,用Array,因为Array效率是最高的。所以结论:最常用的ArrayList,HashSet,HashMap, Array。
5.4 更进一步分析:
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。要特别注意对哈希表的操作,作为key的对象要同时正确复写equals方法和hashCode方法。尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。如果添加元素的顺序对你很重要,应该使用LinkedHashSet或者LinkedHashMap。
例外 也可以在没有同步的HashMap使用Collections类的静态的synchronizedMap()方法,它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。这个对象的方法可以让你同步访问潜在的HashMap。这么做的结果就是当你不需要同步时,你不能切断Hashtable中的同步(比如在一个单线程的应用程序中),而且同步增加了很多处理费用
5.5 测试
选有代表性的容器测试 HashMap 和 TreeMap
import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; public class HashMaps { public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("a", "aaa"); map.put("b", "bbb"); map.put("c", "ccc"); map.put("d", "ddd"); Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println("map.get(key) is :" + map.get(key)); } // 定义HashTable,用来测试 Hashtable<String, String> tab = new Hashtable<String, String>(); tab.put("a", "aaa"); tab.put("b", "bbb"); tab.put("c", "ccc"); tab.put("d", "ddd"); Iterator<String> iterator_1 = tab.keySet().iterator(); while (iterator_1.hasNext()) { Object key = iterator_1.next(); System.out.println("tab.get(key) is :" + tab.get(key)); } TreeMap<String, String> tmp = new TreeMap<String, String>(); tmp.put("a", "aaa"); tmp.put("b", "bbb"); tmp.put("c", "ccc"); tmp.put("d", "ddd"); Iterator<String> iterator_2 = tmp.keySet().iterator(); while (iterator_2.hasNext()) { Object key = iterator_2.next(); System.out.println("tmp.get(key) is :" + tmp.get(key)); } } }
测试结果 如下 HashMap 输出无序,TreeMap输出有序,Map 是堆栈结构。
map.get(key) is :ddd
map.get(key) is :bbb
map.get(key) is :ccc
map.get(key) is :aaa
tab.get(key) is :bbb
tab.get(key) is :aaa
tab.get(key) is :ddd
tab.get(key) is :ccc
tmp.get(key) is :aaa
tmp.get(key) is :bbb
tmp.get(key) is :ccc
tmp.get(key) is :ddd
6 扩展了解
6.1 关于Properties
Java.util.Properties类是Hashtable的一个子类,设计用于String keys和values。Properties对象的用法同Hashtable的用法相象,但是类增加了两个节省时间的方法,你应该知道。
Store()方法把一个Properties对象的内容以一种可读的形式保存到一个文件中。Load()方法正好相反,用来读取文件,并设定Properties对象来包含keys和values。
注意,因为Properties扩展了Hashtable,你可以用超类的put()方法来添加不是String对象的keys和values。这是不可取的。另外,如果你将store()用于一个不包含String对象的Properties对象,store()将失败。作为put()和get()的替代,你应该用setProperty()和getProperty(),它们用String参数。
6.2 关于Element的HashCode
Element的HashCode方法继承自Object,而Object中的HashCode方法返回的HashCode对应于当前的地址,也就是说对于不同的对象,即使它们的内容完全相同,用HashCode()返回的值也会不同。这样实际上违背了我们的意图。因为我们在使用 HashMap时,希望利用相同内容的对象索引得到相同的目标对象,这就需要HashCode()在此时能够返回相同的值。在上面的例子中,我们期望 new Element (i=5)与 Elementtest=newElement(5)是相同的,而实际上这是两个不同的对象,尽管它们的内容相同,但它们在内存中的地址不同。
6.3 红黑树
红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。红黑树的持久版本对每次插入或删除需要O(log n)的空间。
7 总结
1 数据容器都离不开基本数据结构的支持。
2 选择已有容器时,先区分是需要用到的set map(对应于群论 和 键值)。
3 确定数据操作的主要瓶颈和需求。即那种操作最常见,那种操作必须满足什么要求,选择满足必须要求时,最优与常见操作的数据容器。
数组可以保存基本类型 而容器不行 容器可以放置任何java对象。
(基本类型 可以使用java包装类将其作为常量包装后存入容器。或者用你自己的类将其变量包装起来存入容器 比如数组封装 http://cping1982.blog.51cto.com/601635/130062/ )
- java容器之一_概述
- Java容器类概述
- java容器概述
- Java容器--接口概述
- java容器类---概述
- JAVA集合容器--概述
- Java容器类类库概述
- java容器框架概述
- Java基础_容器
- DICOM标准之一_介绍和概述
- java概述_练习
- JAVA容器分析之概述
- java容器(一)概述
- Java容器类框架概述
- java基础知识_容器类
- java面向对象_概述
- 黑马程序员:java学习之一 java概述
- java基础13_容器_集合_比较器
- Android动画之帧动画
- 一种提高Android应用进程存活率新方法
- 晶闸管输出光耦合器TLP541参数及应用实例
- iOS alloc、init和new方法
- ios 绑定事件无效
- java容器之一_概述
- npm 源管理 nrm
- 关于读W3C文档的收获-html篇-a
- jQuery插件来调整表列的大小colResizable.js 拖动改变单元格宽度
- VMware中Nat方式设置静态IP
- 美团渠道打包简单粗暴快速版
- Ext.EditGrid-->beforeedit 例子
- L先生与阶乘
- IntelliJ IDEA安装