以下是我学习java集合框架的笔记,内容主要是个人理解和网络视频、文章的摘录。
首先说一下什么是java集合框架,相信初学者刚开始听这个的时候,对于这个东西的概念是很模糊的。下面我用通俗一点的语言来描述它的由来以及意思。相信大家都知道数组,这是一个可以对数进行存储编辑的结构,但是在面向对象编程里面,我们不仅有数字,还有对象,简单的数组无法满足对对象的存储和编辑,因此,java集合框架就诞生了。每一种集合都是以一种不同的形式来对对象进行存储。本文主要讲的是比较基础的set、list、map。
这是一个来自网上的框架图,方便大家理解和记忆,具体内容在下面有解释。
本文内容整体框架如下:
一、Collection接口
1、List接口:
(1)ArrayList具体类
(2)LinkedList具体类
2、Set接口:
(1)HashSet具体类
(2)LinkedHashSet具体类
(3)TreeSet具体类
二、Map接口
(1)HashMap类
(2)LinkedHashMap类
(3)TreeMap类
一、Collection接口
Collection是集合框架层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。Collection接口下有最常用的接口为List跟Set。需要注意的是,Map并没有实现Collection接口。
1、List接口
(1)ArrayList
优点:类似数组的形式进行存储,因此它的随机访问速度极快。
缺点:不适合于在线性表中间需要频繁进行插入和删除操作。因为每次插入和删除都需要移动数组中的元素,它是用数组存储元素的,这个数组可以动态创建,如果元素个数超过了数组的容量,那么就创建一个更大的新数组,并将当前数组中的所有元素都复制到新数组中。
基本功能:
public static void main(String[] args) {
// TODO Auto-generated method stub
Collection coll = new ArrayList();
methodDemo(coll);
}
// 演示基础功能
public static void methodDemo(Collection coll) {
// 1、添加元素
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
System.out.println(coll);
// 2、删除元素
coll.remove("abc2");
System.out.println(coll);
// 3、清除
coll.clear();
System.out.println(coll);
// 4、包含
System.out.println("contains:" + coll.contains("abc1"));
}
结果如下:
[abc1, abc2, abc3]
[abc1, abc3]
[]
contains:false
下面展示几个带all的方法
// 演示带all的方法
public static void methodAllDemo() {
// 1、创建两个容器
Collection c1 = new ArrayList();
Collection c2 = new ArrayList();
// 2、添加元素
c1.add("abc1");
c1.add("abc2");
c1.add("abc3");
c1.add("abc4");
c2.add("abc2");
c2.add("abc3");
c2.add("abc5");
System.out.println("c1: "+c1);
System.out.println("c2: "+c2);
// 3、往c1中添加c2
c1.addAll(c2);
System.out.println("c1: "+c1);
// 4、判断c1中是否包含c2的所有元素
boolean b=c1.containsAll(c2);
System.out.println("b="+b);
// 5、从c1中删除c2
//c1.removeAll(c2);//将c1中和c2相同的元素从c1中删除
c1.retainAll(c2);// 将c1中和c2不相同的元素从c1中删除
System.out.println("c1: "+c1);
}
结果如下:
c1: [abc1, abc2, abc3, abc4]
c2: [abc2, abc3, abc5]
c1: [abc1, abc2, abc3, abc4, abc2, abc3, abc5]
b=true
c1: [abc2, abc3, abc2, abc3, abc5]
下面算是进入正题了,开篇我们说过集合框架多应用于存取对象,下面我们将演示如何对对象进行操作
首先这是例子对象Person的定义,后面的例子也会用到这个对象
public class Person {
private String name;
private int age;
public Person() {
super();
// TODO Auto-generated constructor stub
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public final String getName() {
return name;
}
public final void setName(String name) {
this.name = name;
}
public final int getAge() {
return age;
}
public final void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
结果如下:Person [name=list1, age=21]Person [name=list2, age=22]Person [name=list3, age=23]
下面就是ArrayList的具体操作
public static void main(String[] args) {//1、创建ArrayList集合对象List list=new ArrayList();//2、添加Person类型的对象Person p1=new Person("list1",21);Person p2=new Person("list2",22);list.add(p1);list.add(p2);list.add(new Person("list3",23));//3、取出元素for(Iterator it=list.iterator();it.hasNext();){Person p=(Person)it.next();System.out.println(p);}}
(2)LinkedList
这也是数组结构,也是长度可变的。线程不同步,替代了Vector,增删速度不快,查询速度很快
优点:适合于在链表中间需要频繁进行插入和删除操作。
缺点: 随机访问速度较慢。查找一个元素需要从头开始一个一个的找。此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作LinkedList是在一个链表中存储元素。
首先还是先演示一下基础操作
public static void main(String[] args) {
// TODO Auto-generated method stub
//1、创建一个链表对象
LinkedList link=new LinkedList();
//2、添加方法
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
System.out.println(link);
//3、取出元素
while(!link.isEmpty()){
System.out.println(link.removeFirst());
}
//4、此时link中没有元素了
System.out.println(link);
}
结果如下:[abc3, abc2, abc1]abc3abc2abc1[]
下面我们用LinkedList来模仿堆栈和队列。
堆栈:先进先出。First In Last Out——FILO
队列:先进先出。First In First Out——FIFOpublic class LinkedListTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyQueue queue=new MyQueue();
queue.myAdd("abc1");
queue.myAdd("abc2");
queue.myAdd("abc3");
queue.myAdd("abc4");
while(!queue.isNull()){
System.out.println(queue.myGetFIFO());//——先进先出
//System.out.println(queue.myGetFILO());——先进后出
}
}
}
class MyQueue{
private LinkedList link;
MyQueue(){
link = new LinkedList();
}
public void myAdd(Object obj){
link.addFirst(obj);
}
//队列:先进先出,
public Object myGetFIFO(){
return link.removeLast();
}
//堆栈:先进后出,
public Object myGetFILO(){
return link.removeFirst();
}
public boolean isNull(){
return link.isEmpty();
}
}
结果为:FIFO: FILO:abc1 abc4abc2 abc3abc3 abc2abc4 abc1
2、Set接口:
set不允许重复元素,和Collection的方法相同。set集合取出方法只有一个:迭代器
(1)HashSet
HashSet是Set接口最常见的实现类,其底层是基于hash算法进行存储相关元素的。HashSet中存储元素的位置是固定的(由hashCode决定),并且是无序的。Set集合中的去重和hashCode与equals方法相关。
首先还是看一下基本操作
public static void main(String[] args) {
//1、创建一个Set容器对象
HashSet set=new HashSet();
//2、添加元素
set.add("haha");
set.add("abc");
set.add("nba");
set.add("heihei");
set.add("abc");
//3、只能用迭代器取出
for(Iterator it=set.iterator();it.hasNext();){
System.out.println(it.next());
}
}
结果如下:hahaabcheiheinba
对于这个例子,添加元素的时候,换几个顺序进行尝试,你会发现结果都一样,虽然无序,但每次都是这个顺序。这是为什么呢,这就关系到HashSet的数据结构:哈希结构,详细大家可以另外进行深度学习,不过简单来说,就是每一个元素添加进去都会自动有一个哈希值,这个哈希值将决定他的顺序,所以同样一个元素,无论你是先添加它还是后添加它,它的哈希值都是一样的,所以它的排序始终不变。如果想要对哈希值进行新的定义和修改,需要怎么做呢?我们可以在对象Person中重写相关方法,具体如下,大家想看整体可以倒回去看上面Person的整个类
@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Person other = (Person) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}
通过hashCode()方法对哈希值进行重新定义,当哈希算法算出的两个元素的值相同时,称为哈希冲突。冲突后,需要对元素进行进一步的判断。判断的是元素的内容,equals()。如果一样,那么根据set性质不会进行储存。如果不一样,则会自动有其他方法进行新的计算。equals()方法可以帮助你解决哈希值的冲突。(2)LinkedHashSet
LinkedHashSet继承HashSet,是用一个链表实现来扩展HashSet类,它支持对规则集内的元素排序。HashSet中的元素是没有被排序的,而LinkedHashSet中的元素可以按照它们插入规则集的顺序提取。
同样,举个例子public static void main(String[] args) {
//1、创建一个Set容器对象
Set set=new LinkedHashSet();
//2、添加元素
set.add("haha");
set.add("abc");
set.add("nba");
set.add("heihei");
set.add("abc");
//3、只能用迭代器取出
for(Iterator it=set.iterator();it.hasNext();){
System.out.println(it.next());
}
}
结果如下;hahaabcnbaheihei
TreeSet实现了Set接口,它与HashSet的区别主要在于TreeSet中的元素会按照相关的值进行排序。HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key。由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较.当然也是用Comparator定位的.如果创建时没有确定,那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口.TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了.
注意:TreeSet是根据对象的CompareTo方法来去重的,如果CompaerTo返回0说明两个对象相等,不能同时存在于TreeSet中。
这里直接演示带CompareTo的例子public static void main(String[] args) {
Set set=new TreeSet(new ComparatorByName());
set.add(new Student(20,"xiaoqiao"));
set.add(new Student(27,"wagncai"));
set.add(new Student(24,"daniu"));
set.add(new Student(19,"dahuang"));
for(Iterator it=set.iterator();it.hasNext();){
Student stu=(Student)it.next();
System.out.println(stu.getName()+"::"+stu.getAge());
}
}
public class ComparatorByName implements Comparator {@Overridepublic int compare(Object o1, Object o2) {// TODO Auto-generated method stubStudent s1=(Student)o1;Student s2=(Student)o2;int temp=s1.getName().compareTo(s2.getName());return temp==0?s1.getAge()-s2.getAge():temp;}}
结果如下:dahuang::19daniu::24wagncai::27xiaoqiao::20
二、Map接口:
Map接口储存一组成对的键-值对象,提供key(键)到value(值)的映射,Map中的key不要求有序,不允许重复。value同样不要求有序,但可以重复。
(1)HashMap类:
最常见的Map实现类,他的储存方式是哈希表,优点是查询指定元素效率高。HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当链表中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
无论什么情况HashMap中哈希表的容量总是2的n次方的一个数。并且有这样一个公式:当length=2^n时,hashcode & (length-1) == hashcode % length
我们还是继续看一下基本操作
public static void main(String[] args) {
Map<Integer,String> map=new HashMap<Integer,String>();
methodDemo(map);
}
public static void methodDemo(Map<Integer,String> map){
//1、存储键值对
map.put(3,"xiaoqiang");
map.put(7,"wangcai");
map.put(2,"daniu");
System.out.println(map.get(7));
System.out.println(map);
}
结果如下:erhu{2=daniu, 3=xiaoqiang, 7=erhu}
下面这2个操作可以取出Map中的全部元素
Set<Map.Entry<String, String>> entrySet=map.entrySet();
for(Iterator<Map.Entry<String, String>> it=entrySet.iterator();it.hasNext();){
Map.Entry<String, String> me=it.next();
String key=me.getKey();
String value=me.getValue();
System.out.println(key+" "+value);
}
Set<String> keySet=map.keySet();for(Iterator<String> it=keySet.iterator();it.hasNext();){String key=it.next();String value=map.get(key);System.out.println(key+" "+value);}
HashMap与Hashtable的区别:
Hashtable实现Map接口,继承自古老的Dictionary类,实现一个key-value的键值映射表。任何非空的(key-value)均可以放入其中。 区别主要有三点:
1.Hashtable是基于陈旧的Dictionary实现的,而HashMap是基于Java1.2引进的Map接口实现的;
2.Hashtable是线程安全的,而HashMap是非线程安全的,我们可以使用外部同步的方法解决这个问题。
3.HashMap可以允许你在列表中放一个key值为null的元素,并且可以有任意多value为null,而Hashtable不允许键或者值为null。
(2)LinkedHashMap:
LinkedHashMap继承自HashMap,它主要是用链表实现来扩展HashMap类,HshMap中条目是没有顺序的,但是在LinkedHashMap中元素既可以按照它们插入图的顺序排序,也可以按它们最后一次被访问 的顺序排序。LinkedHashMap继承自HashMap并且实现了Map接口。和HashMap一样,LinkedHashMap 允许key和value均为null。于该数据结构和HashMap一样使用到hash算法,因此它不能保证映射的顺序,尤其是不能保证顺序持久不变(再哈希)。
(3)TreeMap: 二叉树,不同步
TreeMap基于红黑树数据结构的实现,键值可以使用Comparable或Comparator接口来排序。TreeMap继承自AbstractMap,同时实现了接口NavigableMap,而接口NavigableMap则继承自SortedMap。SortedMap是Map的子接口,使用它可以确保图中的条目是排好序的。 TreeMap默认是根据key升序排序,同TreeSet一样,TreeMap默认是升序的key-value的key是根据Comparable实现的方法CompareTo的返回值来去重的。
最后两种map不再进行展示,只需把上述例子的new语句中的HashMap改为相应的类型即可以观察到不同下面是三种接口的总结:
List接口总结:实际使用中我们需要根据特定的需求选用合适的类,如果 除了在末尾外不能在其他位置插入或者删除元素,那么ArrayList效率更高,如果需要经常插入或者删除元素,就选择LinkedList。
Set接口总结:区别于List接口在于,所有Set中的元素实现了不重复,有点像数学中集合的概念,无序,不允许有重复的元素,最多允许有一个null元素对象
Map接口总结:在实际使用中,如果更新图时不需要保持图中元素的顺序,就使用HashMap,如果需要保持图中元素的插入顺序或者访问顺序,就使用LinkedHashMap,如果需要使图按照键值排序,就使用TreeMap。
最后,总结了几种迭代的方法
public static void main(String[] args) {
//1、创建集合,添加元素
Collection coll=new ArrayList();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
System.out.println(coll);
//迭代器方法1(普通for循环存取)
Iterator it=coll.iterator();
for(int i=0;i<coll.size();i++){
System.out.println(it.next());
}
//迭代器方法2(演示的方法)
Iterator it=coll.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//迭代器方法3(开发常用的方法)
for(Iterator it=coll.iterator();it.hasNext();){
System.out.println(it.next());
}
//for each方法,用于遍历数组或Collection集合
for(Object obj:coll){
System.out.println(obj);
}
}
单独运行每一种方法结果都如下:[abc1, abc2, abc3]abc1abc2abc3
注:以上部分文字摘自网络,如有侵权请联系进行修改