07容器

mac2024-01-23  41

容器 从上图要记住,set和list都继承自Collection,所以set和list的方法基本一致。而map则又是一个单独的接口;

数组

数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。

数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:我们在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?我们在写程序时是无法确定的。因此,在这里就不能使用数组。

范型 如果一个餐厅的大厨把羊肉,鱼肉都放在一个容器里面,那么取的时候就很麻烦 把鱼肉和羊肉分开放在一个容器里面,取的时候就很方便

范型就是一个数据类型(可以拿基本数据类型做比较,但是范型不仅支持基本数据类型,还支持引用型)

public class testString { public static void main(String[] args) throws ParseException, IOException { MyCollection<String> mc = new MyCollection(); mc.set("佘野无敌大帅哥",0); System.out.println(mc.get(1)); } } class MyCollection<E>{ Object[] objs = new Object[5]; public void set(E e,int index){ objs[index] = e; } public E get(int index){ return (E)objs[index]; } }

List

List是有序、可重复的容器 有序:List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。 可重复:List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。

List接口常用的实现类有3个:ArrayList(数组)、LinkedList(链表)和Vector(数组----线程安全)

(1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。

(2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。

ArrayList特点和底层实现

ArrayList底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。我们一般使用它

我们知道,数组长度是有限的,而ArrayList是可以存放任意数量的对象,长度不受限制,那么它是怎么实现的呢?本质上就是通过定义新的更大的数组,将旧数组中的内容拷贝到新数组,来实现扩容。 ArrayList的Object数组初始化长度为10,如果我们存储满了这个数组,需要存储第11个对象,就会定义新的长度更大的数组,并将原数组内容和新的元素一起加入到新数组中,源码如下:

自写ArrayList

/** * @author Sheye * @date 2019-10-30 21:25 * 1自定义范型 * 2数组扩容 * 3增加set和get方法 * 4数组边界的检查 * 5增加remove方法 */ public class SheyeArrayList<E> { private Object[] elementData; private int size; private static final int DFAULT_CAPACITY = 10; public SheyeArrayList(){ elementData=new Object[DFAULT_CAPACITY]; } public SheyeArrayList(int capcity){ if (capcity<=0){ throw new RuntimeException("capacity isn't legitimate:"+capcity); } elementData=new Object[capcity]; } //list的长度 public int size(){ return size; } //list是否为空 public boolean isEmpty(){ return size==0?true:false; } //添加元素 public void add(E obj){ //什么时候扩容 if (size==elementData.length){ //扩容操作,>>1为/2,<<为乘2 Object[] newArray = new Object[elementData.length+(elementData.length>>1)]; System.arraycopy(elementData,0,newArray,0,elementData.length); //newArray指向elementData,旧数组被回收 elementData=newArray; } elementData[size++]=obj; } //取值 public E get(int index){ rangeCheck(index); return (E)elementData[index]; } //设置元素 public void set(int index,E element){ rangeCheck(index); elementData[index]=element; } //移除元素 public void remove(E element){ //element.将他所有的元素进行比较,删除找到的第一个元素 for (int i = 0; i <size ; i++) { //get为上面的get方法 if (element.equals(get(i))){ remove(i); } } } public void remove(int index){ //abcdefgh //abcefgh int numMoved = elementData.length-index-1; if (numMoved>0){ System.arraycopy(elementData,index+1,elementData,index,numMoved); //elementData[size-1]=null; //size--; //这里如果没有以上两步,删除的结果将会是abcefghh,上两行和下两行代码出现重复,统一用elementData[--size]=null表示 } //elementData[size-1]=null; //size--; elementData[--size]=null; } //判断是否超出索引 public void rangeCheck(int index){ //判断索引是否合法 if (index<0||index>size-1){ //不合法 throw new RuntimeException("index isn't legitimate:"+index); } } public String toString(){ StringBuilder sb = new StringBuilder(); sb.append("["); for (int i = 0; i <size ; i++) { sb.append(elementData[i]+","); } sb.setCharAt(sb.length()-1,']'); return sb.toString(); } public static void main(String[] args) { SheyeArrayList s1 = new SheyeArrayList(); for (int i = 0; i <20 ; i++) { s1.add("sheye" + i); } s1.set(10,"ddd"); System.out.println("s1 = " + s1); System.out.println("s1.get(10) = " + s1.get(10)); s1.remove("sheye3"); System.out.println("s1 = " + s1); System.out.println("s1.size = " + s1.size); System.out.println("s1.isEmpty() = " + s1.isEmpty()); } }

运行结果:

s1 = [sheye0,sheye1,sheye2,sheye3,sheye4,sheye5,sheye6,sheye7,sheye8,sheye9,ddd,sheye11,sheye12,sheye13,sheye14,sheye15,sheye16,sheye17,sheye18,sheye19] s1.get(10) = ddd s1 = [sheye0,sheye1,sheye2,sheye4,sheye5,sheye6,sheye7,sheye8,sheye9,ddd,sheye11,sheye12,sheye13,sheye14,sheye15,sheye16,sheye17,sheye18,sheye19] s1.size = 19 s1.isEmpty() = false

LinkedList特点和底层实现

LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。

自写LinkedList

Node类:(本质跟c语言中的结构体(Struct)一样,c中建立链表也是定义如下结构体)

/** * @author Sheye * @date 2019-11-07 21:25 */ public class Node { Node previous; //上一个结点 Node next; //下一个结点 Object element; //元素数据 public Node(Node previous, Node next, Object element) { this.previous = previous; this.next = next; this.element = element; } public Node(Object element) { this.element = element; } }

LinkedList类:

/** * @author Sheye * @date 2019-11-07 21:33 * 1自定义一个链表 * 2添加一个add方法 * 3添加一个get方法 * 4添加一个remove方法 * 5插入结点 * 6增加范型 * 7添加size * 8添加是否为空 */ public class LinkedList<E> { private Node first; private Node last; private int size; //给出index找出当前结点 public Node getNode(int index){ //Node tmp = first; Node tmp = null; //分割成两端小于mid的从头开始,大于mid的从尾开始,二分搜索应该效率更高 if (index<=(size>>1)){ //size/2 tmp=first; for (int i = 0; i <index ; i++) { tmp=tmp.next; } } else { tmp=last; for (int i = size-1; i > index; i--) { tmp=tmp.previous; //循环链表第一个的前一个就是尾端(刚开始是这么理解,后来发现add方法中其实定义的是单链表) } } return tmp; } //判断索引是否越界 public void rangeCheck(int index){ if (index<0||index>size-1){ throw new RuntimeException("Index out of bounds:"+index); } } //list的长度 public int size(){ return size; } //list是否为空 public boolean isEmpty(){ //如果第一个结点为空,那么此链表为空 return first==null?true:false; } //插入 public void insert(int index,E element){ rangeCheck(index); Node newNode= new Node(element); Node tmp = getNode(index); if (tmp!=null){ Node up = tmp.previous; up.next=newNode; newNode.previous=up; newNode.next=tmp; tmp.previous=newNode; size++; //原视频中是没有此句话的--->如果不加,调用size方法时不会显示正确值 } } //[] //[a,b,c,e,f]--->取出第二个就是first.next.next //取出元素 public E get(int index){ rangeCheck(index); Node tmp =getNode(index); return tmp!=null?(E)tmp.element:null; //以防发生空指针 } public void remove(int index){ rangeCheck(index); Node tmp = getNode(index); if (tmp!=null){ Node up = tmp.previous; Node down = tmp.next; if (up!=null){ up.next=down; } if (down!=null){ down.previous=up; } if (index==0){ //如果是第一个,将第一个的下一个作为第一个,等于删除原来的最后一个结点 first=down; } if (index==size-1){ //如果是最后一个,将最后一个的上一个作为最后一个,等于删除原来的最后一个结点 last=down; } size--; } } //添加元素 public void add(E element){ Node node = new Node(element); //如果第一个结点为空,第一次添加数据 if (first==null){ node.previous=null; node.next=null; first=node; last=node; }else{ //[a,b]中添加c--->[a,b,c],当前结点c的上一个是原来[a,b]的最后一个,c的下一个为空 node.previous=last; node.next=null; //将原本[a,b]中的last--->b的next指向当前结点c.c成为最后一个结点 last.next=node; last=node; } size++; } public String toString(){ StringBuffer sb = new StringBuffer("["); Node tmp=first; while (tmp!=null){ //System.out.println("tmp.element = " + tmp.element); sb.append(tmp.element+","); tmp=tmp.next; } //sb.append("]");--->[a,b,c,] sb.setCharAt(sb.length()-1,']'); return sb.toString(); //"aa"; } public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); /** * toString方法的执行顺序,先执行方法里面的System.out.println * 再执行System.out.println("bb"+list)--->()里面的""内容 * 在最后执行return里面的语句 */ System.out.println(list); //如果无法理解类中有本类对象可以debug此行 System.out.println("list.get(4) = " + list.get(4)); list.remove(0); System.out.println(list); list.remove(3); System.out.println(list); list.insert(1,"嘿嘿嘿"); System.out.println(list); System.out.println(list.get(1)); System.out.println("list.size = " + list.size()); System.out.println("list.isEmpty = " + list.isEmpty()); } }

运行结果:

[a,b,c,d,e] list.get(4) = e [b,c,d,e] [b,c,d] [b,嘿嘿嘿,c,d] 嘿嘿嘿 list.size = 4 list.size = false

vector:

Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。

vector的每一个方法基本上都加上了锁,多线程访问同一资源的时候,为防止资源数据的紊乱(比较像读者与写者问题,当读者进入一间教室,那么会在教室门口贴上读者禁止入内,当读者全部离开教室,写者才能进入,并在门口贴上读者禁止入内----防止数据紊乱),而有锁的线程才能对数据进行访问。

Map接口:

现实生活中,我们经常需要成对存储某些信息。比如,我们使用的微信,一个手机号只能对应一个微信账户。这就是一种成对存储的关系。

Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复。 Map 接口的实现类有HashMap、TreeMap、HashTable、Properties等。 import java.util.HashMap; import java.util.Map; /** * @author Sheye * @date 2019-11-08 20:11 */ public class map { public static void main(String[] args) { Map<Integer,String> m = new HashMap<>(); m.put(1,"one"); m.put(2,"two"); m.put(3,"three"); System.out.println("m = " + m); //map中键不能重复!如果重复(是否重复是根据equals方法来判断),则新的覆盖旧的 m.put(2,"二"); System.out.println("m = " + m); } }

运行结果:

m = {1=one, 2=two, 3=three} m = {1=one, 2=二, 3=three}

HashMap:

HashMap底层实现采用了哈希表,这是一种非常重要的数据结构。对于我们以后理解很多技术都非常有帮助(比如:redis数据库的核心技术和HashMap一样),因此,非常有必要让大家理解。

数据结构中由数组和链表来实现对数据的存储,他们各有特点。 (1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。 (2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。 那么,我们能不能结合数组和链表的优点(即查询快,增删效率也高)呢? 答案就是“哈希表”。 哈希表的本质就是“数组+链表”。

一个Entry对象存储了:

1. key:键对象 value:值对象 2. next:下一个节点 3. hash: 键对象的hash值 显然每一个Entry对象就是一个单向链表结构,我们使用图形表示一个Entry对象的典型示意:

Entry[]数组的结构(这也是HashMap的结构):

HashMap如何存储数据。此处的核心是如何产生hash值,该值用来对应数组的存储位置。 假设m1为put(1,“aa”),m2为put(2,“bb”),计算m1的key对象的hash值(不同于hashcode)为15,则放入table数组(上图中的entry[]为table数组中存有entry对象—>形成的单链表)中下标为15的位置,计算m2的key对象的hash值(不同于hashcode)也为15的话,就以单链表的形式在m1后面

散列算法将hashcode计算出hash值,将entry散列到table数组中,使单链表尽量分配得均匀.

(1) 获得key对象的hashcode 首先调用key对象的hashcode()方法,获得hashcode。 (2) 根据hashcode计算出hash值(要求在[0, 数组长度-1]区间) hashcode是一个整数,我们需要将它转化成[0, 数组长度-1]的范围。我们要求转化后的hash值尽量均匀地分布在[0,数组长度-1]这个区间,减少“hash冲突” i. 一种极端简单和低下的算法是: hash值 = hashcode/hashcode; 也就是说,hash值总是1。意味着,键值对对象都会存储到数组索引1位置,这样就形成一个非常长的链表。相当于每存储一个对象都会发生“hash冲突”,HashMap也退化成了一个“链表”。 ii. 一种简单和常用的算法是(相除取余算法): hash值 = hashcode%数组长度 这种算法可以让hash值均匀的分布在[0,数组长度-1]的区间。 早期的HashTable就是采用这种算法。但是,这种算法由于使用了“除法”,效率低下。JDK后来改进了算法。首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值 = hashcode&(数组长度-1)。 (3) 生成Entry对象 如上所述,一个Entry对象包含4部分:key对象、value对象、hash值、指向下一个Entry对象的引用。我们现在算出了hash值。下一个Entry对象的引用为null。 (4) 将Entry对象放到table数组中 如果本Entry对象对应的数组索引位置还没有放Entry对象,则直接将Entry对象存储进数组;如果对应索引位置已经有Entry对象,则将已有Entry对象的next指向本Entry对象,形成链表。

总结如上过程:

当添加一个元素(key-value)时,首先计算key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,就形成了链表,同一个链表上的Hash值是相同的,所以说数组存放的是链表。 JDK8中,当链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

▪ 取数据过程get(key)

我们需要通过key对象获得“键值对”对象,进而返回value对象。明白了存储数据过程,取数据就比较简单了,参见以下步骤: (1) 获得key的hashcode,通过hash()散列算法得到hash值,进而定位到数组的位置。 (2) 在链表上挨个比较key对象。 调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。 (3) 返回equals()为true的节点对象的value对象。 明白了存取数据的过程,我们再来看一下hashcode()和equals方法的关系: Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。因为如果equals()为true而两个对象的hashcode不同;那在整个存储过程中就发生了悖论。

▪ 扩容问题

HashMap的位桶数组,初始大小为16。实际使用时,显然大小是可变的。如果位桶数组中的元素达到(0.75*数组 length), 就重新调整数组大小变为原来2倍大小。 扩容很耗时。扩容的本质是定义新的更大的数组,并将旧数组内容挨个拷贝到新数组中。

▪ JDK8将链表在大于8情况下变为红黑二叉树

JDK8中,HashMap在存储一个元素时,当对应链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

手工实现HashMap:

Entry类:

/** * @author Sheye * @date 2019-11-08 22:59 */ public class Entry<K,V> { int hash; K key; V value; Entry next; }

MyMap类:

import java.util.HashMap; import java.util.Map; /** * @author Sheye * @date 2019-11-08 20:11 * 1实现put方法 * 2增加toString方法 * 3增加get方法 */ public class MyMap<K,V> { Entry[] table; //位桶数组,bucket Array int size; //存放键值对的个数 int length; //记录table数组被占的个数,当等于table.length*0.75,进行扩容 private final static int DEFAULT_SIZE=16; public MyMap() { table = new Entry[DEFAULT_SIZE]; //长度一般定义为2的整数幂 } public V get(K key){ int hash = hashValue(key.hashCode(),table.length); V value=null; if (table[hash]!=null){ Entry tmp = table[hash]; while (tmp!=null){ if (key.equals(tmp.key)){ value=(V)tmp.value; break; }else { tmp=tmp.next; } } } return value; } //元素放入hashmap public void put(K key,V value){ //数组扩容 if (length==table.length*0.75){ Entry[] newArray = new Entry[table.length*2]; System.arraycopy(table,0,newArray,0,table.length); //newArray指向table,旧数组被回收 table=newArray; } Entry newEntry = new Entry(); newEntry.hash=hashValue(key.hashCode(),table.length); newEntry.key=key; newEntry.value=value; newEntry.next=null; Entry tmp = table[newEntry.hash]; Entry iterLast=null; //正在遍历的最后一个元素 boolean keyRepeat=false; if (tmp==null){ //此处数组元素为空,则直接将新结点放进去 table[newEntry.hash]=newEntry; length++; size++; //第一个进数组的entry }else{ //此处数组元素为空,则遍历对应链表 while (tmp!=null){ //判断key如果重复,则覆盖 if (tmp.key.equals(key)){ keyRepeat=true; tmp.value=value; //只是覆盖value,其他值保持不变 break; //如果出现重复,将退出链表的遍历,如果不退出,当前结点始终是!null。死循环 }else { //key不重复,则遍历下一个 iterLast=tmp; //最后一个 tmp=tmp.next; } } if (!keyRepeat){ //一开始没有加此行,那么break之后,iterLast是为null的,报空指针异常 iterLast.next=newEntry; //每添加一个entry之前的那个结点其实都是最后一个,将新结点赋值给最后一个结点的next,形成单链表 size++; //单链表后的entry } } } public void remove(K key){ //此处本想实现删除,但是单链表已知上一结点可以删除下一节点,知道本结点的下一结点,怎么删除本结点,此方法直接跳过即可 int hash = hashValue(key.hashCode(),table.length); Entry tmp = table[hash]; Entry removeEntry = null; if (tmp!=null){ //删除的链表的第一个 //删除的链表中的后面中的一个 while(tmp!=null){ if (tmp.key.equals(key)){ removeEntry=tmp.next; tmp.hash=removeEntry.hash; tmp.key=removeEntry.key; tmp.value=removeEntry.value; tmp=tmp.next; break; }else { tmp=tmp.next; } } } } //将hashcode计算为hash值 public int hashValue(int v,int length){ //System.out.println(" hash value----" + (v&(length-1))); //效率高 //System.out.println(" hash value----" + (v%(length-1))); //效率低 return v&(length-1); } public String toString(){ //{10:aa,20:bb} StringBuffer sb = new StringBuffer("{"); //遍历bucket数组 for (int i = 0; i <table.length ; i++) { Entry tmp = table[i]; //遍历单链表 while(tmp!=null){ sb.append(tmp.key+":"+tmp.value+","); tmp=tmp.next; } } sb.setCharAt(sb.length()-1,'}'); return sb.toString(); } public static void main(String[] args) { MyMap<Integer,String> m = new MyMap<>(); //entry为什么不加<Integer,String>的原因是,entry中没有方法,没有返回值,加与不加都一样,不加就相当于Object // for (int i = 0; i <20 ; i++) { // m.put(i,"aa"); // } m.put(1,"aa"); m.put(2,"bb"); m.put(3,"cc"); System.out.println("m = " + m); System.out.println("m.get(1)= " + m.get(3)); //m.remove(1); //System.out.println("m = " + m); //System.out.println("((Object)55).hashCode() = " + ((Integer)55).hashCode()); 整数的hashcode就等于它本身 //System.out.println("\"String\".hashCode() = " + "String".hashCode()); } }

运行结果:

m = {1:aa,2:bb,3:cc} m.get(1)= cc

TreeMap的使用和底层实现(用于排序):

TreeMap是红黑二叉树的典型实现

import java.util.HashMap; import java.util.Map; import java.util.TreeMap; /** * @author Sheye * @date 2019-11-10 12:19 */ public class TestTreeMap { public static void main(String[] args) { Map<Integer,String> hm = new HashMap<>(); hm.put(20,"aa"); hm.put(3,"bb"); hm.put(6,"cc"); //传统的hashmap乱序输出 for (Integer key:hm.keySet() ) { System.out.println(key+"----"+hm.get(key)); } Map<Integer,String> tm = new TreeMap<>(); tm.put(20,"aa"); tm.put(3,"bb"); tm.put(6,"cc"); System.out.println("-------------------------------------- " ); //treemap按照key递增的方式排序 for (Integer key:tm.keySet() ) { System.out.println(key+"----"+tm.get(key)); } Map<Employee,String> tm1 = new TreeMap<>(); tm1.put(new Employee(100,"张三",50000),"张三是一个好小伙"); tm1.put(new Employee(200,"李四",5000),"李四是一个好小伙"); tm1.put(new Employee(150,"王五",6000),"王五是一个好小伙"); tm1.put(new Employee(50,"赵六",6000),"赵六是一个好小伙"); System.out.println("-------------------------------------- " ); //treemap按照key递增的方式排序 for (Employee key:tm1.keySet() ) { System.out.println(key+"----"+tm1.get(key)); } } } //Employee与Employee进行比较 class Employee implements Comparable<Employee>{ int id; String name; double salary; public Employee(int id, String name, double salary) { this.id = id; this.name = name; this.salary = salary; } //按照salary递增的顺序排序,如果相等。就按照id递增的顺序排序 //如果想要进行降序,把1换成-1,-1换成1即可 /** * 将此对象与指定对象进行比较 * 我的理解:现在也不好追究底层,比如第一数为当前对象,第二个数为比较对象, * 当第一个数大于第二个数,我们设置返回值1(大于),保持不变,形成升序 * 当第一个数大于第二个数,我们设置返回值-1(小于),将大数置后,形成降序 */ @Override public int compareTo(Employee o) { //负数:小于,0:等于,整数:大于 if (this.salary>o.salary){ return 1; }else if(this.salary<o.salary){ return -1; }else { if (this.id>o.id){ return 1; }else if (this.id<o.id){ return -1; }else { return 0; } } } @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", salary=" + salary + '}'; } }

运行结果:

3----bb 20----aa 6----cc -------------------------------------- 3----bb 6----cc 20----aa -------------------------------------- Employee{id=200, name='李四', salary=5000.0}----李四是一个好小伙 Employee{id=50, name='赵六', salary=6000.0}----赵六是一个好小伙 Employee{id=150, name='王五', salary=6000.0}----王五是一个好小伙 Employee{id=100, name='张三', salary=50000.0}----张三是一个好小伙

HashTable(用法与hashmap相同):

HashMap与HashTable的区别

1. HashMap: 线程不安全,效率高。允许key或value为null。 2. HashTable: 线程安全,效率低。不允许key或value为null。

Set(接口):

Set接口继承自Collection,Set接口中没有新增方法,方法和Collection保持完全一致。我们在前面通过List学习的方法,在Set中仍然适用。

Set容器特点:无序、不可重复(因为hashset作为hashmap的key存在,所以不可重复)。无序指Set中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则不能加入;甚至,Set中也只能放入一个null元素,不能多个。

Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet

HashSet底层实现:

HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap),因此,查询效率和增删效率都比较高 我们发现里面有个map属性,这就是HashSet的核心秘密。我们再看add()方法,发现增加一个元素说白了就是在map中增加一个键值对,键对象就是这个元素,值对象是名为PRESENT的Object对象。说白了,就是“往set中加入元素,本质就是把这个元素作为key加入到了内部的map中”

手工实现hashset:

import java.util.HashMap; /** * @author Sheye * @date 2019-11-10 13:34 */ public class MyHashSet { HashMap map; private static final Object PRESENT=new Object(); public MyHashSet() { map=new HashMap(); } public void add(Object o){ map.put(o,PRESENT); } public int size(){ return map.size(); } @Override public String toString() { StringBuilder sb = new StringBuilder("["); for (Object key: map.keySet()) { sb.append(key+","); } sb.setCharAt(sb.length()-1,']'); return sb.toString(); } public static void main(String[] args) { MyHashSet mhs = new MyHashSet(); mhs.add(1); mhs.add(2); mhs.add(3); System.out.println("mhs = " + mhs); } }

运行结果:

mhs = [1,2,3]

TreeSet的使用和底层实现:

TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。 TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable接口。这样,才能根据compareTo()方法比较对象之间的大小,才能进行内部排序。

import java.util.Set; import java.util.TreeSet; /** * @author Sheye * @date 2019-11-10 14:44 */ public class TestTreeSet { public static void main(String[] args) { Set<Integer> ts = new TreeSet<>(); ts.add(300); ts.add(200); ts.add(500); //递增的顺序 for (Integer key: ts) { System.out.println("key = " + key); } System.out.println("------------------------------------------------------" ); Set<Emp> ts1 = new TreeSet<>(); ts1.add(new Emp(3,"张三",3000)); ts1.add(new Emp(5,"李四",1000)); ts1.add(new Emp(1,"王五",4000)); ts1.add(new Emp(4,"赵六",2000)); //递增的顺序 for (Emp key: ts1) { System.out.println("key = " + key); } } } class Emp implements Comparable<Emp>{ int id; String name; double salary; public Emp(int id, String name, double salary) { this.id = id; this.name = name; this.salary = salary; } @Override public int compareTo(Emp o) { if (this.salary>o.salary){ return 1; }else if(this.salary<o.salary){ return -1; }else { if (this.id>o.id){ return 1; }else if (this.id<o.id){ return -1; }else { return 0; } } } @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", salary=" + salary + '}'; } }

运行结果:

key = 200 key = 300 key = 500 ------------------------------------------------------ key = Employee{id=5, name='李四', salary=1000.0} key = Employee{id=4, name='赵六', salary=2000.0} key = Employee{id=3, name='张三', salary=3000.0} key = Employee{id=1, name='王五', salary=4000.0}

使用Iterator迭代器遍历容器元素(List/Set/Map):

import java.util.*; /** * @author Sheye * @date 2019-11-10 14:58 */ public class TestIterator { public static void main(String[] args) { testIteratorList(); System.out.println("-----------------------------------------我是一条分界线----------------------------------------- " ); testIteratorSet(); System.out.println("-----------------------------------------我是一条分界线----------------------------------------- " ); testIteratorMap(); } public static void testIteratorList(){ List<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); //创建一个调用list本身的方法创建一个迭代器对象 for (Iterator<String> iter = list.iterator();iter.hasNext();){ String tmp = iter.next(); System.out.println("tmp = " + tmp); } } public static void testIteratorSet(){ Set<String> set = new HashSet<>(); set.add("aa"); set.add("bb"); set.add("cc"); //创建一个调用set本身的方法创建一个迭代器对象 for (Iterator<String> iter = set.iterator();iter.hasNext();){ String tmp = iter.next(); System.out.println("tmp = " + tmp); } } public static void testIteratorMap(){ Map<Integer,String> map = new HashMap<>(); map.put(1,"aa"); map.put(2,"bb"); map.put(3,"cc"); //第一种方法通过entry结点进行迭代 Set<Entry<Integer,String>> ss = map.entrySet(); for (Iterator<Entry<Integer,String>> iter = ss.iterator(); iter.hasNext();){ Entry<Integer,String> e = iter.next(); System.out.println(e.getKey+ "--" + e.getValue); //运行时总是在报错,故没有结果 } System.out.println("-----------------------------------------我是一条分界线----------------------------------------- " ); //第二种方法,通过key进行迭代 Set<Integer> ss1 = map.keySet(); for (Iterator<Integer> iter = ss1.iterator();iter.hasNext();){ int key = iter.next(); System.out.println(key+"---------"+map.get(key) ); } } }

运行结果:

tmp = aa tmp = bb tmp = cc -----------------------------------------我是一条分界线----------------------------------------- tmp = aa tmp = bb tmp = cc -----------------------------------------我是一条分界线----------------------------------------- -----------------------------------------我是一条分界线----------------------------------------- 1---------aa 2---------bb 3---------cc

foreach的迭代就不多简绍

Collections工具类:

类 java.util.Collections 提供了对Set、List、Map进行排序、填充、查找元素的辅助方法。

1. void sort(List) //对List容器内的元素排序,排序的规则是按照升序进行排序。 2. void shuffle(List) //对List容器内的元素进行随机排列。 3. void reverse(List) //对List容器内的元素进行逆续排列 。 4. void fill(List, Object) //用一个特定的对象重写整个List容器。 5. int binarySearch(List, Object)//对于顺序的List容器,采用折半查找的方法查找特定对象。
最新回复(0)