java集合框架的深入
2016-05-12 20:32
567 查看
今晚和往常一样在论坛上寻找着自己需要的知识,突然看到有人问到当初困扰自己很久的问题,这时我意识到把自己懂得的知识分享给大家和大家一起进步才是一个小菜鸟应该做的!PS:我是一个初中毕业的小菜,可能在描述方面很朴素,但是我会尽最大的努力去描述自己懂的知识。
集合框架
集合框架作为java最开始接触到东西,现在回过头去研究其内部如何运作是非常有必要的!
![](http://img.blog.csdn.net/20160512201855484)
该测试方法会抛出 java.util.ConcurrentModificationException异常;
首先在 AbstractList类中有个属性:protected transient int modCount = 0;
这里我们只讨论modCount属性对于AbstractList类 我们后面再讨论。
modCount这个属性是用记录操作集合的计数器;
以ArrayList为例
添加对象
移除对象
Iterator的hasNext()和next()方法,Iterator的实现在AbstractList类中;
判断是否有元素方法
获取下一个元素方法
检查集合是否被修改方法
到这里大概有个了解吧,当一个集合获取一个迭代器的时候,该集合的modCount的值将会赋值给expectedModCount,当迭代器进行遍历集合的时候会检查expectedModCount的值是否和modCount相等,假如不相等将会抛出异常;所以在迭代的时候修改集合,注意是对集合,不是对集合里面的对象喔,将会抛出异常。
foreach循环说白了就是对迭代器进行了一次封装,让代码更加简洁更不容易出错,但是它们的remove()方法还是有区别的。
ArrayList集合 remove()方法源码:
Iterator remove()方法源码
这里大概明白了把当调ArrayList的remove()时没有对expectedModCount值进行改变的操作,而Iterator的remove()会有一个对expectedModCount赋值的操作。遍历的时候checkForComodification()会检查expectedModCount与modCount是否相等,这里也就是抛出异常的地方。
2、collection定义了一些列方法:
![](http://img.blog.csdn.net/20160514104828491)
注意该方法是Iterable接口的喔。
3、简单的来说:Deque接口为队列、List接口可重复的集合、set接口不可重复的集合,对于更加详细的我们接下来一起慢慢做研究。
1、ArrayList底层是基于数组是线性的,优点:随机访问速度很快。缺点:当插入删除元素时较为费时。
2、LinkedList是基于链表的,优点:插入删除元素时速度快。缺点:随机访问的速度较慢。
3、Vector 和ArrayList几乎一样,但Vector是线程安全,默认的扩容方式是原来的2倍,ArrayList是原来的1.5倍。
ArrayList就是一个动态的数组有下标的概念,当知道第一个元素的位置就知道其他元素的位置了所以随机访问的数据很快,但就因为它的下标导致它每次增加或删除元素后下标都需要挪动位置,ArrayList用作查询最好,ArrayList在多线程环境下需要注意同步;
LinkedList就是一个双向循环链表,内部有个Entry类,里面包括前一个元素和后一个元素的地址,这样增加或删除元素后就不要重写排列速度很快,但随机访问就需要从第一个开始查找速度简直慢爆了;
Vector几乎和ArrayList一样,在多线程环境下建议使用。
添加元素方法如下:
用元素的个数加一进行容量验证。
当素组为长度为0时,默认长度为DEFAULT_CAPACITY(10)和minCapacity中最大的值。
操作计数器自增(用于迭代器遍历判断使用),minCapacity 容量大于elementData长度将继续容量验证。
minCapacity容量比elementData长度乘以1.5还要大的话,将minCapacity赋值给newCapacity,继续对newCapacity和MAX_ARRAY_SIZE(int最大值减去8)进行比较,大于MAX_ARRAY_SIZE该值的话,将用minCapacity值去和MAX_ARRAY_SIZE比较如果还大于,就将会得到int最大的值作为容量,然后进行数据拷贝。
添加一堆数据方法。
看了数组和链形的实现是不是清晰很多?举个栗子数组就像班上的同学每个人都有一个学号,知道学号就知道是那个同学了;链表还是一个班上的同学,只不过他们是手牵着手的,除了首尾两个同学之外,其他的每个同学都只知道自己上一个和下一个同学是谁。
遍历耗时如下:
arrayList(for循环):7
LinkedList(for循环):6239
LinkedList(迭代器):4
我们发现LinkedList(for循环)遍历方式简直弱爆了,我们来一探究竟。
通过源码我们知道,其实每次调用get()方法就是一次遍历,虽然if (index < (size >> 1))做了优化,但是实在是无能为力了,一旦linkedList元素过多采用这种方式就完蛋了。
正确的遍历方式应该采用迭代器或者foreach()的方式,foreach()底层也是迭代器,就是个语法糖而已。
(实际也会发生碰撞,即两个不同的消息经过hash函数得到一样的消息摘要),这个消息摘要可以标识这个消息就像数据表的主键一样。
set接口有两个最终实现类hashSet和treeSet,hashSet无序不重复的集合、treeSet有序不重复的集合。
两个最终实现类的底层几乎全是调用map的方法,可以理解为实现类的底层是map的底层,而map底层中最重要的就是hash,由于最终类主要调用map的方法,set就简单研究而map再去深入研究。
第二种方法:实现Comparator接口
2、如过要对String类进行排序,我们试图改写规则要怎么办String这个类我们没办法修改,这时候用Comparator()接口实现我们自己的排序就非常棒。
3、其实用什么排序看具体的情况而定,千万不要生搬硬套。
集合框架
集合框架作为java最开始接触到东西,现在回过头去研究其内部如何运作是非常有必要的!
Iterator
迭代器可以遍历集合框架里面所有的集合是非常重要和方便的,但里面也有很多需要注意的地方,迭代器作为一个接口,从图中可以看到Collection总接口继承该接口也就是说所有的集合都可以用迭代器进行遍历,而该接口中有一个最重要方法Iterator iterator();该方法返回T 类型的元素上进行迭代的迭代器。可能遇到的异常:
java.util.ConcurrentModificationException。public void test(){ List<String>list=new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> it = list.iterator(); while(it.hasNext()){ String value=it.next(); if("c".equals(value)){ list.add("error"); } System.out.println(value); } }
该测试方法会抛出 java.util.ConcurrentModificationException异常;
首先在 AbstractList类中有个属性:protected transient int modCount = 0;
这里我们只讨论modCount属性对于AbstractList类 我们后面再讨论。
modCount这个属性是用记录操作集合的计数器;
以ArrayList为例
添加对象
private void ensureExplicitCapacity(int minCapacity) { //计数器进行了自增 modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
移除对象
public E remove(int index) { rangeCheck(index); //计数器进行了自增 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0){ System.arraycopy(elementData, index+1, elementData, index, numMoved); } elementData[--size] = null; return oldValue; }
Iterator的hasNext()和next()方法,Iterator的实现在AbstractList类中;
判断是否有元素方法
public boolean hasNext() { //cursor集合里面下一个对象的下标 return cursor != size(); }
获取下一个元素方法
public E next() { //检查该集合被修改没有 checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; //集合里面下一个对象的下标进行自增,用于hasNext()方法判断是否还有下一条数据; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
检查集合是否被修改方法
final void checkForComodification() { //这里发现了modCount的作用了,expectedModCount是调用iterator()方法初始化时获取 modCount的值; if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
public Iterator<E> iterator() { return new Itr(); } //Itr类片段 private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; //将modCount的值赋值给expectedModCount int expectedModCount = modCount; }
到这里大概有个了解吧,当一个集合获取一个迭代器的时候,该集合的modCount的值将会赋值给expectedModCount,当迭代器进行遍历集合的时候会检查expectedModCount的值是否和modCount相等,假如不相等将会抛出异常;所以在迭代的时候修改集合,注意是对集合,不是对集合里面的对象喔,将会抛出异常。
Iterator和foreach的关系:
public static void test(){ List<String>list=new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> it = list.iterator(); //foreach循环 for(String value:list){ System.out.println(value); } //foreach底层实现也是采用迭代器的 while(it.hasNext()){ String value =it.next(); System.out.println(value); } }
foreach循环说白了就是对迭代器进行了一次封装,让代码更加简洁更不容易出错,但是它们的remove()方法还是有区别的。
public static void test(){ List<String>list=new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> it = list.iterator(); //foreach for(String value:list){ if(value.equals("a")){ list.remove(value);//调用了该方法后,进行下一次迭代 就会抛出ConcurrentModificationException异常 } } //Iterator System.out.println(list.size()); //长度为4 while(it.hasNext()){ String value =it.next(); if(value.equals("a")){ it.remove(); } } System.out.println(list.size()); //长度为3,说明正确删除掉了。 }
ArrayList集合 remove()方法源码:
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index);//调用删除方法 return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; //操作计数器进行自增 int numMoved = size - index - 1; if (numMoved > 0){ System.arraycopy(elementData, index+1, elementData, index,numMoved); } elementData[--size] = null; }
Iterator remove()方法源码
public void remove() { if (lastRet < 0) throw new IllegalStateException(); //检查expectedModCount与modCount是否相等 checkForComodification(); try { //调用删除方法 AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; //这里很关键,expectedModCount被重新赋值 expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } }
这里大概明白了把当调ArrayList的remove()时没有对expectedModCount值进行改变的操作,而Iterator的remove()会有一个对expectedModCount赋值的操作。遍历的时候checkForComodification()会检查expectedModCount与modCount是否相等,这里也就是抛出异常的地方。
Collection (JDK7)
1、collection接口继承Iterable接口也就是迭代器接口。2、collection定义了一些列方法:
注意该方法是Iterable接口的喔。
3、简单的来说:Deque接口为队列、List接口可重复的集合、set接口不可重复的集合,对于更加详细的我们接下来一起慢慢做研究。
AbstractCollection
该抽象类实现了Collection接口和Iterable接口里面的方法,是AbstractList(list接口实现抽象类)和AbstractSet(set接口实现抽象类)的父类。AbstractList子类:ArrayList、LinkedList、Vector 。
ArrayList和LinkedList、Vector 的区别:1、ArrayList底层是基于数组是线性的,优点:随机访问速度很快。缺点:当插入删除元素时较为费时。
2、LinkedList是基于链表的,优点:插入删除元素时速度快。缺点:随机访问的速度较慢。
3、Vector 和ArrayList几乎一样,但Vector是线程安全,默认的扩容方式是原来的2倍,ArrayList是原来的1.5倍。
ArrayList就是一个动态的数组有下标的概念,当知道第一个元素的位置就知道其他元素的位置了所以随机访问的数据很快,但就因为它的下标导致它每次增加或删除元素后下标都需要挪动位置,ArrayList用作查询最好,ArrayList在多线程环境下需要注意同步;
LinkedList就是一个双向循环链表,内部有个Entry类,里面包括前一个元素和后一个元素的地址,这样增加或删除元素后就不要重写排列速度很快,但随机访问就需要从第一个开始查找速度简直慢爆了;
Vector几乎和ArrayList一样,在多线程环境下建议使用。
ArrayList比较重要的知识
ArrayList默认初始化大小?
// ArrayList带容量大小的构造函数。 public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } // ArrayList无参构造函数。(jdk1.7) public ArrayList() { //DEFAULTCAPACITY_EMPTY_ELEMENTDATA是空数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } // ArrayList无参构造函数。(jdk1.7以前不传入容量默认容量为10) public ArrayList() { this(10); }
超级数组是如何动态变大的?
在arrayList中有两个最为重要的变量:
//存放元素的数组 transient Object[] elementData; //表示元素的个数 private int size;
添加元素方法如下:
//调用顺序:1 public boolean add(E e) { //动态添加的秘密在这里面喔! ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
用元素的个数加一进行容量验证。
//调用顺序:2 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
当素组为长度为0时,默认长度为DEFAULT_CAPACITY(10)和minCapacity中最大的值。
//调用顺序:3 private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
操作计数器自增(用于迭代器遍历判断使用),minCapacity 容量大于elementData长度将继续容量验证。
//调用顺序:4 private void grow(int minCapacity) { int oldCapacity = elementData.length; //oldCapacity + (oldCapacity >> 1)可以认为是1.5倍 //jdk1.7以前采用的是int newCapacity = (oldCapacity * 3)/2 + 1; //jdk1.7采用位运算比以前的计算方式更快。 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //jdk1.7这里增加了对元素个数的最大个数判断,jdk1.7以前是没有最大值判断的,MAX_ARRAY_SIZE 为int最大值减去8 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //最重要的复制元素方法 elementData = Arrays.copyOf(elementData, newCapacity); }
minCapacity容量比elementData长度乘以1.5还要大的话,将minCapacity赋值给newCapacity,继续对newCapacity和MAX_ARRAY_SIZE(int最大值减去8)进行比较,大于MAX_ARRAY_SIZE该值的话,将用minCapacity值去和MAX_ARRAY_SIZE比较如果还大于,就将会得到int最大的值作为容量,然后进行数据拷贝。
//复制元素方法( original - 要复制的数组 //newLength - 要返回的副本的长度 //newType - 要返回的副本的类) public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") //确定数组类型class,(newType.getComponentType()方法是获取数组类型的Class) T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); //调用系统的copy方法 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } //复制元素最后的方法(该方法被native修饰,是由外部实现无法看到源码) //但是根据参数的含义,我们也大概只要这个方法要做什么。 //src - 源数组。 //srcPos - 源数组中的起始位置。 //dest - 目标数组。 //destPos - 目标数据中的起始位置。 //length - 要复制的数组元素的数量。 public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length);
添加一堆数据方法。
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; //只需要注意这里,判断的容量是:原本元素的个数和插入个数相加。 ensureCapacityInternal(size + numNew); System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
LinkedList比较重要的知识
什么是线性、非线性、栈、队列?
线性代表表达:A 顺序线性表也就是数组,在内存中是一块连续的空间,长度不可变;B:链表也就是在内存中不是连续的长度可变,每一个对象中都包含下一个对象的内存地址;C线性包括:数组、链表、队列、栈,非线性包括:树、图片、表。 栈:先进后出,只能在一端进行数据操作,类似于向一个篮子里面扔衣服永远都是先拿最上面的衣服。 队列:先进先出,只能在一端进行增加一端进行删除,类似于排队买票,先排的人先买到票。
编写一个简单无限大的栈
import java.util.Arrays; /** * 简单模拟线性栈(数组版) * @author orange * */ public class StackTest { private Object[] nodesObjects=new Object[10];//存放元素的数据 private int size=-1;//顶层元素的下标 //增加 private void add(Object obj){ chekLength(size+1);//检查容量 nodesObjects[++size]=obj; } //弹出 private Object pop(){ return nodesObjects[size--]; } //查看 private Object show(){ return nodesObjects[size]; } //是否为空 private boolean isNull(){ return size==-1; } //清空 private void clear(){ Arrays.fill(nodesObjects, null); size=-1; } //长度检测 public void chekLength(int newLength){ int oldLength=nodesObjects.length; if(oldLength<newLength){ //默认扩容 oldLength=oldLength+(oldLength>>1); newLength=Math.max(oldLength, newLength); Object[] temp=new Object[newLength]; System.arraycopy(nodesObjects, 0, temp, 0, nodesObjects.length); nodesObjects=temp; } } }
LinkedList简单实现
/** * LinkedList 简单版本 * @author orange * */ public class StackTest3 { public int size; //集合个数 public NodeTest headNode; //第一个节点 public NodeTest tailNode; //最后一个节点 //用于存放节点的对象 class NodeTest{ Object object; //存放具体的数据 NodeTest next; //下一个对象 NodeTest previa;//上一个对象 NodeTest(NodeTest previa,Object object,NodeTest next){ this.object=object; this.next=next; this.previa=previa; } } //添加尾部 public void addTail(Object object){ //创建一个节点对象 NodeTest node=new NodeTest(null,object,null); if(isNull()){ //当链表为空时,头指针和尾指针都为新创建的节点。 headNode=tailNode=node; }else{ //将新节点设置为最后一个节点的下一个节点。 tailNode.next=node; //将新节点的上一个节点为最后一个节点。 node.previa=tailNode; //尾指针指向新节点。 tailNode=node; } //节点个数加1 size++; } //添加头部 public void addHead(Object object){ //创建一个节点对象 NodeTest node=new NodeTest(null,object,null); if(isNull()){ //当链表为空时,头指针和尾指针都为新创建的节点。 headNode=tailNode=node; }else{ //将新节点的下一个节点设置为第一个节点。 node.next=headNode; //将第一个节点的上一个设置为新节点。 headNode.previa=node; //头指针指向新节点。 headNode=node; } //节点个数加1 size++; } //删除尾部 public Object deleteTail(){ if(isNull()){ //链表为空时,返回null return null; } NodeTest temp = tailNode; if(size==1){ //链表只有一个节点的话,将头尾指针设置为空。 tailNode=headNode=null; }else{ //将尾指针指向最后一个节点的上一个节点。 tailNode=temp.previa; //取消对最后一个节点的引用 tailNode.next=null; } //节点个数减1 size--; return temp; } //删除头部 public Object deleteHead(){ if(isNull()){ //链表为空时,返回null return null; } NodeTest temp =headNode; if(size==1){ //链表只有一个节点的话,将头尾指针设置为空。 headNode=tailNode=null; }else{ //头指针指向第二个节点。 headNode=temp.next; //取消对第一个节点的引用 headNode.previa=null; } //节点个数减1 size--; return temp; } //查看头部 public Object showHead(){ return headNode.object; } //查看尾部 public Object showTtai(){ return tailNode.object; } //根据下标获取值 public Object getindex(int index){ if(isNull()||index>=size||index<0){ return null; } NodeTest value=null; if(index<=size/2){//将长度一分为二,根据接近的那一半来开始循环。 value=headNode; int flag=0; while(true){ //正序循环 if(flag==index){ break; } value=value.next; flag++; } }else{ value=tailNode; int flag = size-1; while(true){ //倒序循环 if(flag<=index){ break; } value=value.previa; flag--; } } return value.object; } //清空链表 public void clear(){ //将头尾指针长度初始化。 headNode=tailNode=null; size=0; } //判断链表是否为空 public boolean isNull(){ return size==0; } }
看了数组和链形的实现是不是清晰很多?举个栗子数组就像班上的同学每个人都有一个学号,知道学号就知道是那个同学了;链表还是一个班上的同学,只不过他们是手牵着手的,除了首尾两个同学之外,其他的每个同学都只知道自己上一个和下一个同学是谁。
LinkedList遍历陷阱,千万注意!
/** * 对比ArraryList、LinkedList遍历耗时 * @author orange * */ public class LinkedListTest { public static void main(String[] args) { LinkedList<String> linedLinkedListTest=new LinkedList<String>(); ArrayList<String>arrayList=new ArrayList<String>(); //添加值 for(int i=0;i<30000;i++){ linedLinkedListTest.addLast(i+""); arrayList.add(i+""); } //ArrayList(for循环) String arrayTemp=""; long arrayTime1 = System.currentTimeMillis(); for(int i=0;i<arrayList.size();i++){ arrayTemp=arrayList.get(i); } long arrayTime2 = System.currentTimeMillis(); //linkedList(for循环) String linkedTemp1=""; long linkedTime1 = System.currentTimeMillis(); for(int i=0;i<linedLinkedListTest.size();i++){ linkedTemp1=linedLinkedListTest.get(i); } long linkedTime2 = System.currentTimeMillis(); //linkedList(迭代器) String linkedTemp2=""; long linkedTime3 = System.currentTimeMillis(); Iterator<String> it = linedLinkedListTest.iterator(); while(it.hasNext()){ linkedTemp2=it.next(); } long linkedTime4 = System.currentTimeMillis(); //打印遍历耗时 System.out.println("arrayList(for循环):"+(arrayTime2-arrayTime1)); System.out.println("LinkedList(for循环):"+(linkedTime2-linkedTime1)); System.out.println("LinkedList(迭代器):"+(linkedTime4-linkedTime3)); } }
遍历耗时如下:
arrayList(for循环):7
LinkedList(for循环):6239
LinkedList(迭代器):4
我们发现LinkedList(for循环)遍历方式简直弱爆了,我们来一探究竟。
Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { //得到index是在上半部分还是下半部分 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
通过源码我们知道,其实每次调用get()方法就是一次遍历,虽然if (index < (size >> 1))做了优化,但是实在是无能为力了,一旦linkedList元素过多采用这种方式就完蛋了。
正确的遍历方式应该采用迭代器或者foreach()的方式,foreach()底层也是迭代器,就是个语法糖而已。
vector比较重要的知识
vector特殊的属性和方法
//用于数组扩容的增量值 protected int capacityIncrement; //数组扩容方法 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //如果增量值在创建的时候设置了,就用增量值,反之就是2倍的扩容。 int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } //获取超级数组的长度。 public synchronized int capacity() { return elementData.length; }
Set比较重要的知识
hash是什么,Set是什么,它们之间有什么关系?
hash通俗来讲即是任意长度的消息经过hash函数得到一个长度固定的消息摘要,而这个消息摘要理论上是不会重复的(实际也会发生碰撞,即两个不同的消息经过hash函数得到一样的消息摘要),这个消息摘要可以标识这个消息就像数据表的主键一样。
set接口有两个最终实现类hashSet和treeSet,hashSet无序不重复的集合、treeSet有序不重复的集合。
两个最终实现类的底层几乎全是调用map的方法,可以理解为实现类的底层是map的底层,而map底层中最重要的就是hash,由于最终类主要调用map的方法,set就简单研究而map再去深入研究。
HashSet如何区别重复
重写对象的hashCode()和equals()方法package ABC; import java.util.HashSet; /** * hashSet 去重复 * @author orange * */ public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Student() { } public Student(String name) { this.name = name; } //重写object类的hashCode()方法,这里方法返回固定的值,这样就只需要比较equals()方法就好了。 @Override public int hashCode() { //return super.hashCode(); return 9527; } //由于hashCode()方法返回固定值,区别就由equals()方法来做,这里是名字相同就认为是相同的对象。 @Override public boolean equals(Object obj) { return this.name.equals(((Student)obj).name); } @Override public String toString() { return "Student [name=" + name + "]"; } public static void main(String[] args) { Student student1=new Student("abc1"); Student student2=new Student("abc1"); Student student3=new Student("abc3"); HashSet<Student>set=new HashSet<Student>(); set.add(student1); set.add(student2); set.add(student3); //最后只会有abc1和abc3,名字重复的无法插入。 for (Student student : set) { System.out.println(student); } } }
TreeSet如何区别大小
第一种方法:实现Comparable接口package ABC; import java.util.TreeSet; /** * treeSet 排序(Comparable) * @author orange * */ public class Person implements Comparable{ public String name; public int age; @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public Person( String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //Comparable接口实现 @Override public int compareTo(Object o) { if(this.age>((Person)o).age){ return 1; }else if (this.age<((Person)o).age) { return -1; } //返回0 的话表示相等! return 0; } public static void main(String[] args) { Person p1=new Person("testName1", 2333); Person p2=new Person("testName2", 666); Person p3=new Person("testName3", 9527); Person p4=new Person("testName4", 4399); //treeSet排序 TreeSet treeSet=new TreeSet( ); treeSet.add(p1); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); for (Object person : treeSet) { System.out.println(person); } //集合排序 List<Person>list=new ArrayList<Person>(); list.add(p1); list.add(p2); list.add(p3); list.add(p4); //Collections里面有个排序的方法,实现了Comparable接口就可以进行排序 Collections.sort(list); //数组排序 Person[]persons=new Person[4]; persons[0]=p1; persons[1]=p2; persons[2]=p3; persons[3]=p4; //Arrays里面有个排序的方法,实现了Comparable接口就可以进行排序 Arrays.sort(persons); } }
第二种方法:实现Comparator接口
package ABC; import java.util.Comparator; import java.util.TreeSet; /** * treeSet 排序(Comparator) * @author orange * */ public class Person2{ public String name; public int age; @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public Person2( String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //Comparator接口实现,其实该接口没必要在类中实现由外部实现最好,可以不用对该类进行任何修改也可以排序。 /* @Override public int compare(Object o1, Object o2) { Person s1=(Person) o1; Person s2=(Person) o2; if (s1.age>s2.age) { return 1; }else if (s1.age<s2.age) { return -1; } return 0; }*/ public static void main(String[] args) { //在创建TreeSet的时候,实现Comparator接口,这样可以不用关心Person2类里面具体的类容。 TreeSet treeSet=new TreeSet(new Comparator<Person2>() { @Override public int compare(Person2 o1, Person2 o2) { //模仿源码写个三目运算,看起来是不是清爽很多? return o1.age>o2.age ? 1 : o1.age==o2.age ? 0 : -1; } }); Person2 p1=new Person2("testName1", 2333); Person2 p2=new Person2("testName2", 666); Person2 p3=new Person2("testName3", 9527); Person2 p4=new Person2("testName4", 4399); treeSet.add(p1); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); for (Object person : treeSet) { System.out.println(person); } } }
Comparable和Comparator的区别
1、Comparable接口必须在对象里面实现,可以使用Arrays和Collections的sort()方法进行排序;Comparator接口可以在类的外部实现,对类0入侵的同时实现排序。2、如过要对String类进行排序,我们试图改写规则要怎么办String这个类我们没办法修改,这时候用Comparator()接口实现我们自己的排序就非常棒。
3、其实用什么排序看具体的情况而定,千万不要生搬硬套。
相关文章推荐
- jdk8环境变量 jdk8图解安装 java8安装
- Java作业(六)
- java多线程(二)解决共享资源竞争
- spring integration JMS-基于ActiveMQ实现
- SCRUM 流程的步骤2: Spring 计划
- java多线程-常用方法
- 4种方法让SpringMVC接收多个对象
- 《JAVA继承与多态》【Person、Student、Employee类】(注:此题在书上原题基础上有修改)设计一个名为Person的类和它的两个名为Student和Employee子类。
- This在java中的运用
- 解决spring-security-oauth2 导入包冲突
- Java SE语法——数组
- Java并发编程总结1——线程状态、synchronized
- hibernate 级联验证-@Valid (JDK1.7)
- 2016第20周四java基础概念
- 25个Java机器学习工具&库
- eclipse/intellij idea 远程调试hadoop 2.6.0
- 添加LogCat 至 eclipse --2
- Java Web中表单数据的获取
- java学习笔记(十七)
- 大话设计模式java版--观察者模式