Java整理(三)集合框架和数据结构
Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类:
集合框架(Collection)
枚举(Enumeration)
位集合(BitSet)
向量(Vector)
队列(Queue)
栈(Stack)
字典(Dictionary)
哈希表(Hashtable)
属性(Properties)
集合框架(Collection)
集合包最常用的有Collection和Map两个接口的实现类,Colleciton用于存放多个单对象,Map用于存放Key-Value形式的键值对。
Collection中最常用的又分为两种类型的接口:List和Set,两者最明显的差别为List支持放入重复的元素,而Set不支持。
List最常用的实现类有:ArrayList、LinkedList、Vector及Stack;Set接口常用的实现类有:HashSet、TreeSet。
常见集合框架介绍
Collection 接口 | List接口 | ArrayList实现类 | |
LinkedList实现类 | |||
Vector实现类 | Stack实现类 | ||
Queue接口 | |||
Set接口 | SortedSet接口 | ||
HashSet实现类 | LinkedHashSet实现类 | ||
TreeSet实现类 | |||
Map接口 | SortedMap接口 |
| |
HashMap实现类 | LinkedHashMap实现类 | ||
TreeMap实现类 |
| ||
WeakHashMap实现类 |
| ||
IdentityHashMap实现类 |
| ||
Hashtable实现类 | Properties实现类 |
集合框架定义了一些接口。本节提供了每个接口的概述:
序号 | 接口描述 |
1 | Collection 接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。 |
2 | List 接口 List接口是一个有序的Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的小标)来访问List中的元素,而且允许有相同的元素。 |
3 | Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。 |
4 | SortedSet |
5 | Map |
6 | Map.Entry |
7 | SortedMap |
8 | Enumeration |
ArrayList类
ArrayList基于数组方式实现,默认构造器通过调用ArrayList(int)来完成创建,传入的值为10,实例化了一个Object数组,并将此数组赋给了当前实例的elementData属性,此Object数组的大小即为传入的initialCapacity,因此调用空构造器的情况下会创建一个大小为10的Object数组。
插入对象:add(E)
基于已有元素数量加1作为名叫minCapacity的变量,比较此值和Object数组的大小,若大于数组值,那么先将当前Object数组值赋给一个数组对象,接着产生一个新的数组容量值。此值的计算方法为当前数组值*1.5+1,如得出的容量值仍然小于minCapacity,那么就以minCapacity作为新的容量值,调用Arrays.copyOf来生成新的数组对象。
还提供了add(int,E)这样的方法将元素直接插入指定的int位置上,将目前index及其后的数据都往后挪一位,然后才能将指定的index位置的赋值为传入的对象,这种方式要多付出一次复制数组的代价。还提供了addAll
删除对象:remove(E)
这里调用了faseRemove方法将index后的对象往前复制一位,并将数组中的最后一个元素的值设置为null,即释放了对此对象的引用。还提供了remove(int)方法来删除指定位置的对象,remove(int)的实现比remove(E)多了一个数组范围的检测,但少了对象位置的查找,因此性能会更好。
获取单个对象:get(int)
遍历对象:iterator()
判断对象是否存在:contains(E)
总结:
1,ArrayList基于数组方式实现,无容量的限制;
2,ArrayList在执行插入元素时可能要扩容,在删除元素时并不会减小数组的容量(如希望相应的缩小数组容量,可以调用ArrayList的trimToSize()),在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找;
3,ArrayList是非线程安全的。可以使用CopyOnWriteArrayList保证线程安全。
LinkedList类
LinkedList基于双向链表机制,所谓双向链表机制,就是集合中的每个元素都知道其前一个元素及其后一个元素的位置。在LinkedList中,以一个内部的Entry类来代表集合中的元素,元素的值赋给element属性,Entry中的next属性指向元素的后一个元素,Entry中的previous属性指向元素的前一个元素,基于这样的机制可以快速实现集合中元素的移动。
总结:
1,LinkedList基于双向链表机制实现;
2,LinkedList在插入元素时,须创建一个新的Entry对象,并切换相应元素的前后元素的引用;在查找元素时,须遍历链表;在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将此元素删除即可,此时原有的前后元素改变引用连在一起;
3,LinkedList是非线程安全的。
HashSet类
默认构造创建一个HashMap对象,HashSet是基于HashMap实现的。
add(E):调用HashMap的put方法来完成此操作,将需要增加的元素作为Map中的key,value则传入一个之前已创建的Object对象。
remove(E):调用HashMap的remove(E)方法完成此操作。
contains(E):HashMap的containsKey
iterator():调用HashMap的keySet的iterator方法。
HashSet不支持通过get(int)获取指定位置的元素,只能自行通过iterator方法来获取。
总结:
1,HashSet基于HashMap实现,无容量限制;
2,HashSet是非线程安全的。
TreeSet和HashSet的主要不同在于TreeSet对于排序的支持,TreeSet基于TreeMap实现。
HashMap类
HashMap空构造,将loadFactor设为默认的0.75,threshold设置为12,并创建一个大小为16的Entry对象数组。
基于数组+链表的结合体(链表散列)实现,当链表长度大于8时,则转换为红黑树实现,减少查找时间复杂度,将key-value看成一个整体,存放于Entity[]数组,put的时候根据key hash后的hashcode和数组length-1按位与的结果值判断放在数组的哪个位置,如果该数组位置上若已经存放其他元素,则在这个位置上的元素以链表的形式存放。如果该位置上没有元素则直接存放。
当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把Map集合中的value当成key的附属,当系统决定了key的存储位置之后,value随之保存在那里即可。get取值也是根据key的hashCode确定在数组的位置,在根据key的equals确定在链表处的位置。
1 while (capacity < initialCapacity)
2 capacity <<= 1;
以上代码保证了初始化时HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方。它通过h & (table.length -1) 来得到该对象的保存位,若length为奇数值,则与运算产生相同结果,便会形成链表,尽可能的少出现链表才能提升hashMap的效率,所以这是hashMap速度上的优化。
扩容resize():
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。
负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。
HashMap的实现中,通过threshold字段来判断HashMap的最大容量。threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。
initialCapacity*2,成倍扩大容量,HashMap(int initialCapacity,float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。不设定参数,则初始容量值为16,默认的负载因子为0.75,不宜过大也不宜过小,过大影响效率,过小浪费空间。扩容后需要重新计算每个元素在数组中的位置,是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
HashTable数据结构的原理大致一样,区别在于put、get时加了同步关键字,而且HashTable不可存放null值。
在高并发时可以使用ConcurrentHashMap,其内部使用锁分段技术,维持这锁Segment的数组,在数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。
总结:
1,HashMap采用数组方式存储key、value构成的Entry对象,无容量限制;
2,HashMap基于key hash寻找Entry对象存放到数组的位置,对于hash冲突采用链表的方式解决;
3,HashMap在插入元素时可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中;
4,HashMap是非线程安全的。
TreeMap基于红黑树的实现,因此它要求一定要有key比较的方法,要么传入Comparator实现,要么key对象实现Comparable借口。在put操作时,基于红黑树的方式遍历,基于comparator来比较key应放在树的左边还是右边,如找到相等的key,则直接替换掉value。
遍历 ArrayList
public class Main { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("Hello"); list.add("World"); list.add("HAHAHAHA"); //第一种遍历方法使用foreach遍历List for (String str : list) { //也可以改写for(int i=0;i<list.size();i++)这种形式 System.out.println(str); } //第二种遍历,把链表变为数组相关的内容进行遍历 String[] strArray = new String[list.size()]; list.toArray(strArray); for (int i = 0; i < strArray.length; i++) { //这里也可以改写为 foreach(String str:strArray)这种形式 System.out.println(strArray[i]); } //第三种遍历 使用迭代器进行相关遍历 Iterator<String> ite = list.iterator(); while (ite.hasNext()) { //判断下一个元素之后有值 System.out.println(ite.next()); } } }
遍历 Map
public class Main{ public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("1", "value1"); map.put("2", "value2"); map.put("3", "value3"); //第一种:普遍使用,二次取值 System.out.println("通过Map.keySet遍历key和value:"); for (String key : map.keySet()) { System.out.println("key= "+ key + " and value= " + map.get(key)); } //第二种 System.out.println("通过Map.entrySet使用iterator遍历key和value:"); Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } //第三种:推荐,尤其是容量大时 System.out.println("通过Map.entrySet遍历key和value"); for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } //第四种 System.out.println("通过Map.values()遍历所有的value,但不能遍历key"); for (String v : map.values()) { System.out.println("value= " + v); } } }
传统集合特设类
Enumeration接口
Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。
这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。
常用方法:
boolean hasMoreElements( )//测试此枚举是否包含更多的元素。
Object nextElement( )//如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。
Bitset类
一个Bitset类创建一种特殊类型的数组来保存位值。
构造方法:
BitSet()
BitSet(int size)//初始化为0
方法摘要 | |
void | and(BitSet set) |
void | andNot(BitSet set) |
int | cardinality() |
void | clear() |
void | clear(int bitIndex) |
void | clear(int fromIndex, int toIndex) |
clone() | |
boolean | equals(Object obj) |
void | flip(int bitIndex) |
void | flip(int fromIndex, int toIndex) |
boolean | get(int bitIndex) |
get(int fromIndex, int toIndex) | |
int | hashCode() |
boolean | intersects(BitSet set) |
boolean | isEmpty() |
int | length() |
int | nextClearBit(int fromIndex) |
int | nextSetBit(int fromIndex) |
void | or(BitSet set) |
void | set(int bitIndex) |
void | set(int bitIndex, boolean value) |
void | set(int fromIndex, int toIndex) |
void | set(int fromIndex, int toIndex, boolean value) |
int | size() |
toString() | |
void | xor(BitSet set) |
Vector类
Vector类实现了一个动态数组。和ArrayList和相似,但是两者是不同的:
1、Vector是同步访问的。
2、Vector包含了许多传统的方法,这些方法不属于集合框架。
其add、remove、get(int)方法都加了synchronized关键字,默认创建一个大小为10的Object数组,并将capacityIncrement设置为0。容量扩充策略:如果capacityIncrement大于0,则将Object数组的大小扩大为现有size加上capacityIncrement的值;如果capacity等于或小于0,则将Object数组的大小扩大为现有size的两倍,这种容量的控制策略比ArrayList更为可控。
Vector是基于Synchronized实现的线程安全的ArrayList,但在插入元素时容量扩充的机制和ArrayList稍有不同,并可通过传入capacityIncrement来控制容量的扩充。
Queue类
队列(queue)是一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则。Java中,LinkedList实现了Queue接口,因为LinkedList进行插入、删除操作效率较高
相关常用方法:
boolean offer(E e):将元素追加到队列末尾,若添加成功则返回true。
E poll():从队首删除并返回该元素。
E peek():返回队首元素,但是不删除
Queue<String> queue = new LinkedList<String>(); queue.offer("a");//追加元素 queue.offer("b"); queue.offer("c"); queue.add("1");//同offer System.out.println(queue);//输出[a, b, c, 1] String poll = queue.poll();//从队首取出元素并删除 queue.remove();//删除队首,同poll String peek = queue.peek();//从队首取出元素但是不删除 queue.element();//同peek
双向队列(Deque),是Queue的一个子接口,双向队列是指该队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则
常用方法如下:
void push(E e):将给定元素”压入”栈中。存入的元素会在栈首。即:栈的第一个元素
E pop():将栈首元素删除并返回。
Deque<String> deque = new LinkedList<String>(); deque.push("a"); deque.push("b"); deque.push("c"); deque.add("1");//add在队尾加入元素 deque.push("2");//push类似于压入栈 deque.addFirst("x");//头加入,类似于push deque.addLast("y");//尾加入,类似于add deque.contains("1");//是否包含该元素true/false deque.element();//栈首元素,不出栈 deque.offerFirst("(");//队首插入,返回boolean deque.offerLast(")");//队尾插入,返回boolean System.out.println(deque);//输出[(, x, 2, c, b, a, 1, y, )] String str = deque.peek();//获取栈首元素后,元素不会出栈 str=deque.pop();//获取栈首元素,元素出栈
Stack类
栈是Vector的一个子类,它实现了一个标准的后进先出的栈。
堆栈只定义了默认构造函数,用来创建一个空栈。
Stack继承于Vector,在其基础上实现了Stack所要求的后进先出(LIFO)的弹出与压入操作,其提供了push、pop、peek三个主要的方法:
ush操作通过调用Vector中的addElement来完成;
pop操作通过调用peek来获取元素,并同时删除数组中的最后一个元素;
peek操作通过获取当前Object数组的大小,并获取数组上的最后一个元素。
Dictionary类
Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似。
此类已过时。新的实现应该实现 Map 接口,而不是扩展此类。
方法摘要 | |
abstract Enumeration<V> | elements() |
abstract V | get(Object key) |
abstract boolean | isEmpty() |
abstract Enumeration<K> | keys() |
abstract V | |
abstract V | remove(Object key) |
abstract int | size() |
Hashtable类
Hashtable是原始的java.util的一部分, 是一个Dictionary具体的实现 。
然而,Java 2 重构的Hashtable实现了Map接口,因此,Hashtable现在集成到了集合框架中。它和HashMap类很相似,但是它支持同步。非同步环境下推荐使用HashMap,同步环境下推荐使用ConcurrentHashMap。
像HashMap一样,Hashtable在哈希表中存储键/值对。
构造方法摘要 | |
Hashtable() | |
Hashtable(int initialCapacity) | |
Hashtable(int initialCapacity, float loadFactor) | |
Hashtable(Map<? extends K,? extends V> t) |
方法摘要 | |
void | clear() |
clone() | |
boolean | contains(Object value) |
boolean | containsKey(Object key) |
boolean | containsValue(Object value) |
elements() | |
entrySet() | |
boolean | equals(Object o) |
get(Object key) | |
int | hashCode() |
boolean | isEmpty() |
keys() | |
keySet() | |
void | putAll(Map<? extends K,? extends V> t) |
protected void | rehash() |
remove(Object key) | |
int | size() |
toString() | |
values() |
Properties类
Properties 继承于 Hashtable.Properties 类表示了一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。
字段摘要 |
| ||
protected Properties | defaults |
| |
构造方法摘要 | |||
Properties() | |||
Properties(Properties defaults) | |||
方法摘要 | |||
getProperty(String key) | |||
getProperty(String key, String defaultValue) | |||
void | list(PrintStream out) | ||
void | list(PrintWriter out) | ||
void | load(InputStream inStream) | ||
void | load(Reader reader) | ||
void | loadFromXML(InputStream in) | ||
Enumeration<?> | propertyNames() | ||
void | save(OutputStream out, String comments) | ||
void | store(OutputStream out, String comments) | ||
void | store(Writer writer, String comments) | ||
void | storeToXML(OutputStream os, String comment) | ||
void | storeToXML(OutputStream os, String comment, String encoding) | ||
stringPropertyNames() |
Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类:
枚举(Enumeration)
位集合(BitSet)
向量(Vector)
栈(Stack)
字典(Dictionary)
哈希表(Hashtable)
属性(Properties)
集合框架(Collection)
集合框架(Collection)
集合包最常用的有Collection和Map两个接口的实现类,Colleciton用于存放多个单对象,Map用于存放Key-Value形式的键值对。
Collection中最常用的又分为两种类型的接口:List和Set,两者最明显的差别为List支持放入重复的元素,而Set不支持。
List最常用的实现类有:ArrayList、LinkedList、Vector及Stack;Set接口常用的实现类有:HashSet、TreeSet。
常见集合框架介绍
Collection 接口 | List接口 | ArrayList实现类 | |
LinkedList实现类 | |||
Vector实现类 | Stack实现类 | ||
Queue接口 | |||
Set接口 | SortedSet接口 | ||
HashSet实现类 | LinkedHashSet实现类 | ||
TreeSet实现类 | |||
Map接口 | SortedMap接口 |
| |
HashMap实现类 | LinkedHashMap实现类 | ||
TreeMap实现类 |
| ||
WeakHashMap实现类 |
| ||
IdentityHashMap实现类 |
| ||
Hashtable实现类 | Properties实现类 |
集合框架定义了一些接口。本节提供了每个接口的概述:
序号 | 接口描述 |
1 | Collection 接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。 |
2 | List 接口 List接口是一个有序的Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的小标)来访问List中的元素,而且允许有相同的元素。 |
3 | Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。 |
4 | SortedSet |
5 | Map |
6 | Map.Entry |
7 | SortedMap |
8 | Enumeration |
ArrayList类
ArrayList基于数组方式实现,默认构造器通过调用ArrayList(int)来完成创建,传入的值为10,实例化了一个Object数组,并将此数组赋给了当前实例的elementData属性,此Object数组的大小即为传入的initialCapacity,因此调用空构造器的情况下会创建一个大小为10的Object数组。
插入对象:add(E)
基于已有元素数量加1作为名叫minCapacity的变量,比较此值和Object数组的大小,若大于数组值,那么先将当前Object数组值赋给一个数组对象,接着产生一个新的数组容量值。此值的计算方法为当前数组值*1.5+1,如得出的容量值仍然小于minCapacity,那么就以minCapacity作为新的容量值,调用Arrays.copyOf来生成新的数组对象。
还提供了add(int,E)这样的方法将元素直接插入指定的int位置上,将目前index及其后的数据都往后挪一位,然后才能将指定的index位置的赋值为传入的对象,这种方式要多付出一次复制数组的代价。还提供了addAll
删除对象:remove(E)
这里调用了faseRemove方法将index后的对象往前复制一位,并将数组中的最后一个元素的值设置为null,即释放了对此对象的引用。 还提供了remove(int)方法来删除指定位置的对象,remove(int)的实现比remove(E)多了一个数组范围的检测,但少了对象位置的查找,因此性能会更好。
获取单个对象:get(int)
遍历对象:iterator()
判断对象是否存在:contains(E)
总结:
1,ArrayList基于数组方式实现,无容量的限制;
2,ArrayList在执行插入元素时可能要扩容,在删除元素时并不会减小数组的容量(如希望相应的缩小数组容量,可以调用ArrayList的trimToSize()),在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找;
3,ArrayList是非线程安全的。
LinkedList类
LinkedList基于双向链表机制,所谓双向链表机制,就是集合中的每个元素都知道其前一个元素及其后一个元素的位置。在LinkedList中,以一个内部的Entry类来代表集合中的元素,元素的值赋给element属性,Entry中的next属性指向元素的后一个元素,Entry中的previous属性指向元素的前一个元素,基于这样的机制可以快速实现集合中元素的移动。
总结:
1,LinkedList基于双向链表机制实现;
2,LinkedList在插入元素时,须创建一个新的Entry对象,并切换相应元素的前后元素的引用;在查找元素时,须遍历链表;在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将此元素删除即可,此时原有的前后元素改变引用连在一起;
3,LinkedList是非线程安全的。
HashSet类
默认构造创建一个HashMap对象
add(E):调用HashMap的put方法来完成此操作,将需要增加的元素作为Map中的key,value则传入一个之前已创建的Object对象。
remove(E):调用HashMap的remove(E)方法完成此操作。
contains(E):HashMap的containsKey
iterator():调用HashMap的keySet的iterator方法。
HashSet不支持通过get(int)获取指定位置的元素,只能自行通过iterator方法来获取。
总结:
1,HashSet基于HashMap实现,无容量限制;
2,HashSet是非线程安全的。
TreeSet和HashSet的主要不同在于TreeSet对于排序的支持,TreeSet基于TreeMap实现。
HashMap类
HashMap空构造,将loadFactor设为默认的0.75,threshold设置为12,并创建一个大小为16的Entry对象数组。
基于数组+链表的结合体(链表散列)实现,将key-value看成一个整体,存放于Entity[]数组,put的时候根据key hash后的hashcode和数组length-1按位与的结果值判断放在数组的哪个位置,如果该数组位置上若已经存放其他元素,则在这个位置上的元素以链表的形式存放。如果该位置上没有元素则直接存放。
当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把Map集合中的value当成key的附属,当系统决定了key的存储位置之后,value随之保存在那里即可。get取值也是根据key的hashCode确定在数组的位置,在根据key的equals确定在链表处的位置。
1 while (capacity < initialCapacity)
2 capacity <<= 1;
以上代码保证了初始化时HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方。它通过h & (table.length -1) 来得到该对象的保存位,若length为奇数值,则与运算产生相同结果,便会形成链表,尽可能的少出现链表才能提升hashMap的效率,所以这是hashMap速度上的优化。
扩容resize():
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。
负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。
HashMap的实现中,通过threshold字段来判断HashMap的最大容量。threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。
initialCapacity*2,成倍扩大容量,HashMap(int initialCapacity,float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。不设定参数,则初始容量值为16,默认的负载因子为0.75,不宜过大也不宜过小,过大影响效率,过小浪费空间。扩容后需要重新计算每个元素在数组中的位置,是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
HashTable数据结构的原理大致一样,区别在于put、get时加了同步关键字,而且HashTable不可存放null值。
在高并发时可以使用ConcurrentHashMap,其内部使用锁分段技术,维持这锁Segment的数组,在数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。
总结:
1,HashMap采用数组方式存储key、value构成的Entry对象,无容量限制;
2,HashMap基于key hash寻找Entry对象存放到数组的位置,对于hash冲突采用链表的方式解决;
3,HashMap在插入元素时可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中;
4,HashMap是非线程安全的。
TreeMap基于红黑树的实现,因此它要求一定要有key比较的方法,要么传入Comparator实现,要么key对象实现Comparable借口。在put操作时,基于红黑树的方式遍历,基于comparator来比较key应放在树的左边还是右边,如找到相等的key,则直接替换掉value。
遍历 ArrayList
import java.util.*;
public class Test{
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("Hello");
list.add("World");
list.add("HAHAHAHA");
//第一种遍历方法使用foreach遍历List
for (String str : list) { //也可以改写for(inti=0;i<list.size();i++)这种形式
System.out.println(str);
}
//第二种遍历,把链表变为数组相关的内容进行遍历
String[] strArray=new String[list.size()];
list.toArray(strArray);
for(int i=0;i<strArray.length;i++) //这里也可以改写为 foreach(String str:strArray)这种形式
{
System.out.println(strArray[i]);
}
//第三种遍历 使用迭代器进行相关遍历
Iterator<String> ite=list.iterator();
while(ite.hasNext())//判断下一个元素之后有值
{
System.out.println(ite.next());
}
}
}
遍历 Map
import java.util.*;
public class Test{
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种:普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " +map.get(key));
}
//第二种
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " andvalue= " + entry.getValue());
}
//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " andvalue= " + entry.getValue());
}
//第四种
System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
for (String v : map.values()) {
System.out.println("value= " + v);
}
}
}
传统集合特设类
Java Enumeration接口
Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。
这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。
常用方法:
boolean hasMoreElements( )//测试此枚举是否包含更多的元素。
Object nextElement( )//如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。
Java Bitset类
一个Bitset类创建一种特殊类型的数组来保存位值。
构造方法:
BitSet()
BitSet(int size)//初始化为0
方法摘要 | |
void | and(BitSet set) |
void | andNot(BitSet set) |
int | cardinality() |
void | clear() |
void | clear(int bitIndex) |
void | clear(int fromIndex, int toIndex) |
clone() | |
boolean | equals(Object obj) |
void | flip(int bitIndex) |
void | flip(int fromIndex, int toIndex) |
boolean | get(int bitIndex) |
get(int fromIndex, int toIndex) | |
int | hashCode() |
boolean | intersects(BitSet set) |
boolean | isEmpty() |
int | length() |
int | nextClearBit(int fromIndex) |
int | nextSetBit(int fromIndex) |
void | or(BitSet set) |
void | set(int bitIndex) |
void | set(int bitIndex, boolean value) |
void | set(int fromIndex, int toIndex) |
void | set(int fromIndex, int toIndex, boolean value) |
int | size() |
toString() | |
void | xor(BitSet set) |
Java Vector类
Vector类实现了一个动态数组。和ArrayList和相似,但是两者是不同的:
1、Vector是同步访问的。
2、Vector包含了许多传统的方法,这些方法不属于集合框架。
其add、remove、get(int)方法都加了synchronized关键字,默认创建一个大小为10的Object数组,并将capacityIncrement设置为0。容量扩充策略:如果capacityIncrement大于0,则将Object数组的大小扩大为现有size加上capacityIncrement的值;如果capacity等于或小于0,则将Object数组的大小扩大为现有size的两倍,这种容量的控制策略比ArrayList更为可控。
Vector是基于Synchronized实现的线程安全的ArrayList,但在插入元素时容量扩充的机制和ArrayList稍有不同,并可通过传入capacityIncrement来控制容量的扩充。
Java Queue类
队列(queue)是一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则。Java中,LinkedList实现了Queue接口,因为LinkedList进行插入、删除操作效率较高
相关常用方法:
boolean offer(E e):将元素追加到队列末尾,若添加成功则返回true。
E poll():从队首删除并返回该元素。
E peek():返回队首元素,但是不删除
Queue<String> queue = new LinkedList<String>(); queue.offer("a");//追加元素 queue.offer("b"); queue.offer("c"); queue.add("1");//同offer System.out.println(queue);//输出[a, b, c, 1] String poll = queue.poll();//从队首取出元素并删除 queue.remove();//删除队首,同poll String peek = queue.peek();//从队首取出元素但是不删除 queue.element();//同peek
双向队列(Deque),是Queue的一个子接口,双向队列是指该队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则
常用方法如下:
void push(E e):将给定元素”压入”栈中。存入的元素会在栈首。即:栈的第一个元素
E pop():将栈首元素删除并返回。
Deque<String> deque = new LinkedList<String>(); deque.push("a"); deque.push("b"); deque.push("c"); deque.add("1");//add在队尾加入元素 deque.push("2");//push类似于压入栈 deque.addFirst("x");//头加入,类似于push deque.addLast("y");//尾加入,类似于add deque.contains("1");//是否包含该元素true/false deque.element();//栈首元素,不出栈 deque.offerFirst("(");//队首插入,返回boolean deque.offerLast(")");//队尾插入,返回boolean System.out.println(deque);//输出[(, x, 2, c, b, a, 1, y, )] String str = deque.peek();//获取栈首元素后,元素不会出栈 str=deque.pop();//获取栈首元素,元素出栈
Java Stack类
栈是Vector的一个子类,它实现了一个标准的后进先出的栈。
堆栈只定义了默认构造函数,用来创建一个空栈。
Stack继承于Vector,在其基础上实现了Stack所要求的后进先出(LIFO)的弹出与压入操作,其提供了push、pop、peek三个主要的方法:
ush操作通过调用Vector中的addElement来完成;
pop操作通过调用peek来获取元素,并同时删除数组中的最后一个元素;
peek操作通过获取当前Object数组的大小,并获取数组上的最后一个元素。
Java Dictionary类
Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似。
注:此类已过时。新的实现应该实现 Map 接口,而不是扩展此类。
方法摘要 | |
abstract Enumeration<V> | elements() |
abstract V | get(Object key) |
abstract boolean | isEmpty() |
abstract Enumeration<K> | keys() |
abstract V | |
abstract V | remove(Object key) |
abstract int | size() |
Java Hashtable类
Hashtable是原始的java.util的一部分, 是一个Dictionary具体的实现 。
然而,Java 2 重构的Hashtable实现了Map接口,因此,Hashtable现在集成到了集合框架中。它和HashMap类很相似,但是它支持同步。
像HashMap一样,Hashtable在哈希表中存储键/值对。
构造方法摘要 | |
Hashtable() | |
Hashtable(int initialCapacity) | |
Hashtable(int initialCapacity, float loadFactor) | |
Hashtable(Map<? extends K,? extends V> t) |
方法摘要 | |
void | clear() |
clone() | |
boolean | contains(Object value) |
boolean | containsKey(Object key) |
boolean | containsValue(Object value) |
elements() | |
entrySet() | |
boolean | equals(Object o) |
get(Object key) | |
int | hashCode() |
boolean | isEmpty() |
keys() | |
keySet() | |
void | putAll(Map<? extends K,? extends V> t) |
protected void | rehash() |
remove(Object key) | |
int | size() |
toString() | |
values() |
Java Properties类
Properties 继承于 Hashtable.Properties 类表示了一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。
字段摘要 |
| ||
protected Properties | defaults |
| |
构造方法摘要 | |||
Properties() | |||
Properties(Properties defaults) | |||
方法摘要 | |||
getProperty(String key) | |||
getProperty(String key, String defaultValue) | |||
void | list(PrintStream out) | ||
void | list(PrintWriter out) | ||
void | load(InputStream inStream) | ||
void | load(Reader reader) | ||
void | loadFromXML(InputStream in) | ||
Enumeration<?> | propertyNames() | ||
void | save(OutputStream out, String comments) | ||
void | store(OutputStream out, String comments) | ||
void | store(Writer writer, String comments) | ||
void | storeToXML(OutputStream os, String comment) | ||
void | storeToXML(OutputStream os, String comment, String encoding) | ||
stringPropertyNames() |