**java 中几种常用数据结构**
一、数据结构起源
1968年,美国 Donald E. Knuth 教授在《计算机程序设计艺术》第一卷《基本算法》中系统阐述了数据的逻辑结构和存储结构及其操作,开创了数据结构课程体系。
70年代初,大型程序相继出现,软件也开始相对独立,结构程序设计成为程序设计方法学主要内容,人们开始认为程序设计的实质是对确定的问题选择一种好的结构,加上设计一种好的算法。也就是 程序设计 = 数据结构 + 算法(尼古拉斯·沃斯(Niklaus Wirth) 提出)
现实生活中,更多的不是解决一些数值计算问题,我们需要通过表、树、图等数据结构的帮助来更好地处理问题。
所以数据结构也是一门研究非数字计算的程序设计问题中的操作对象,以及他们之间的关系和操作等相关问题的学科。(引用自《大话数据结构》)
二、基本概念和术语
1、数据
数据指的是能输入到计算机中,并能被计算机程序处理的对象。
对于数值类型(整型、实型等),可以进行数值计算;
对于字符数据类型(声音、图像、视频等可通过编码转化为字符数据),可以进行非数值处理。
2、数据元素
数据元素指组成数据的、有意义的基本单位,也被称为记录。
如:一部电影里面的女主就是数据元素
3、数据项
数据项是数据不可分割的最小单位,一个数据元素可以由若干数据项组成。
如:一部电影里面女主的姓名、性别等都是数据项,恩,女主性别一般是女…
4、数据对象
数据对象指性质相同的数据元素的集合,是数据的子集;
数据对象简称数据
什么是性质相同呢?
性质相同指数据元素具有相同数量和类型的数据项;
如一部电影中每个角色(数据元素)都有姓名、性别(数据项),这些角色(数据元素)构成了一部电 影,那么这部电影所有人物的集合我们可以想象成是一个数据对象。
5、数据结构
数据结构指互相之间存在一种或多种特定关系的数据元素的集合;
数据结构 = 数据元素 + 关系;
还得用上面的例子,一部电影可以想象成是一个数据结构,是由一个个人物(数据元素)和一个个串联的人物情节 (关系)构成,例子有点勉强,大概意思都应该理解的.
java中有几种常用的数据结构,主要分为Collection和map两个主要接口(接口只提供方法,并不提供实现),而程序中最终使用的数据结构是继承自这些接口的数据结构类。其主要的关系(继承关系)有: (—-详细参见java api文档!)
Collection和Map的区别在于容器中每个位置保存的元素个数:
Java容器类的用途是“保存对象”,分为两类:Map——存储“键值对”组成的对象;Collection——存储独立元素。Collection又可以分为List和Set两大块。List保持元素的顺序,而Set不能有重复的元素。
1) Collection 每个位置只能保存一个元素(对象)
2) Map保存的是”键值对”,就像一个小型数据库。我们可以通过”键”找到该键对应的”值”
Collection—->Collections
Map—–>SortedMap——>TreeMap
Collection—->List—–>(Vector \ ArryList \ LinkedList) Map——>HashMap
Collection—->Set——>(HashSet \ LinkedHashSet \ SortedSet)
图片来源:http://blog.csdn.net/u010947402/article/details/51878166
Set
|——SortedSet接口——TreeSet实现类
Set接口——|——HashSet实现类
|——LinkedHashSet实现类
HashSet
HashSet有以下特点
不能保证元素的排列顺序,顺序有可能发生变化
不是同步的
集合元素可以是null,但只能放入一个null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其 hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
TreeSet
TreeSet类型是J2SE中唯一可实现自动排序的类型
TreeSet是SortedSet接口的唯一实现类,SortedSet接口主要用于排序操作,即实现此接口的子类都属于排序的子类。TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向 TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(To1,To2)方法
LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,
我来解释一下吧。首先,想要明白hashCode的作用,你必须要先知道Java中的集合。
Set总结:
(1)Set实现的基础是Map(HashMap)(2)Set中的元素是不能重复的,如果使用add(Object obj)方法添加已经存在的对象,则会覆盖前面的对象
java的HashCode方法
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。
你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。 也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。 哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。
初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。 这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。 所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同
上面说的对象相同指的是用eqauls方法比较。你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。
hashcode这个方法是用来鉴定2个对象是否相等的。 那你会说,不是还有equals这个方法吗? 不错,这2个方法都是用来判断2个对象是否相等的。但是他们是有区别的。 一般来讲,equals这个方法是给用户调用的,如果你想判断2个对象是否相等,你可以重写equals方法,然后在代码中调用,就可以判断他们是否相等 了。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。
举个例子,有个学生类,属性只有姓名和性别,那么我们可以 认为只要姓名和性别相等,那么就说这2个对象是相等的。
hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode 这个方法,而且也用到了equals方法。这里不可以重复是说equals和hashcode只要有一个不等就可以了!所以简单来讲,hashcode相 当于是一个对象的编码,就好像文件中的md5,他和equals不同就在于他返回的是int型的,比较起来不直观。我们一般在覆盖equals的同时也要 覆盖hashcode,让他们的逻辑一致。
举个例子,还是刚刚的例子,如果姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名 的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。 要从物理上判断2个对象是否相等,用==就可以了。
Set相关实现类的代码如下:
public class TestSet {
public static void main(String[] args) {
// Set<String> set = new HashSet<String>();
//
// insert(set);
// System.out.print(set);
// /**
// * 遍历方法一,迭代遍历
// */
// System.out.println();
// for(Iterator<String> iterator = set.iterator();iterator.hasNext();){
// System.out.print(iterator.next()+"---");
// }
//
//
// System.out.println();
// System.out.println("********************");
// /**
// * for增强循环遍历
// */
// for(String value : set){
// System.out.print(value+"---");
// }
System.out.println();
System.out.println("************");
testTreeSet();
System.out.println();
System.out.println("************");
testHashSet();
System.out.println();
System.out.println("************");
testLinkedHashSet();
}
/**
* Created at 2017/10/12 by xxx
* description 添加元素
*/
public static void insert(Set set){
set.add("18");
set.add("23");
set.add("14");
set.add("34");
set.add("53");
set.add("44");
set.add("34");//在Set中的对象元素不能重复
set.add("13");
// set.add(null);
}
public static void testTreeSet(){
Set<String> treeSet = new TreeSet<String>();
insert(treeSet);
System.out.println("treeSet添加元素后顺序输出:"+treeSet);
}
public static void testHashSet(){
Set<String> hashSet=new HashSet<String>();
insert(hashSet);
System.out.println("hashSet添加元素后的无序输出:"+hashSet);
}
public static void testLinkedHashSet(){
Set<String> linkedSet = new LinkedHashSet<String>();
linkedSet.add("First");
linkedSet.add("Second");
linkedSet.add("Thrid");
linkedSet.add("Fourth");
System.out.println("LinkedHashSet:"+linkedSet);//按照数据插入的顺序输出
Set<String> hashSet = new HashSet<String>();
hashSet.add("First");
hashSet.add("Second");
hashSet.add("Thrid");
hashSet.add("Fourth");
System.out.println("HashSet:"+hashSet);
}
}
————–Collection—————-
1、Collections
2、List
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下 >标)来访问List中的元素,这类似于Java的数组。
3、Vector
基于数组(Array)的List,其实就是封装了数组所不具备的一些功能方便我们使用,所以它难易避免数组的限制,同时性能也不可能超越数组。所以,在可能的情况下,我们要多运用数组。另外很重要的一点就是Vector是线程同步的(sychronized)的,这也是Vector和ArrayList 的一个的重要区别。
4、ArrayList
同Vector一样是一个基于数组上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector好一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。
5、LinkedList
LinkedList不同于前面两种List,它不是基于数组的,所以不受数组性能的限制。
它每一个节点(Node)都包含两方面的内容:
1.节点本身的数据(data);
2.下一个节点的信息(nextNode)。
所以当对LinkedList做添加,删除动作的时候就不用像基于数组的ArrayList一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了,这是LinkedList的优势。
List总结:
所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ]
所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]
所有的List中可以有null元素,例如[ tom,null,1 ]
基于Array的List(Vector,ArrayList)适合查询,而LinkedList 适合添加,删除操作
————说明———–
一、几个常用类的区别
1.ArrayList: 元素单个,效率高,多用于查询
2.Vector: 元素单个,线程安全,多用于查询
3.LinkedList:元素单个,多用于插入和删除
4.HashMap: 元素成对,元素可为空
5.HashTable: 元素成对,线程安全,元素不可为空
二、Vector、ArrayList和LinkedList
大多数情况下,从性能上来说ArrayList最好,但是当集合内的元素需要频繁插入、删除时LinkedList会有比较好的表现,但是它们三个性能都比不上数组,另外Vector是线程同步的。所以:
如果能用数组的时候(元素类型固定,数组长度固定),请尽量使用数组来代替List;
如果没有频繁的删除插入操作,又不用考虑多线程问题,优先选择ArrayList;
如果在多线程条件下使用,可以考虑Vector;
如果需要频繁地删除插入,LinkedList就有了用武之地;
三、Collections的相关算法
binarySearch:折半查找。
sort:排序,这里是一种类似于快速排序的方法,效率仍然是O(n * log n),但却是一种稳定的排序方法。
reverse:将线性表进行逆序操作
rotate:以某个元素为轴心将线性表“旋转”。
swap:交换一个线性表中两个元素的位置。
……
List的相关实现类代码如下:
public class TestList {
/**
* 初始化一个List
* @param list
*/
public static void init(List list){
if(list != null){
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
}
}
/**
* 输出List的内容
* @param list
*/
public static void output(List list){
if (list != null){
//根据列表下标遍历,使用list.size()获取列表中元素的个数
for (int i=0; i<list.size(); i++){
System.out.print(list.get(i) + " ");
}
//或者用迭代器遍历
Iterator it = list.iterator();
Object value = null;
while (it.hasNext()){
value = it.next();
//System.out.println(value);
}
}
System.out.println();
}
/**
* 使用ArrayList
*/
public static void testArrayList(){
List list = new ArrayList();
init(list);
System.out.println("使用ArrayList: ");
output(list);
}
/**
* 使用Vector
*/
public static void testVector(){
List list = new Vector();
init(list);
System.out.println("使用Vector: ");
output(list);
}
/**
* 使用LinkedList
*/
public static void testLinkedList(){
List list = new LinkedList();
init(list);
System.out.println("使用LinkedList: ");
output(list);
}
public static void main(String[] args) {
TestList.testArrayList();
TestList.testVector();
TestList.testLinkedList();
List list = new ArrayList();
System.out.println(list == null);
init(list);
//List支持元素重复
list.add("aaa");
list.add("bbb");
System.out.println("插入元素aaa, bbb后:");
output(list);
//指定元素插入的位置
list.add(1, "fff");
System.out.println("在下标为1处插入fff后:");
output(list);
List list2 = new ArrayList();
list2.add("ggg");
list2.add("hhh");
//将另一列表中的元素插入到列表中
list.addAll(list2);
System.out.println("添加list2的元素后:");
output(list);
//判断列表是否包含某一元素
//通过元素的equals方法,判断元素是否相等
System.out.println("list包含aaa? " + list.contains("aaa"));
//判断列表中是否包含了另外一个列表中的所有元素。
System.out.println("list包含list2中的所有元素? " + list.containsAll(list2));
//定位一个元素在列表中最先出现的位置
System.out.println("aaa在list中第一次出现的位置: " + list.indexOf("aaa"));
//定位一个元素在列表中最后出现的位置
System.out.println("aaa在list中最后一次出现的位置: " + list.lastIndexOf("aaa"));
//更新列表中某个位置的元素值
list.set(2, "xxx");
System.out.println("更新位置为2的元素为xxx后:");
output(list);
//删除列表中的某个元素,只删除第一次出现的那个
list.remove("aaa");
System.out.println("删除元素aaa后:");
output(list);
//删除列表中指定位置的元素
list.remove(1);
System.out.println("删除下标为1的元素后:");
output(list);
//删除列表除list2以外的其他元素
list.retainAll(list2);
System.out.println("删除除list2包含的以外的元素后:");
output(list);
//删除列表中在另一列表中也包含了的元素
list.removeAll(list2);
System.out.println("删除list2包含的元素后:");
output(list);
//清空列表
list.clear();
//判断列表中是否有数据
System.out.println("清空List后,list为空么? " + list.isEmpty());
init(list);
//用列表中的某些元素构造一个新的列表
list2 = list.subList(1,4);
System.out.println("用list的第1个到第4个元素构造一个新的List:");
output(list2);
//用List特有的遍历器ListIterator遍历列表
//与普通的Iterator不同,它允许两个方向遍历列表
ListIterator listIt = list.listIterator();
System.out.println("正向遍历列表");
while (listIt.hasNext()){
System.out.print(listIt.next());
}
System.out.println();
System.out.println("反向遍历列表");
while (listIt.hasPrevious()){
System.out.print(listIt.previous()+"-");
}
System.out.println();
//也可以使用ListIterator从List中间插入和删除元素,
//只能在遍历器当前位置添加和删除。
listIt.add("newadd");
System.out.println("用ListIterator往列表中添加元素newadd后: ");
output(list);
listIt.next();
listIt.remove();//todo:删除多个
System.out.println("用ListIterator删除列表中元素后: ");
output(list);
listIt.next();//下标下移一位
listIt.remove();
System.out.println("用ListIterator删除列表中元素后: ");
output(list);
//LinkedList自定义的方法
LinkedList linklist = new LinkedList();
init(linklist);
//添加元素到列表头
linklist.addFirst("fff");
System.out.println("把fff放到列表头后:");
output(linklist);
//添加元素到列表尾
linklist.addLast("eee");
System.out.println("把eee放到列表尾后:");
output(linklist);
//获取表头元素
System.out.println("列表头元素:" + linklist.getFirst());
//获取表尾元素
System.out.println("列表尾元素:" + linklist.getLast());
//删除列表头的元素
linklist.removeFirst();
System.out.println("删除列表头元素后:");
output(linklist);
//删除列表尾的元素
linklist.removeLast();
System.out.println("删除列表尾元素后:");
output(linklist);
List list3=new LinkedList<>();
list3.add("111");
list3.add("222");
linklist.addAll(4,list3);
System.out.println("在下标为4处添加list3后:");
output(linklist);
//堆栈Stack类,它继承自Stack类
Stack myStack = new Stack();
//插入元素,是插入到尾部
myStack.push("aaa");
myStack.push("bbb");
myStack.push("ccc");
myStack.push("ddd");
myStack.push("aaa");
myStack.push("ddd");
System.out.println("堆栈中的元素是: ");
output(myStack);
System.out.println("堆栈尾部元素: " + myStack.peek());
System.out.println("弹出堆栈尾部元素: " + myStack.pop());
output(myStack);
myStack.push("kkk");
output(myStack);
}
}
treeSet的倒叙代码如下(继承Comparator):
public class TreeSetTest2 {
public static void main(String[] args) {
Set<String> set = new TreeSet<String>(new PersonComparator());
set.add("a");
set.add("b");
set.add("c");
set.add("d");
set.add("e");
set.add("A");
// set.add("1");
// set.add("8");
// set.add("3");
// set.add("4");
// set.add("5");
// set.add("6");
// for(Iterator<Person> iterator = set.iterator();iterator.hasNext();){
// System.out.print(iterator.next().score+" ");
// }
System.out.println(set);
}
}
class PersonComparator implements Comparator<String>{
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);//降序排列
// return o1.compareTo(o2);//升序排列
}
}
Map
HashMap和Hashtable的区别
2、TreeMap
TreeMap则是对键按序存放,因此它便有一些扩展的方法,比如firstKey(),lastKey()等,你还可以从TreeMap中指定一个范围以取得其子Map。
键和值的关联很简单,用put(Object key,Object value)方法即可将一个键与一个值对象相关联。用get(Object key)可得到与此key对象所对应的值对象。
1 HashMap不是线程安全的
HashMap是map接口的子类,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value,而hashtable不允许。
2 HashTable是线程安全。
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。
HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差。
总结:
Map的相关实现类的代码如下:
public class TestMap {
public static void main(String args[]) {
testTreeMap();
testHashMap();
testHashTable();//随机顺序
HashMap<String,Object> hashap =new HashMap<>();
hashap.put("as", new Integer(90));
hashap.put("yer", new Integer(30));
hashap.put("tt", new Integer(10));
hashap.put("we", new Integer(20));
hashap.put("ee", new Integer(60));
System.out.println("---"+hashap);
}
public static void testHashTable(){
Map map =new Hashtable();
addElement(map);
System.out.println("HashTable输出:"+map);
}
public static void testTreeMap(){
Map map =new TreeMap();
addElement(map);
System.out.println("TreeMap输出:"+map);
}
public static void testHashMap(){
Map map =new HashMap();
addElement(map);
System.out.println("HashMap输出:"+map);
}
public static void addElement(Map map){
// map.put("1呀", new Integer(90));
// map.put("2呀", new Integer(30));
// map.put("4呀", new Integer(10));
// map.put("3呀", new Integer(20));
// map.put("5呀", new Integer(60));
// map.put("6呀", new Integer(80));
// map.put("7呀", new Integer(234));
// map.put("8呀", new Integer(14));
// map.put("9呀", new Integer(45));
// Map hashap =new HashMap();
// map.put(1, new Integer(90));
// map.put(3, new Integer(30));
// map.put(4, new Integer(10));
// map.put(5, new Integer(20));
// map.put(6, new Integer(60));
map.put("6呀", new Integer(80));
map.put("7呀", new Integer(234));
map.put("8呀", new Integer(14));
map.put("9呀", new Integer(45));
// map.put(null, null);
}
}
栈
在Java中Stack类表示后进先出(LIFO)的对象堆栈。栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的。每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出,如下:
图片来源:http://blog.csdn.net/guofengpu/article/details/52092333
队列
队列(Queue):也是运算受限的线性表。是一种先进先出(First In First Out ,简称FIFO)的线性表。只允许在表的一端front进行插入,而在另一端rear进行删除。
队首(front) :允许进行删除的一端称为队首。
队尾(rear) :允许进行插入的一端称为队尾。
例如:排队购物。操作系统中的作业排队。先进入队列的成员总是先离开队列。
队列中没有元素时称为空队列。在空队列中依次加入元素a1, a2, …, an之后,a1是队首元素,an是队尾元素。显然退出队列的次序也只能是a1, a2, …, an ,即队列的修改是依先进先出的原则进行的,如图所示。
图片来源:http://www.cnblogs.com/skywang12345/p/3562279.html
参考java常用数据结构来源:http://blog.csdn.net/u010947402/article/details/51878166
set不同实现类的区别:http://www.cnblogs.com/wl0000-03/p/6019627.html
map的不同实现类参考来源:http://www.cnblogs.com/langtianya/archive/2013/03/19/2970273.html
数据结构概念出处链接:http://www.jianshu.com/p/75425f405c25