JAVA菜鸟学习-数据结构
在Java中的数据结构主要包括以下几种接口和类:
枚举(Enumeration)
位集合(BitSet)
向量(Vector)
栈(Stack)
字典(Dictionary)
哈希表(Hashtable)
属性(Properties)
任何对象加入集合类后,自动转变为Object类型,所以在取出的时候,需要进行强制类型转换。 任何对象没有使用泛型之前会自动转换Object类型,使用泛型之后不用强制转换。
集合框架的类和接口均在java.util包中。
枚举Enumeration接口
枚举(Enumeration)接口本身不属于数据结构,但它在其他数据结构的范畴里应用很广。 枚举(The Enumeration)接口定义一种从数据结构中取回连续元素的方式。
Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。
使用在诸如Vector和Properties这些传统类、API类、应用程序所定义的方法中。
Enumeration声明的方法:
序号 | 方法描述 |
---|---|
1 | boolen hasMoreElements()检测此枚举是否包含更多元素 |
2 | Object nextElement()若此枚举对象至少含有一个可提供的元素,则返回此枚举的下一个元素 |
以下实例演示了Enumeration的使用:
实例
import java.util.Vector;
import java.util.Enumeration;
public class EnumerationTester {
public static void main(String args[]) {
Enumeration<String> days; //枚举
Vector<String> dayNames = new Vector<String>();
dayNames.add("Sunday");
dayNames.add("Monday");
dayNames.add("Tuesday");
dayNames.add("Wednesday");
dayNames.add("Thursday");
dayNames.add("Friday");
dayNames.add("Saturday");
days = dayNames.elements(); //从vector中枚举元素
while (days.hasMoreElements()){
System.out.println(days.nextElement());
}
}
}
位集合Bitset类
一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。
该类在处理一组布尔值的时候非常有用,你只需要给每个值赋值一"位",然后对位进行适当的设置或清除,就可以对布尔值进行操作了。
BitSet定义了两个构造方法。
1、创建一个默认的对象:
BitSet()
2、允许用户指定初始大小。所有位初始化为0。
BitSet(int size)
注:数组元素并不是只有0和1。
向量Vector类
向量(Vector)类和数组一样,Vector对象的元素可通过索引访问。
Vector 类实现了一个动态数组。和 ArrayList 很相似,但是两者是不同的:
1、Vector 是同步访问的。
2、Vector 包含了许多传统的方法,这些方法不属于集合框架。
构造方法
1、创建一个默认的向量,默认大小为 10:
Vector()
2、创建指定大小的向量。
Vector(int size)
3、创建指定大小的向量,并且增量用 incr 指定。增量表示向量每次增加的元素数目。
Vector(int size,int incr)
4、创建一个包含集合 c 元素的向量:
Vector(Collection c)
定义方法:
方法 | 描述 |
---|---|
void add(int index, Object element) | 在此向量的指定位置插入指定的元素 |
boolean add(Object o) | 将指定元素添加到此向量的末尾。 |
boolean addAll(Collection c) | 将指定 Collection 中的所有元素添加到此向量的末尾,按照指定 collection 的迭代器所返回的顺序添加这些元素。 |
boolean addAll(int index, Collection c) | 在指定位置将指定 Collection 中的所有元素插入到此向量中 |
void addElement(Object obj) | 将指定的组件添加到此向量的末尾 |
int capacity() | 返回此向量的当前容量 |
void clear() | 从此向量中移除所有元素 |
Object clone() | 返回向量的一个副本 |
boolean contains(Object elem) | 如果此向量包含指定的元素,则返回 true |
int size() | 返回此向量中的组件数) |
等等
注:初始化后,若数组元素未赋值,此时int capacity()为指定大小或默认大小,int size()为0.
import java.util.*;
public class VectorDemo {
public static void main(String args[]) {
// initial size is 3, increment is 2
Vector v = new Vector(3, 2);
System.out.println("Initial size: " + v.size());
System.out.println("Initial capacity: " +
v.capacity());
v.addElement(new Integer(1));
v.addElement(new Integer(2));
v.addElement(new Integer(3));
v.addElement(new Integer(4));
System.out.println("Capacity after four additions: " +
v.capacity());
v.addElement(new Double(5.45));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Double(6.08));
v.addElement(new Integer(7));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Float(9.4));
v.addElement(new Integer(10));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Integer(11));
v.addElement(new Integer(12));
System.out.println("First element: " +
(Integer)v.firstElement());
System.out.println("Last element: " +
(Integer)v.lastElement());
if(v.contains(new Integer(3)))
System.out.println("Vector contains 3.");
// enumerate the elements in the vector.
Enumeration vEnum = v.elements();
System.out.println("\nElements in vector:");
while(vEnum.hasMoreElements())
System.out.print(vEnum.nextElement() + " ");
System.out.println();
}
}
注:当执行v.addElement()时,若元素还未赋值,则将值赋给此元素,并不会添加新的元素,即v.capacity()不会发生变化。
栈Stack类
栈(Stack)实现了一个先进后出,后进先出(LIFO)的数据结构。
栈是Vector的一个子类,堆栈只定义了默认构造函数,用来创建一个空栈。
Stack()
堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。
方法 | 描述 |
---|---|
boolean empty() | 测试堆栈是否为空 |
Object peek( ) | 查看堆栈顶部的对象,但不移除 |
Object pop( ) | 移除堆栈顶部的对象,并返回 |
Object push(Object element) | 把项压入堆栈顶部 |
int search(Object element) | 返回对象在堆栈中的位置,以 1 为基数 |
字典(Dictionary)类
字典(Dictionary) 类是一个抽象类,用来存储键/值对,没有提供特定的实现。
Dictionary定义的抽象方法如下表所示:
方法 | 描述 |
---|---|
Enumeration elements( ) | 返回此 dictionary 中值的枚举 |
Object get(Object key) | 返回此 dictionary 中该键所映射到的值 |
boolean isEmpty( ) | 测试此 dictionary 是否不存在从键到值的映射 |
Enumeration keys( ) | 返回此 dictionary 中键的枚举 |
Object put(Object key, Object value) | 将指定 key 映射到此 dictionary 中指定 value |
Object remove(Object key) | 从此 dictionary 中移除 key (及其相应的 value) |
int size( ) | 返回此 dictionary 中条目(不同键)的数量 |
Dictionary类已经过时了。在实际开发中,你可以实现Map接口来获取键/值的存储功能。
Map接口
Map接口中键和值一一映射. 可以通过键来获取值。
1、给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
2、当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
3、当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
4、当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
5、当尝试修改一个只读的Map时,会抛出一个UnsupportedOperationException异常。
方法 | 描述 |
---|---|
void clear( ) | 返回此 dictionary 中值的枚举 |
boolean containsKey(Object k) | 如果此映射包含指定键的映射关系,则返回 true |
boolean containsValue(Object v) | 如果此映射将一个或多个键映射到指定值,则返回 true |
Set entrySet( ) | 返回此映射中包含的映射关系的 Set 视图 |
boolean equals(Object obj) | 比较指定的对象与此映射是否相等 |
Object get(Object k) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null |
int hashCode( ) | 返回此映射的哈希码值 |
boolean isEmpty( ) | 如果此映射未包含键-值映射关系,则返回 true |
Set keySet( ) | 返回此映射中包含的键的 Set 视图 |
Object put(Object k, Object v) | 将指定的值与此映射中的指定键关联(可选操作) |
void putAll(Map m) | 从指定映射中将所有映射关系复制到此映射中(可选操作) |
Object remove(Object k) | 如果存在一个键的映射关系,则将其从此映射中移除(可选操作) |
int size( ) | 返回此映射中的键-值映射关系数 |
Collection values( ) | 返回此映射中包含的值的 Collection 视图 |
map.entrySet()
和 keySet()
比较:
map.entrySet() | keySet() | values() | |
---|---|---|---|
遍历hashMap | 将 key - value 全部取出来,性能较差 | 根据取出的 key 值去查询对应的 value 值,若key值简单,则效率高;随着key复杂度高,entrySet更有优势 | 只遍历 value |
遍历TreeMap | 遍历 key-value 时候务必使用 entrySet(),性能更高 | 只遍历 key 的时候使用 keySet(), 在只遍历 value 的是使用 values() 方法 | 只遍历 value |
哈希表(Hashtable)类
Hashtable类提供了一种在用户定义键结构的基础上来组织数据的手段。
HashMap | Hashtable |
---|---|
不支持同步 | 支持同步 |
在哈希表中存储键/值对 | 在哈希表中存储键/值对 |
Hashtable实现了Map接口,因此,Hashtable现在集成到了集合框架中。
当使用一个哈希表,要指定用作键的对象,以及要链接到该键的值。
然后,该键经过哈希处理后所得到的散列码被用作存储在该表中值的索引。
构造方法
1、默认构造方法:Hashtable()
2、创建指定大小的哈希表:Hashtable(int size)
3、创建一个指定大小的哈希表,并且通过fillRatio指定填充比例。
填充比例必须介于0.0和1.0之间,它决定了哈希表在重新调整大小之前的充满程度:Hashtable(int size,float fillRatio)
4、创建一个以M中元素为初始化元素的哈希表。哈希表的容量被设置为M的两倍。Hashtable(Map m)
属性(Properties)类
Properties 继承于 Hashtable 类,表示一个持久的属性集。属性列表中每个键及其对应值都是一个字符串。
Properties 类被许多Java类使用。例如,在获取环境变量时它就作为System.getProperties()方法的返回值。
构造方法
1、没有默认值:Properties()
2、使用propDefault 作为默认值:Properties(Properties propDefault)
两种情况下,属性列表都为空
定义方法:
方法 | 描述 |
---|---|
String getProperty(String key[, String defaultProperty]) | 用指定的键在此属性列表中搜索属性 |
void list(PrintStream streamOut) | 将属性列表输出到指定的输出流 |
void load(InputStream streamIn) throws IOException | 从输入流中读取属性列表(键和元素对) |
Enumeration propertyNames( ) | 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对) |
Object setProperty(String key, String value) | 调用 Hashtable 的方法 put |
void store(OutputStream streamOut, String description) | 以适合使用 load(InputStream)方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流 |
实例
import java.util.*;
public class PropDemo {
public static void main(String args[]) {
Properties capitals = new Properties();
Set states;
String str;
capitals.put("Illinois", "Springfield");
capitals.put("Missouri", "Jefferson City");
capitals.put("Washington", "Olympia");
capitals.put("California", "Sacramento");
capitals.put("Indiana", "Indianapolis");
// Show all states and capitals in hashtable.
states = capitals.keySet(); //得到键的set视图
Iterator itr = states.iterator(); // Iterator(迭代器)是一个接口,作用是遍历容器的所有元素
while(itr.hasNext()) {
str = (String) itr.next();
System.out.println("The capital of " +
str + " is " + capitals.getProperty(str) + ".");
}
System.out.println();
// look for state not in list -- specify default
str = capitals.getProperty("Florida", "Not Found");
System.out.println("The capital of Florida is "
+ str + ".");
}
}
集合框架
设计要满足以下几个目标:
1、该框架必须是高性能的。
2、该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
3、对一个集合的扩展和适应必须是简单的。
为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,也可以通过这些接口实现自己的集合。
Java 集合框架主要包括两种类型的容器,一种是集合(Collection)存储一个元素集合,另一种是图(Map)存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
所有的集合框架都包含如下内容:
1、集合接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象。
接口 | 描述 |
---|---|
Collection 接口 | 最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。 |
List 接口 | 使用此接口能够精确的控制每个元素插入的位置,能够通过索引来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组不唯一,有序(插入顺序)的对象。 |
Set | 具有与 Collection 完全一样的接口, 不保存重复的元素。Set 接口存储一组唯一,无序的对象。 |
SortedSet | 继承于Set,存储一组唯一,有序的集合 |
Map | 存储一组键值对象,提供key(键)到value(值)的映射。 |
Map.Entry | 描述在一个Map中的一个元素(键/值对)。是一个Map的内部类。 |
SortedMap | 继承于 Map,使 Key 保持在升序排列。 |
Enumeration | 枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。 |
Set和List的区别
Set | List |
---|---|
存储的是**无序的,不重复(唯一)**的数据 | 存储的是**有序的,可以重复(不唯一)**的元素 |
检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 | 检索效率高,删除和插入效率低,会引起其他元素位置改变 |
实现类有HashSet,TreeSet | 实现类有ArrayList,LinkedList,Vector |
2、集合实现(类):是集合接口的具体实现。其中一些是具体类,这些类可以直接拿来使用,而另外一些是抽象类,提供了接口的部分实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
标准集合类汇总:
类 | 描述 |
---|---|
AbstractCollection | 实现了大部分的集合接口 |
AbstractList | 继承于AbstractCollection ,实现了大部分List接口 |
AbstractSequentialList | 继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问 |
LinkedList | 实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,没有同步方法,如果多个线程同时访问一个List,必须自己实现访问同步,即在创建List时候构造一个同步的List, List list=Collections.synchronizedList(newLinkedList(...)); 查找效率低。 |
ArrayList | 实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时较快。非同步。插入删除效率低 |
AbstractSet | 继承于AbstractCollection 并且实现了大部分Set接口 |
HashSet | 实现了Set接口,不允许出现重复元素,即唯一,不保证集合中元素的顺序,允许最多包含一个值为null的元素 |
LinkedHashSet | 具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现 |
TreeSet | 该类实现了Set接口,可以实现排序等功能 |
AbstractMap | 实现了大部分的Map接口 |
HashMap | 散列表,它存储的内容是键值对(key-value)映射。实现了Map接口,根据键的HashCode值存储数据,访问速度快,最多允许一条记录的键为null,不支持线程同步 |
TreeMap | 继承了AbstractMap,并且使用一颗树 |
WeakHashMap | 继承AbstractMap类,使用弱密钥的哈希表 |
LinkedHashMap | 继承于HashMap,使用元素的自然顺序对元素进行排序 |
IdentityHashMap | 继承AbstractMap类,比较文档时使用引用相等 |
以及上述向量Vector向量、栈Stack、字典Dictionary、哈希表(Hashtable)、属性Properties、位向量BitSet。
ArrayList与Linkedlistbijiao比较:
ArrayList | LinkedList |
---|---|
数组 | 链表 |
遍历、查找元素快 | 遍历、查找元素慢 |
添加、删除元素比较慢 | 添加、删除元素比较快 |
3、算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
用于集合和映射。被定义为集合类的静态方法。
在尝试比较不兼容的类型时,一些方法能够抛出 ClassCastException异常。当试图修改一个不可修改的集合时,抛出UnsupportedOperationException异常。
集合定义三个静态的变量:EMPTY_SET,EMPTY_LIST,EMPTY_MAP的。这些变量都不可改变。
java集合框架位于java.util包中, 所以当使用集合框架的时候需要进行导包。
迭代器Iterator
采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或ListIterator接口。
迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了Iterator,以允许双向遍历列表和修改元素。
(1) 使用方法 iterator() 要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 继承。
(2) 使用 next() 获得序列中的下一个元素。
(3) 使用 hasNext() 检查序列中是否还有元素。
(4) 使用 remove() 将迭代器新返回的元素删除。
实例 遍历List:
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");
//第一种遍历方法使用 For-Each 遍历 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++) //这里也可以改写为 for(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");
//第一种:普遍使用,通过map.keyset,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
//第二种:通过Map.entrySet使用iterator遍历key和value
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());
}
//第三种:推荐,尤其是容量大时,通过Map.entrySet遍历key和value
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);
}
}
}
比较器 Comparator
TreeSet和TreeMap的按照排序顺序来存储元素. 然而,这是通过比较器来精确定义按照什么样的排序顺序。
这个接口可以让我们以不同的方式来排序一个集合。
Java 的快速失败和安全失败
一、快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出 Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:异常的抛出条件是检测到 modCount != expectedmodCount 。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。
场景:java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
总结:在单线程的遍历过程中,如果要进行 remove 操作,可以调用迭代器的 remove 方法而不是集合类的 remove 方法。
二、安全失败(fail—safe)
1、采用安全失败机制的集合容器,使用 CopyOnWriterArrayList 代替 ArrayList,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,待完成后,才将指向旧数组的引用指向新数组,
缺点:迭代器并不能访问到修改后的内容,只能保证数据的最终一致性,不能保证数据的实时一致性
2、使用ConcurrentHashMap代替HashMap,采用了锁机制,是线程安全的。当 iterator 被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,在改变时new新的数据从而不影响原有的数据 ,iterator 完成后再将头指针替换为新的数据 ,这样 iterator 线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生 fail-fast,但不保证获取的是最新的数据。
场景:Java并发包java.util.concurrent 下的容器都是安全失败,可以在多线程下并发使用,并发修改。