集合框架中的接口以及继承关系
关于集合框架中的接口,注意下面列出的全都是接口:
接口与具体的实现类蓝色表示接口橙色表示具体实现类(仅仅列出比较常用的一些):
这个图是一个具体的集合框架的接口以及实现类的图,可以和上面的全部接口相对应:
因为collection有很多的子类,为了操作方便,并没有哪个类直接实现了collecion接口,而是直接对collection接口进行继承,仅仅是提供了更加具体的子接口。
关于List
ArrayList的基本使用:
package com.javase.collection; import java.util.ArrayList; import java.util.LinkedList; public class testList { public static void main(String[] args) { //最好是通过泛型的方式来使用 负责取出元素的时候还需要进行强制类型转化 ArrayList<String> list=new ArrayList<String>(); //通过append的方式进行追加 list.add("abc"); list.add("def"); list.add("ghi"); list.add("jkl"); //获得固定下标的元素 String a=list.get(0); int s=list.size(); System.out.println("the size is "+s); for(int i=0;i<s;i++) { System.out.println(list.get(i)); } //判断某个元素的索引 System.out.println("the index of abc is "+ list.indexOf("def")); //删除固定索引的元素 list.remove(0); System.out.println("after the remove option the first element is "+ list.get(0)); //转化为一个数组 //这里要是写成 String [] str=(String [])list.toArray();在执行的时候就会抛出异常 //只能在使用的时候 先取出一个个 Object对象 再进行强制转化 //因为仅仅是String Integer等等这些类型继承了Object 而String[] Integer[]并没有继承Object[] //因此这里只能使用Object[]来进行强制类型转化 使用别的就会报错 Object [] str=(Object [])list.toArray(); System.out.println("--------------------"); for(int i=0;i<str.length;i++) { System.out.println(str[i]); } LinkedList linklist=new LinkedList(); //直接可以将其他的集合类型作为参数传入 生成一个ArrayList ArrayList list3=new ArrayList(linklist); //clear清除全部内容 list.clear(); System.out.println("after clear option the size is "+list.size()); } } 输出结果: the size is 4 abc def ghi jkl the index of abc is 1 after the remove option the first element is def -------------------- def ghi jkl after clear option the size is 0
关于ArrayList源码的简要分析:
类的声明的结构如下
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
注意看整个ArrayList类声明的时候就是通过泛型的方式来进行的
一共有三个构造方法:
第一个是通过声明一个capacity
可以看到在构造函数中 关键的部分是:
this.elementData = new Object[initialCapacity];
其中elementData是个Object类型的数组 初始的时候,实际ArrayList的底层是通过这个Object类型的数组来维护的。
第二个是没有参数的形式
public ArrayList() {
this(10);
}
可以看到这段,要是不指定capacity的话,ArrayList底层默认生成一个capacity为10的数组。
第三个是通过传入另外一个集合框架 将其中的内容转化成为一个ArrayList
比如可以生成一个LinkedList 对象 将其作为形参传入 生成ArrayList
关于add方法
注意添加新元素之前要对集合的siz+1进行检测,保证当前底层的Object数组的空间是足够的,这一步通过ensureCapacityInternal(size + 1);这个函数来进行,够的话就直接放入,size增加1,要是不够的话会通过调整newCapacity来继续进行,调整之后将原来的元素拷贝到一个新的Object数组中,数组空间大小为newCapacity(通过Arrays.copyof来进行):
elementData = Arrays.copyOf(elementData, newCapacity);
还可以添加参数,将元素插入到指定的位置,但这种方式需要将插入位置后面的部分都进行移动,代价比较高通过这个函数来进行:
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
函数原型声明:(void java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length))
关于remove方法
这个其实与上面在固定位置插入元素的操作很相似,只不过拷贝的方式是向前进行的。
由于ArrayList的底层就是一个Object数组,因此在固定位置上的插入和删除操作代价都是比较高的。
关于LinkedList
这个也算是数据结构中最基本的了,就是链表的结构,根据链表自身的结构特性,比起ArrayList又有一些特殊的方法,比如头插addFirst 尾插 addLast。
当然LinkedList底层肯定维护的是一个链表,不想ArrayList那样维护一个数组,这个是结点Node 的部分,是一个用泛型来实现的静态内部类,内容就是我们通常意义上的具体元素值,前驱指针,后继指针:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
具体的插入删除操作都与通常数据结构书中介绍的内容类似。
还是那些老生常谈的问题,再注意一下,也就是线性表和链表的区别,插入删除操作的时候用LinkedList比较快,指定位置检索元素的时候用ArrayList比较快。
关于Set的机制
Hashcode与equals方法的补充说明
首先要再对hashcode 以及equal方法再进一步进行说明,之前的一些说明可以参(http://www.cnblogs.com/Goden/p/3990985.html)。
进一步补充:
hashcode的一般要求:
1、 一致性,同一个对象的hashcode方法被多次调用之后应当返回一样的值(前提是该对象的内容没有发生变化)
2、 两个对象a,b,如果a.equals(b)返回的是true,则对象a和b的hashcode值要是相同的。
3、 两个对象 a,b 如果a.equals(b)返回的是false,则对象 a 和 b 的hashcode不要求一定相同,即使说在hashcode相同的时候,a.equals(b)不一定是true,也可以是false。
对于Object类,其hashcode表示的是对象的地址,因此不同Object实例,其对象是不同的。
一般一个自定义的类的equal方法重写之后,对应的hashcode方法也需要进行重写,这是为了保证通过equal比较相等的对象有相同的hashcode。
当使用HashSet的时候,若是增加一个元素,则会判断已经存在集合中的所有元素的hashcode的值是否与增加的这个元素的hashcode的值是一致的。如果不一致,则将这个元素加进去。如果与某个对象一致(hashcode一样),按照前面的描述,这个时候equals方法还有可能返回的是false于是再进行equals方法的调用,若和这个对象进行比较,若equals返回为true,说明这个元素已经加入集合,就不再进行添加了。若equals返回为 false,说明这个元素还没有加入到集合中(就是那种hashcode相同而equals不相同的情况)Hashcode相同而equals不同的例子可能不太好想到,比如下面这个:
package com.testcollection; import java.util.HashSet; class People{ String name; public People(String name){ this.name=name; } } public class testHashcode { public static void main(String[] args) { HashSet<People> set=new HashSet<People>(); //这两次添加就成功了 因为按照object类的hashcode和equal方法 这个是两个不同的 //但实际上里面的内容是相同的 set.add(new People("abc")); set.add(new People("abc")); //像这种情况 虽然两个类的hashcode并不相同 根据打印结果就能看出来 默认的toString方法 在@后面 就是hashcode值 但是实际上他们的内容是一样的 //属性的情况比较复杂的话 就不能满足需求了 //这种情况下 Object类中的hashcode与equals方法就不能满足实际的需要了 需要重写 System.out.println(set); People p1=new People("def"); System.out.println(set.add(p1)); //这个添加就失败了
//这里两次添加的都是p1指针指向的是同一个对象 不像是上面那种情况 指向的是不同的两个对象(虽然对象的内容是相同的 但是是生成了两次的)
System.out.println(set.add(p1)); } } /* 打印结果 [com.testcollection.People@55fe910c, com.testcollection.People@3be4d6ef] true false */
实际情况下,应当对Person中的hashcode和equals方法进行重写,hashcode就直接用属性的hashcode,而equals方法则参照Object类中的equals方法来实现具体可以参考(http://www.cnblogs.com/Goden/p/3990985.html)
下面就是重写的例子:
package com.test.collection; import java.util.HashSet; public class testHashcode { public static void main(String[] args) { HashSet<People> set=new HashSet<People>(); System.out.println(set.add(new People("abc"))); //由于重写了people类的hashcode 以及equals方法 第二次插入就会返回false 插入失败 System.out.println(set.add(new People("abc"))); } } class People { String name; public People(String name) { this.name = name; } public int hashCode() { return this.name.hashCode(); } public boolean equals(Object obj) { boolean b = false; if (this == obj) { b = true; } if (null != obj && obj instanceof People) { People p = (People) obj; if (name.equals(p.name)) { b = true; } else { b = false; } } return b; } }
实际开发中对于hashcode以及equals方法的重写通常是需要通过一一比较类中的内容,这样进行下去。由于实际情况中这个用的还是比较普遍,通常直接在编译器的source->generate equals and hashcode的操作这样就能继续往下进行。
迭代器
各个集合的实现都有一个iterator()方法,这个方法主要是用来返回一个迭代器。
迭代器的最顶层就是一个用泛型来实现的public interface Iterator<E>接口,方法也比较简单,主要有三个:hasNext Next 以及remove。
hasNext表示集合中是否还有更多的元素,如果集合中还有更多元素的话,这个结果就返回true,要是没有更多元素的话,就返回false
Next 表示返回下一个元素,注意每次调用了Next之后,要执行两个操作:返回下一个元素,以及往后跳动一个位置。remove 这个是用于安全地删除集合中的元素,具体的集合的实现类一般都对这个方法进行了不同的实现。
通常用到的是前两个方法。
下面是一个简要的逻辑图,便于理解next以及hasnext,注意初始的时候:
由于每个集合框架都对这个Iterator接口有具体的实现,实际中也要是通过iterator函数返回一个iterator迭代器,之后通过这个迭代器来遍历集合当中的每个元素,这样一次可以返回一个元素,通常使用一个循环来遍历集合中的每个元素,格式比较固定,主要是三步:调用iterator方法得到迭代函数,建立hasNext()循环迭代,只要返回值为true就继续进行,在循环内部通过next方法得到下一个的元素(指针会同时后移)。
下面是一个基本的例子,对应的写法要掌握:
package com.test.collection; import java.util.HashSet; import java.util.Iterator; public class testIterator { public static void main(String[] args) { HashSet<Integer> set=new HashSet<Integer>(); for(int i=0;i<=10;i++){ set.add(i); } //通过迭代的方式遍历集合 打印出集合中全部元素 for(Iterator<Integer> iter=set.iterator();iter.hasNext();){ System.out.println(iter.next()); } } }
关于HashSet与TreeSort
通过名称也比较好理解,HashSet就是最直接的Set的机制,最后输出的话,输出元素的顺序是凌乱的而且每次输出的时候都不一样,TreeSet就是集合+排序,里面的元素在每次添加的时候都是自动排好序的,TreeSet实现了SortedSet的接口,里面元素的顺序是有序的,输出的时候就是按序输出的。注意定义TreeSet的时候,如果里面的元素是非原始的数据类型,一定要传入一个自定义的比较器,要不然就没法往TreeSet中放元素,一定要先通过TreeSet来指定好元素的比较顺序。
package com.test.collection; import java.util.HashSet; import java.util.Iterator; import java.util.TreeSet; public class testIterator { public static void main(String[] args) { HashSet<Integer> set=new HashSet<Integer>(); TreeSet<Integer> treeset=new TreeSet<Integer>(); for(int i=100;i>=1;i--){ set.add(i); treeset.add(i); } //通过迭代的方式遍历集合 打印出集合中全部元素 这个输出就是无序的 for(Iterator<Integer> iter=set.iterator();iter.hasNext();){ System.out.println(iter.next()); } //放到TreeSet里面的输出就是有序的 默认是从小到大的 for(Iterator<Integer> siter=treeset.iterator();siter.hasNext();){ System.out.println(siter.next()); } } }
关于Comparator接口
由于TreeSet实现了SortedSet接口,每次新加入进来的元素都是有序存放的,这里就涉及到了排序的问题。
就像C++的sort的实现一样,如果是自定义的排序问题的话,特别是对于结构话的数据进行比较,需要引入一个Comparator接口,在声明集合的时候将比较器传入。这个接口中有一个compare方法,自定义的比较器实现了这个接口之后再传入TreeSort中就可以按照自定义的模式进行比较了。
注意compare方法的返回类型是int类型,按照自定义的比较方式,如果:
第一个参数小于第二个参数 返回一个负数
第一个参数等于第二个参数 返回0
第一个参数大于第二个参数 返回一个正数
下面是一个基本的在定义TreeSet集合的时候使用自定义Comparator构造器的例子:
package com.test.tempTest; import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; class Person { int id; String name; public Person(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; 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 (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } class Mycompare implements Comparator<Person> { // sorted by name increasing public int compare(Person p1, Person p2) { int result = p1.getName().compareTo(p2.getName()); if (result == 0) { result = p1.getId() - p2.getId(); } return result; } } public class testbasicComparator { public static void main(String[] args) { // 声明的时候可以传入一个实现了comparator接口的类作为自定义比较器 TreeSet<Person> treeset = new TreeSet<Person>(new Mycompare()); treeset.add(new Person(1, "dfe")); treeset.add(new Person(2, "efd")); treeset.add(new Person(3, "abc")); treeset.add(new Person(4, "dfe")); // 通过迭代器输出 for (Iterator<Person> iter = treeset.iterator(); iter.hasNext();) { Person p = iter.next(); System.out.println("the name is: " + p.getName() + " the id is " + p.getId()); } } } /* 输出结果如下,可以看出来结果符合Comparator中定义的顺序: the name is: abc the id is 3 the name is: dfe the id is 1 the name is: dfe the id is 4 the name is: efd the id is 2 */
关于Collections类
Arrays类是数组方法的工具类,里面提供的许多对于数组操作的静态方法。类似地Collections是一个集合的工具类,里面提供了许多对于集合的操作的静态方法,操纵的目标是集合对象,为集合对象提供辅助的方法,比如最基本的sort方法可以提供实现了List接口的集合的排序。
还有一些比较常用到的辅助函数:
比如Collections.shuffle(List<E>list)这个主要功能是将list中元素的顺序弄乱,在快速排序的时候,用这个可以避免最差的那种情况,来提升效率。
还有就是Collections.min(List<E>list)以及Collections.max(List<E>list)用于返回序列中的最大和最小的元素。
整个Comparator就是基于策略模式实现的,下面是一个基于策略模式实现的比较的例子:一个Person类中有id,name,age三个属性,通过泛型来自定义一个比较器,通过策略模式来传入一种sort策略,进行排序。
//首先是一个基本的Person类 package com.test.collector; public class Person { int id; int age; String name; public Person(int id, int age, String name) { super(); this.id = id; this.age = age; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } //注意重写hashcode以及equals方法 //这里可以通过eclispe来自动生成 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + id; 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 (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } //Sort接口: package com.test.collector; import java.util.List; public interface Sort { public void sort(List<Person> list); } //Sort接口的两种实现,同时实现了Comparator接口: package com.test.collector; import java.util.Collections; import java.util.Comparator; import java.util.List; //这里同时还要实现comparator接口 public class upNameSort implements Sort,Comparator<Person> { public void sort(List<Person> list) { //通过collections工具类的sort方法来进行排序 Collections.sort(list,this); } public int compare(Person p1, Person p2) { int result=p1.getName().compareTo(p2.getName()); //根据id来进行 if(result==0){ result=p1.getId()-p2.getId(); } return result; } } package com.test.collector; import java.util.Collections; import java.util.Comparator; import java.util.List; public class upIdSort implements Sort,Comparator<Person> { public void sort(List<Person> list) { Collections.sort(list, this); } public int compare(Person p1, Person p2) { int result=p1.getId()-p2.getId(); return result; } } //通过这个类来体现策略模式 通过传入的way参数来调用不同的方式进行排序: package com.test.collector; import java.util.List; public class SortWay { private Sort dosort; private int way; public Sort getDosort() { return dosort; } public void setDosort(Sort dosort) { this.dosort = dosort; } public void doSort(List<Person> list, int way) { if (way == 1) { this.dosort = new upIdSort(); dosort.sort(list); } if (way == 2) { this.dosort = new upNameSort(); dosort.sort(list); } } } //Client类 用于进行实际的测试: package com.test.collector; import java.util.ArrayList; import java.util.Iterator; public class Client { public static void main(String[] args) { ArrayList<Person> list = new ArrayList<Person>(); list.add(new Person(4, 23, "abc")); list.add(new Person(2, 24, "cdf")); list.add(new Person(3, 25, "fgr")); list.add(new Person(1, 26, "abc")); SortWay sw = new SortWay(); // 此时是按照id排序 sw.doSort(list, 1); for (Iterator<Person> iter = list.iterator(); iter.hasNext();) { Person p = iter.next(); System.out.println("the name is :" + p.getName() + " the id is :" + p.getId()); } System.out.println("--------------------------"); sw = new SortWay(); // 此时是按照name排序 sw.doSort(list, 2); for (Iterator<Person> iter = list.iterator(); iter.hasNext();) { Person p = iter.next(); System.out.println("the name is :" + p.getName() + " the id is :" + p.getId()); } } } /* 输出结果: the name is :abc the id is :1 the name is :cdf the id is :2 the name is :fgr the id is :3 the name is :abc the id is :4 -------------------------- the name is :abc the id is :1 the name is :abc the id is :4 the name is :cdf the id is :2 the name is :fgr the id is :3 上面是按照id进行排序 下面是按照name字典顺序进行的排序 */
关于Map的机制
关于map的基本概念
Map<K,V>数据结构主要用于表示一个key<—>value的键值对,key与value可以是通过泛型来定义的不同的数据类型,他们之间的的关系是一一对应的,一个key映射到一个value值上,但是所有的Key值是不可以重复的,如果add相同的key值,但是value不同的键值对进入map,貌似后插入的一个key-value值会将先前的key-value值覆盖掉,最后只存最后的key-value值。
通常使用的就是HashMap,就像HashSet一样,存入进来的键值对再取出的时候是乱序的。
放入的时候直接map.put(“key”,”value”)取value值的时候通过key值来作为索引取出,比如取出一个String类型的value,String value=map.get(“key”)。(注意在map中是使用put而不是使用add)
关于迭代输出的时候通常有两种方法:
1、 通过返回map中所有key的集合(set类型),再依次通过map.get(“key”)取出所有的value的值。
2、 通过返回一个entry结构的集合(Map中的一个内部类,这个类中维护了key value的信息)具体见下面的源码分析
下面是一个Map基本操作的例子:
package com.test.testMap; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; public class basicMap { public static void main(String[] args) { HashMap<String, String> map = new HashMap<String, String>(); map.put("a", "aa"); map.put("b", "bb"); map.put("c", "cc"); map.put("d", "aa"); // 加入了key相同的键值对 后加入的一个先加入的一个键值对替代掉 map.put("a", "bb"); // 通过key的集合来迭代 Set<String> set = map.keySet(); for (Iterator<String> iter = set.iterator(); iter.hasNext();) { String key = iter.next(); String value = map.get(key); System.out .println("the key is :" + key + " the value is :" + value); } System.out.println("--------------------"); // 通过第二种方式进行迭代 通过返回一个Entry的集合来进行 Set<Entry<String, String>> entryset = map.entrySet(); for (Iterator<Entry<String, String>> entryiter = entryset.iterator(); entryiter .hasNext();) { Entry<String, String> e = entryiter.next(); String key = e.getKey(); String value = e.getValue(); System.out .println("the key is :" + key + " the value is :" + value); } } } /* 输出的结果如下,可以看出来后加进来的关键字相同的(a,bb)键值对代替了最开始输入的的(a,aa)键值对。 the key is :d the value is :aa the key is :b the value is :bb the key is :c the value is :cc the key is :a the value is :bb ------------------------------ the key is :d the value is :aa the key is :b the value is :bb the key is :c the value is :cc the key is :a the value is :bb */
关于HashSet以及HashMap的简要源码分析
HashSet的底层是由HashMap来实现的,只是从逻辑上来看的话,就抽象成了一个HashSet,事实上在HashSet的源码中:
属性值中有这个:
private transient HashMap<E,Object> map;
默认的构造函数是这个:
public HashSet() {
map = new HashMap<>();
}
可以看出来Set的本质就是一个Map,只不过这Set中的值就是这个Map中的key值,这个Map中的Value值并没有实际的意义,只是用一个Object来占个位置就好了。逻辑上看成是一个Set类型,实际底层使用Map类型来实现的。当一个元素被放入HashSet中的时候,实际上这个元素是被放入了底层所维护的HashMap的key中,而他们的value值全都是一个Objec对象,并没有什么实际的意义,只起到占位的作用,这个对象在我们对Set进行操作的相关过程中并不会用到。
实际上只要把HashMap分析清楚就相当于把HashSet一起分析了。
先看HashSet中维护了静态类:
static class Entry<K,V> implements Map.Entry<K,V>
这个类里面维护了一个Key Value键值对的信息,以及其他的相关信息(指向下一个Entry结点的指针)。
在类的属性中有一个table表,实际上就是一个Entry的数组:
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
其中EMPTY_TABLE初始的时候是一个空的ENTRY数组:
static final Entry<?,?>[] EMPTY_TABLE = {};
具体的代码没有一句一句来分析,只是把主要的思想记录下来,主要是分析在put一个键值对进入的时候具体都执行了哪些操作:
向HashMap中put一个键值对的时候,先对空间进行检查,如果之前table是空的,就重新为table数组分配空间,之后通过hash函数进行hash地址的计算(这里用到了比较多的移位的操作),就像通常数据结构中介绍的那样,计算出散列地址,之后进行冲突处理。
这里对于冲突的处理方式是采用链地址法,每次散列到了一个新的地址,就会采用头插法,如果新的键值对与旧的键值对有相同的key值和不同的value值,后面的value值将会把之前的value值替换掉。如果新的键值对与旧的键值对仅仅是hash地址相同但key值是不同的,就是发生的地址冲突的情况,就用头插大将将新的Entry结点插入到当前的位置(最近被调用的再次被调用的几率比较大),当前新的Entry结点的next指针会指向旧的Entry结点。
在这个过程中还要涉及到table数组的重新复制以及扩容(扩容的阈值是通过之前指定的负载因子来控制的)。
其他
可能还有一些框架比如Vector这个基本上被ArrayList代替了,它们实现的功能大致也是相同的,类似地,HashTable也被HashMap代替了,它们也实现的是类似的功能,貌似它们之间主要的区别是线程安全问题,HashTable是线程安全的,其中的许多方法都是Synchonized的HashMap不是线程安全的,但在单线程的环境下,性能比HashTable要好,具体的还没有去深究。