常用数据结构

链表:你可以想象成自行车链子 一个拴着一个(data数据域和指针next组成),单向链表指针永远指向下一个,最后一个指针是null。双向链表,则有头和尾,循环链表尾指向头。



创建一个单链表的节点类

/*** 单向链表的节点*/
public class Node {private Integer data; //节点中的数据private Node next; //储存的下一个节点(每个节点都保存了本节点的data和指向下一个节点的地址)public Node(Integer data) {this.data = data;}public Integer getData() {return data;}public void setData(Integer data) {this.data = data;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}
}

方法1:添加数据到链尾

public class SingleLinkedList {private Node head; // 创建单链表的头节点,因为链表是从头节点开始遍历,找到头节点才能顺着链子向后面遍历private int size;//表示单链表的长度public SingleLinkedList() {}public Node getHead() {return head;}public int getSize() {return size;}//向但链表的链尾添加数据public boolean add (Integer data){//创建一个你想要添加数据的节点,头空赋给头,不是空的赋给尾Node tmp = new Node(data);//判断头本身是否空(并不调用.next 不是判断下一个为空)if (head==null)head = tmp;//若头不是空else {//创建临时变量,把头赋给临时指针 因为需要head移动遍历Node t = head;//从头开始只要后面不为空,就一直往后走while (t.getNext()!=null){//只要后面的不为空,就把后面的值赋给t变量,新的t继续执行循环t = t.getNext();}//此时跳出循环,证明后一个为空,把你要赋值的节点给t.next(此时为空)t.setNext(tmp);}//执行完毕,链表长度+1size++;return true;}
}
public class SingleLinkedListTest {public static void main(String[] args) {//测试添加数据到队尾的add方法SingleLinkedList list = new SingleLinkedList();//自动装箱成Integer传入list.add(1);list.add(2);list.add(3);}
}

通过debug查看,发现每个next下都存在data和下一个next

2.指定位置添加数据

添加数据到下标位置,比如添加5到index2,为了不使原数据丢失,需要使包含data5的节点指向3的节点,然后断开2指向3的链子,使2指向5,这样指定下标的数据添加就完成了。

/*** 添加数据到下标位置** @param data  数据* @param index 下标* @return*/public boolean add(Integer data, int index) {//创建一个你想要添加数据的节点Node tmp = new Node(data);//你要添加的下标大于等于数组长度if (index >= size) {throw new RuntimeException("越界异常!IndexOutOfBoundException,index:" + index + ",size" + size);}//你要在链表头插数据else if (index == 0) {//你的节点指向头tmp.setNext(head);//把你的节点赋值给头head = tmp;}//在头以后插数据else {//创建链表头的临时变量方便进行遍历Node t = head;for (int i = 0; i < index - 1; i++) {//每次循环都把下一个节点赋值给这个变量t = t.getNext();}//让你要存的节点指向下一个节点tmp.setNext(t.getNext());//让你存入节点的上一个节点指向你存入节点t.setNext(tmp);}size++;return true;}/*** 遍历链表的方法*/public void print() {Node temp = head;while (temp != null) {System.out.println(temp.getData());temp = temp.getNext();}}


3.删除最后一个数据

当要删除最后一个节点时只要把前一个连接的链子断掉,就是删除了。

 /*** 删除最后一个节点:指向最后一个节点的链子断掉* @return*/public Integer  remove(){//如果头节点为空if (head == null){throw new RuntimeException("链表为空,不能删除");//如果头节点的下一个为空,也就是只有头节点一个}if (head.getNext()==null){//获取头节点的dataInteger data = head.getData();//给头节点赋空值head = null;//返回删除的数据return data;}//定义两个指针(tSlow在前tFast在后),满足条件就同时移动,// 直到fast为空,也就是说fast后面没有节点了Node tSlow =null;Node tFast =head;//只要快指针后面有节点就一直遍历while (tFast.getNext()!=null){//两个指针同时向后移动tSlow = tFast;tFast = tSlow.getNext();}//循环结束时,证明快指针后面为空,即快指针为最后一个节点//删除最后一个节点,只需要把前面和他相连的链子断了就行tSlow.setNext(null);//返回删除的数据(节点尾)return tFast.getData();}

4.删除指定下标的节点

/*** 根据下标删除节点:index前后连起来* @param index* @return*/public Integer  remove(Integer index){if (index >= size){throw new RuntimeException("越界异常!IndexOutOfBoundException,index:" + index + ",size" + size);}if (index == 0){//获取头节点的dataInteger data = head.getData();//删除后的头节点就是下一个节点了head = head.getNext();//返回删除的数据return data;}//要删除的下标不是0时,说明在头以后//定义两个指针(tSlow在前tFast在后),满足条件就同时移动,Node tSlow =null;Node tFast =head;//当循环到下标index时,最终tSlow指向你要删除的节点,tFast是index指向下个节点的指针for (int i = 0; i <index ; i++) {//两个指针向后移动tSlow = tFast;tFast =tFast.getNext();}//使指向index的指针指向index的下一个tSlow.setNext(tFast.getNext());//使index指向下个节点的指针为null,这样index的前一个和index的后一个就连起来了tFast.setNext(null);return tFast.getData();}

5.通过下标获取链表指定数据

/*** 通过下标获取链表指定数据* @param index* @return*/public Integer get(int index){if (index<0||index>=size){throw new RuntimeException("越界异常!IndexOutOfBoundException,index:" + index + ",size" + size);}Node temp = head;for (int i = 0; i <index ; i++) {temp =temp.getNext();}return temp.getData();}

二叉树:
二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥 有的子树数)。


1号位于根节点,其余叫做子节点,每个节点相当于被分成三部分,一份存放自身的数据,另两份存放左右两个节点的地址。

创建二叉树节点

/*** 二叉树的节点*/
public class TreeNode {//数据private Integer data;//左节点 默认nullprivate  TreeNode left;//右节点 默认nullprivate TreeNode right;public TreeNode(Integer data) {this.data = data;}public Integer getData() {return data;}public void setData(Integer data) {this.data = data;}public TreeNode getLeft() {return left;}public void setLeft(TreeNode left) {this.left = left;}public TreeNode getRight() {return right;}public void setRight(TreeNode right) {this.right = right;}
}

创建一颗树

  public static void main(String[] args) {//创建7个节点TreeNode root = new TreeNode(1);TreeNode node2 = new TreeNode(2);TreeNode node3 = new TreeNode(3);TreeNode node4 = new TreeNode(4);TreeNode node5 = new TreeNode(5);TreeNode node6 = new TreeNode(6);TreeNode node7 = new TreeNode(7);//这些节点构建为一棵树root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);node3.setRight(node7);}


广度优先遍历:按层遍历

 /*** 广度优先遍历:按层打印,先父后子,取出父的时候子进行排队,子取出,子的子再排队以此类推* @param root*/public static void printByLayer(TreeNode root){//根节点为空直接returnif (root==null){return;}//创建一个队列存储节点  队列特点:先进先出Queue<TreeNode> queue = new LinkedList<>();//把根节点存入队列queue.add(root);//只要队列里面有东西,就一直循环while (!queue.isEmpty()){//从队列中取出一个节点赋值给临时变量TreeNode temp = queue.poll();//弹出数据//打印这个数据System.out.println(temp.getData());//开始管理自己的子节点if (temp.getLeft()!=null)queue.add(temp.getLeft());if (temp.getRight()!=null)queue.add(temp.getRight());}}


深度优先遍历

 /*** 前序遍历:根左右** @param root*/public static void pre_print(TreeNode root) {if (root!=null){//根System.out.println(root.getData());//左if (root.getLeft()!=null)pre_print(root.getLeft());//右if (root.getRight()!=null)pre_print(root.getRight());}}/*** 中序遍历:左根右** @param root*/public static void mid_print(TreeNode root) {if (root!=null){//左if (root.getLeft()!=null)mid_print(root.getLeft());//根System.out.println(root.getData());//右if (root.getRight()!=null)mid_print(root.getRight());}}/*** 后序遍历:左右根** @param root*/public static void last_print(TreeNode root) {if (root!=null){//左if (root.getLeft()!=null)last_print(root.getLeft());//右if (root.getRight()!=null)last_print(root.getRight());//根System.out.println(root.getData());}}

二叉排序树:

/*** 二叉排序树*/
public class BinarySortTree {//定义根节点private TreeNode root;/*** 二叉排序树的添加 :根据根节点比较小于根节点放左边,大于根节点放右边* 注意点一:如果插入的值,树中已经存在,则不会继续插入* 注意点二:遍历要采用中序遍历(左根右->小中大)* @param data* @return*/public boolean add(Integer data){if (root == null){this.root = new TreeNode(data);}else {//定义当前节点TreeNode current = root;//定义相对的根节点TreeNode parentNode = null;//只要当前节点不为空就一直循环while (current!=null){//把当前节点作为根节点parentNode  = current;//传入的数据小于当前节点if (data < current.getData()){//把当前节点的左侧当作当前节点current = current.getLeft();if (current == null){parentNode.setLeft(new TreeNode(data));return true;}}//传入的数据大于当前节点else {//把当前节点的右侧当作当前节点current = current.getRight();if (current == null){parentNode.setRight(new TreeNode(data));return true;}}}}return false;}public TreeNode getRoot() {return root;}public void setRoot(TreeNode root) {this.root = root;}
}

测试及结果(采用中序遍历)

查找指定数据(get)

 /*** 查询树中有没有对应的值* @param key* @return*/public TreeNode get(Integer key){TreeNode temp = root;while (temp!=null){if (temp.getData()>key){temp = temp.getLeft();}else if(temp.getData()<key){temp = temp.getRight();}else {return temp;}}return null;}


若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树

栈:

队列:

数组:

集合和数组既然都是容器,它们有啥区别呢?

数组的长度是固定的。集合的长度是可变的

数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类
型可以不一致。在开发中一般当对象多的时候,使用集合进行存储

类集设置的目的

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。
在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。
类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
所有的类集操作的接口或类都在 java.util 包中。
Java 类集结构图:

Collection 接口

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。C ollection一共定义了15个方法(比如迭代器,size,add),但我们实际开发中并不会直接去操作Collection,而是去操作他的子接口(List和Set)。

常用方法:

。 public boolean add(E e) : 把给定的对象添加到当前集合中

。 public void clear() :清空集合中所有的元素

。 public boolean remove(E e) : 把给定的对象在当前集合中删除

。 public boolean contains(E e) : 判断当前集合中是否包含给定的对象

。 public boolean isEmpty() : 判断当前集合是否为空

。 public int size() : 返回集合中元素的个数

。 public Object[] toArray() : 把集合中的元素,存储到数组中
。没有get方法,ArrayList和Vector有

Iterator

迭代器,区别于以前的遍历是系统迭代数据结构的最优实现,每个类集都会自带一个迭代器。

List接口

List也是保存单值的借口,同时对于保存的数据规定是有序,可重复的。
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充(比如拥有自己的迭代器ListIterator)。我们平时使用List接口的时候则是针对其实现类(ArrayList,Vector,LinkedList)进行操作。

ArrayList

传统数组虽然取值比较快速,但是对于增加或者修改尤其缓慢。数组一旦创建则不可改变,如果我们添加的值超过了数组的界限,则我们需要手动扩容(非常麻烦,且耗费内存)。对于删除输出来说,删除一个数据需要后面的所有数据往前位移,如果是一百万个数据呢???
使用ArrayList则完美的解决了这个问题,相比于数组,ArrayList是可自动扩容的数组,我们无须再手动扩容。底层数据结构依然是数组结构array,所以查询速度快,增删改慢。

通过源码分析其实现,ArrayList可以通过构造方法在初始化的时候指定底层数组的大小。
通过无参构造方法的方式ArrayList()初始化,则赋值底层数Object[] elementData为一个默认空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以数组容量为0,只有真正对数据进行添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。

大家可以分别看下他的无参构造器和有参构造器,无参就是默认大小,有参会判断参数。

ArrayList 会自动判断传入长度,当数组长度不够的时候就会自动扩容
jdk1.8以后大概是这个样子

原数组容量右移一位,此操作在二进制的基础上位移动。举个例子十进制右移一位(10就 变成了1,相当于除10),二进制(正数)则是最右位去掉,最高位补0,则相当于除以二。
我们看这个公式就是 新长度 = 旧长度+0.5旧长度。 所以 ArraysList每次扩容都是之前的长度都是之前的1.5倍。

同时ArraysList是线程不安全的,Vector 是线程安全的。
ArrayList和Vector相同点与区别:

同:

1 ArrayList和Vector都是继承了相同的父类和实现了相同的接口 2 底层都是数组(Object[])实现的

3 初始默认长度都为10。

区别:

1 同步性:

Vector中的public方法多数添加了synchronized关键字、以确保方法同步、也即是Vector线程安全、ArrayList线程不安全。

2 扩容:

ArrayList以1.5倍的方式在扩容、Vector
当扩容容量增量大于0时、新数组长度为原数组长度+扩容容量增量、否则新数组长度为原数组长度的2倍

3性能:

在性能方便通常情况下ArrayList的性能更好、而Vector存在synchronized
的锁等待情况、需要等待释放锁这个过程、所以性能相对较差。

4 输出:

ArrayList支持支持 Iterator、ListIterator 输出,Vector除了支持
Iterator、ListIterator外,还有Enumeration输出

ArrayList常用方法

List接口常用方法:1、add(Object element): 向列表的尾部添加指定的元素。2、size(): 返回列表中的元素个数。3、get(int index): 返回列表中指定位置的元素,index从0开始。4、add(int index, Object element): 在列表的指定位置插入指定元素。5、set(int i, Object element): 将索引i位置元素替换为元素element并返回被替换的元素。6、clear(): 从列表中移除所有元素。7、isEmpty(): 判断列表是否包含元素,不包含元素则返回 true,否则返回false。8、contains(Object o): 如果列表包含指定的元素,则返回 true。9、remove(int index): 移除列表中指定位置的元素,并返回被删元素。10、remove(Object o): 移除集合中第一次出现的指定元素,移除成功返回true,否则返回false。11、iterator(): 返回按适当顺序在列表的元素上进行迭代的迭代器。

ArrayList一样可以使用List的所有方法

Vector

线程安全,因为公开的方法都加了锁,同时导致了性能不好。和ArraysList一样使用了数组的结构,查找快,增删慢。ArrayList有的方法他都有。

LinkedList

使用的是双向链表,增删快,查找慢。
存储的结构是链表结构 List里面的方法多有 同时还有自己特有的

。public void addFirst(E e) :将指定元素插入此列表的开头

。 public void addLast(E e) :将指定元素添加到此列表的结尾

。 public E getFirst() :返回此列表的第一个元素

。 public E getLast() :返回此列表的最后一个元素

。 public E removeFirst() :移除并返回此列表的第一个元素

。 public E removeLast() :移除并返回此列表的最后一个元素

。 public E pop() :从此列表所表示的堆栈处弹出一个元素

。 public void push(E e) :将元素推入此列表所表示的堆栈

。 public boolean isEmpty() :如果列表不包含元素,则返回true。

方法详见:https://blog.csdn.net/vjrmlio/article/details/7950887#

Iterator接口

用来遍历Collcection集合下的所有集合 List Set…

public E next() :返回迭代的下一个元素 同时指针下移。

public E previous() :返回迭代的上一个元素 同时指针上移。

public boolean hasNext() :如果仍有元素可以迭代,则返回 true

增强for

for(数据类型 变量名 : 集合 或 数组名){}

失败:遍历的时候,改变了集合,导致遍历失败
快速失败:创建迭代器后的任何时间,除了自己的remove以外修改集合,迭代器将抛出ConcuurentModificationException.
安全失败:把集合复制了一份,我们遍历的是复制的,所以怎么操作无所谓,通常使用的是安全失败。

使用迭代器遍历集合的内容:

1.通过调用集合的Iterator()方法,获取指向集合开头的迭代器。

2.建立一个hasNext()方法调用循环。只要hasNext()方法返回true,就继续迭代。

3.在循环中,通过调用next()方法获取每个元素。

package Collection;import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;public class IteratorDemo {public static void main(String[] args) {//create an Array listArrayList<String> arrayList = new ArrayList<String>();arrayList.add("q");arrayList.add("e");arrayList.add("fg");arrayList.add("iu");arrayList.add("ug");System.out.println("Original contents of arraylist: ");//创建ArrayList的迭代器Iterator<String> iterator = arrayList.iterator();//iterator.hasNext()判断指针指向的下一个是否为空,返回值是booleanwhile (iterator.hasNext()){//iterator.next()获取元素并且指针指向后移String element = iterator.next();System.out.print(element +" ");}System.out.println();//Modify objects being iteratedListIterator<String> lit = arrayList.listIterator();while (lit.hasNext()){String element = lit.next();lit.set(element + "+");}System.out.println("Modified contents of arraylist: ");iterator = arrayList.iterator();while (iterator.hasNext()){String element = iterator.next();System.out.print(element + " ");}System.out.println();//Now,display the list backwards.System.out.println("Modified list backwards: ");while (lit.hasPrevious()){String element = lit.previous();System.out.print(element + " ");}System.out.println();}
}

ListIterator



介绍一下新来的几个方法:

void hasPrevious() 判断游标前面是否有元素;
Object previous() 返回游标前面的元素,同时游标前移一位。游标前没有元素就报 java.util.NoSuchElementException的错,所以使用前最好判断一下;
int nextIndex() 返回游标后边元素的索引位置,初始为 0 ;遍历 N 个元素结束时为 N;
int previousIndex() 返回游标前面元素的位置,初始时为 -1,同时报
java.util.NoSuchElementException 错;
void add(E) 在游标 前面 插入一个元素 注意是前面
void set(E) 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。 注意,当没有迭代,也就是没有调用 next() 或者 previous() 直接调用 set 时会报错
void remove() 删除迭代器最后一次操作的元素,注意事项和
set 一样。

forEach

forEach:增强for循环,最早出现在C#中
用于迭代数组 或 集合 (Collection下的集合才行list和set)(自动寻在最优解)
for(数据类型 :变量名 :集合或者数组名){
// 变量一只在循环中改变
System.out.println(变量名)
}

Set(接口)

继承自Collection,方法几乎一致,并没有太多的改变。和Collection一样没有get方法,只有用toArray变成数组或者iterator迭代器找数据,这是为什么呢?因为Set的实现类不包含重复单值的集合,及不满足e1.equals(e2) 且 null 也最多就一个。
如果使用可变对象作为set元素则需要非常小心,比如传入一个Person,里面有属性name age 结果被改变了 ,位置也就改变。

HashSet

没有实现过多自己的方法,基本还是用Colletion的方法,没有get,只能toArray变成数组操作或者迭代器操作。内部是散列存放,基于hashmap存储。
换个方式理解,我们编写一个软件,存储数据,长度不确定,用数组不合适,选择集合,不增删只查找,我们就可以用ArrayList,我们使用,系统已经提供了动态扩容结构。 设计哈希表,hashset是单值, 之前map已经是双值存储的hash表了 ,系统怎么设计呢, 就利用了这个hash表
put 数据 一定要是两个 一个是我们传入的 一个是写死的
add方法返回的是boolean类型,基于set无序的不可重复的特点,因为存在所以添加失败为false,因为不可重复所以输出以下结果。至于如何实现无序和不可重复的呢?详见后文hashmap

TreeSet和Comparable

TreeSet内部也是用treemap实现的,Hashset无序,TreeSet有序(自然顺序,不是根据你输入的顺序,系统根据Ascall码排序)
注意:如果想根据你自定义的顺序进行排序输出,要在类型的类中实现Compareble<类型>接口,否则直接add会抛异常,如图。


类型转换异常,因为系统也不知道怎么给你排,你需要实现接口重写里面的compareTo方法。

//实现Comparable,写要比较的类型
static class Person implements Comparable<Person>{private String name;private int 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;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&name.equals(person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}@Overridepublic int compareTo(Person o) {//this 与 泛型 传入的o比较//返回的数据:负数:this小/零 一样/正数 this 大//里边写重写的方法return 0;}//   Person p1 = new Person("张三",18);
//   Person p2 = new Person("李四",19);
//   data.add(p1);
//   data.add(p2);
// p1存的时候不排序,P2存的时候,就相当于p1.comPareTo(p2)
//根据返回的数据决定谁大谁小存在不同的位置@Overridepublic int compareTo(Person o) {if(this.age>o.age){return 1;}else if (this.age == o.age){return 0;}return -1;}}

自定义排序规则后的输出(根据自身业务需要)

但是如果我们添加了一个添加了一个18岁的王二麻子会怎么样?



我们发现王二麻子并没有输出,因为set是不能重复的。

Compartor接口

https://blog.csdn.net/lx_nhs/article/details/78871295

Map

注意:List(单值),Set(单值),Map(双值) 并不在同一等级。Map和Collection是同一个级别,只不过我们很少用Collection。
Map集合存储的是一个个的 键值对 数据。

左边是像list set单值存储结构,右边是map双值结构。
左边通过迭代器方式或下标方式获取元素,右边则根据key获取value。


之前说hashset内部是hashmap实现的,treeset是用treemap实现的,这是为什么?

相当于利用了map的key(不可重复性)来存储数据。

右边的已经被写死,左边利用map的k来存储数据。

map不是地图的意思(谷歌翻译有误),是映射的意思(mapping的缩写),每个键只能映射一个值。因为键和值都不是有序的,根据我们定义的,所以我们无法通过遍历得到他们的值。我们可以把key都保存在keyset里面,再遍历keyset()得到所有的key,然后再调用get方法传入key取出value; put方法存值

如图,为什么put(k,v)的时候需要返回值呢?
因为map不允许存重复的值所以当我们put一个k一样的值但是v不一样的值的时候,系统会用新值替换旧的值,就会把旧值返回回来。如果没有替换,只是单纯的添加,那就会返回null。

remove为什么也返回v?
删除成功返回的就是删除的值,删除失败就返回null。我们有时候也用remove取出数据,如果需要取出并删除可以使用remove

size() 获取到键值对的数量

哈希表

一篇不错的文章

数组是我们平时常见的并且经常使用的一种数据结构,那么它具有什么优点呢?我们都知道,在我们知道数组中某元素的下标值时,我们可以通过下标值的方式获取到数组中对应的元素,这种获取元素的速度是非常快的。

但是呢,数组也是有一定的缺点的,如果我们不知道某个元素的下标值,而只是知道该元素在数组中,这时我们想要获取该元素就只能对数组进行线性查找,即从头开始遍历,这样的效率是非常低的,如果一个长度为10000的数组,我们需要的元素正好在第10000个,那么我们就要对数组遍历10000次,显然这是不合理的。

所以,为了解决上述数组的不足之处,引入了哈希表的概念,哈希表在很多语言的底层用到的非常的多

哈希表的优缺点

在刚才哈希表的描述中,大家一定能看出哈希表的优缺点了,这里我来给大家总结一下吧~

(1)优点

首先是哈希表的优点:

无论数据有多少,处理起来都特别的快
能够快速地进行 插入修改元素 、删除元素 、查找元素 等操作
代码简单(其实只需要把哈希函数写好,之后的代码就很简单了)
(2)缺点

然后再来讲讲哈希表的缺点:
哈希表中的数据是没有顺序的
数据不允许重复
(3)冲突

前面提到了冲突,其含义就是在哈希化以后有几个元素的下标值相同,这就叫做 冲突。 那当两个元素的下标值冲突时,是后一个元素是不是要替换掉前一个元素呢?当然不是!

那么如何解决冲突这个现象呢?一般是有两种方法,即拉链法(链地址法)开放地址法
拉链法:如果存的都在同一个位置就使用链表,一个绑着一个
开放地址法:如果存的过多就使用红黑树



原文详见

实例:

哈希表使用的是对象数组+链表,或者对象数组+红黑树
比如Hashmap默认初始长度为16的对象数组+链表结构,下标是0-15,当我们存入一个数据时,先通过hashCode()计算其hash值(返回一个int),然后对其长度(桶数量)(此时是16求余%得到一个0-15下标的数)根据该数找到对应的哈希桶(位置)。那么如果计算出来比如17%16=1 33%16=1,两个数值一样怎么办?哈希冲突:有两种解决办法,这里我们只用开放地址法。
当第二次计算33%16得出1的时候,查看2下标的位置有没有,如果有就继续以此类推。。。

Hash表、Hash函数和HashCode
jdk1.8以后针对哈希表进行了优化,当哈希桶达到8时候,转换为红黑二叉树进行存储。当哈希桶数量减少到6的时候,从红黑二叉树转换为链表。

假设我们存了一万个数据,那么这16个桶(无法确定初始值,就设置16),每一个桶都有大几百个数据,这样性能肯定会变的很慢。
散列因子:0.75 当16个桶中有百分之七十五存上数据了就对桶扩容,桶就会更多,默认长度是桶的两倍
那么下次取余就是32了 比如:322%32

HashMap源码分析

散列因子(默认0.75,也可以通过有参构造修改,官方最推荐0.75)

默认容量16,1左移四位

put方法

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

冲突:链表或红黑树解决

HashMap/Hashtable/ConcurrentHashMap

HashMap 线程不安全的,效率高

Hashtable 线程安全的,效率低

ConcurrentHashMap
采用分段锁机制,保证线程线程安全,效率又比较高

在链表内分段排队,举例(0号位置A在执行,B计算出来去1号位置执行,遇到同一个位置的在排队,没有就去对应位置执行)

散列表的几点说明

散列因子(默认0.75):太小耗费空间,但是效率高(比如0.1当有十分之一个桶装了就扩容)
太大不耗费空间,但效率太低(比如0.9但有十分之九装了才扩容,很可能一个桶已经上千条数据了)
容量(桶的个数):默认16 比如要存10000 数据,产生了大量散列 ,初始容量一定要给的合理,本身hash表就是存取效率很高,不要因为人为操作失误导致效率变慢。
存储在hashmap的key如果是自定义类型就不要修改值

使用map,尤其是hashmap 一定要支持equals 和hashcode


当我们在get方法前修改key值时,由于get方法再调用的时候会计算hash值导致计算出来的hash值和之前不一样,就去了错误的桶里面找,当然找不到,结果为null。


此时你想通过原来的key找到value发现,这是为什么?明明我的key一样啊,却找不到value。原来hashmap在get()时不仅会调用hashcode计算hash值(key),还会调用equals判断。


所以,当我们在使用map,尤其是hashmap的时候,当作为哈希表的key存储时key就不要去改变,如果需要改变的就不要放在key上

Jdk9集合类的新特性

只对List Set Map 使用,子类不行
只能创建固定长度的集合,不可改变,不可以add,remove,set不能修改

      //SetSet<String> set = Set.of("锄禾日当午","汗滴禾下土","谁知盘中餐","粒粒皆辛苦");for (String s : set) {System.out.println(s);}//ListList<Integer> list = List.of(1,2,3,4);for (Integer integer : list) {System.out.println(integer);}//MapMap<String,String> map = Map.of("1","锄禾日当午","2","汗滴禾下土","3","谁知盘中餐","4","粒粒皆辛苦");Set<String> keySet = map.keySet();for (String key: keySet) {System.out.println(key+"->"+map.get(key));}

最后再来个全家福

集合类超级无敌史无前例的超详细总结相关推荐

  1. Java小记-集合类(超级无敌认真好用,万字收藏篇!!!!)

    文章目录 集合类 1 为什么要使用集合类? 1.2数组的特点 1.3 集合类的特点 2 Java中集合类的类结构 3 基于List接口的ArrayList类的使用 3.1 ArrayList的特点 3 ...

  2. uniapp阿里矢量图多色图标的使用方法(超级无敌巨无霸详细)

    用最简单的方法参照:阿里巴巴矢量图库的具体使用用法可以变色改变大小 以上你会发现多色图标显示出来并不是彩色的!!! 第一步:老规矩 先选好自己喜欢的多色图标,下载 第二步: 创建一个文件夹 把下载的东 ...

  3. SpringBoot 自动配置原理(超级无敌详细)-2

    SpringBoot 自动配置原理(超级无敌详细)-1 2.自动配置的实现 刚刚我们整体的过了一下主配置文件是如何实现的,但我们还没深入的研究如何实现自动装配功能.我们回到这个文件下,找一个具体的自动 ...

  4. hadoop +hbase+zookeeper 伪分布安装(超级无敌详细)

    hadoop +hbase+zookeeper 伪分布安装(超级无敌详细) hadoop 配置 图片打不开的可以点击下方链接直接去图床查看,辣鸡CSDN 安装jdk sudo apt update// ...

  5. 【超级无敌详细的黑马前端笔记!即时更新~】

    [超级无敌详细的黑马前端笔记!即时更新~] 这个笔记,是我自己在同步学习黑马前端使用的,不可以商用哦 学习路径 基础概念铺垫(了解) 认识网页 五大浏览器和渲染引擎 Web标准 HTML初体验 HTM ...

  6. 从零学ELK系列(十一):SpringBoot项目接入ELK超级版(超详细图文教程)

    前言 之前在<从零学ELK系列(十):SpringBoot项目接入ELK升级版(超详细图文教程)>中演示了SpringBoot项目接入ELK请求记录及优化,本次针对于未知异常通过拦截进行记 ...

  7. 电脑技巧全书(超详细.)

    目录检索(相关文章在对应的目录下找~~)~~ 1.最全的windows操作系统快捷键-------------------------------------1楼   2.Windows 开始→运行→ ...

  8. php 自定义超全局,一个超级简单的 PHP 超全局变量管理扩展

    一个超级简单的 PHP 超全局变量管理扩展(自卖自夸) 介绍 SG 全称 Superglobals,它的诞生为了方便快捷操作 PHP 预定义的超全局变量,用户定义的超全局变量. 如果在非 CLI 模式 ...

  9. SQLMap用户手册【超详细】

    SQLMap用户手册[超详细] 文章来源:http://www.cnblogs.com/hongfei/p/3872156.html ps:直接copy http://192.168.136.131/ ...

最新文章

  1. 电子表单系列谈之表单数据处理
  2. 计算机网络·通俗理解RIP协议(距离向量算法计算)
  3. 用权值实现数据被抽取的概率
  4. flag在java怎么用_JAVA flag怎么用
  5. oracle结构设计
  6. php用switch编写车费的输出,PHP Switch语句在实际代码中的应用
  7. 话里话外:成功的ERP需要全程的流程变革(三)
  8. python的基本语法if语句_Python基础之条件控制操作示例【if语句】
  9. 【云和恩墨大讲堂】从执行计划洞察ORACLE优化器的“小聪明”
  10. 集合运算 蓝桥杯 set容器
  11. 日期插件(jedate)
  12. 计算机毕业设计 SSM+Vue音乐播放网站系统 云音乐播放系统 付费音乐播放系统Java Vue MySQL数据库 远程调试 代码讲解
  13. 用人单位不与劳动者签定书面劳动合同的后果
  14. matlab求点,MATLAB求两点中点
  15. PS保留渐变进行换色
  16. 计算机硬件相关行业,2021年中国电脑硬件行业市场规模及发展前景预测分析(图)...
  17. 配置法 求解1D第二类线性的Fredholm积分方程 +MATLAB
  18. 原装应广单片机 MCU芯片PMS152 SOP8封装 单片机开发
  19. 仓库管理系统包括哪些方面?
  20. python中种子数是什么意思_Python shuffle():其种子数的粒度/shuffle()结果多样性...

热门文章

  1. vue给url 中文参数 添加编码解码
  2. 英语美文阅读:当爱向你们挥手
  3. 计算机科学与工程6,2019上海软科世界一流学科排名计算机科学与工程专业排名哈佛大学排名第6...
  4. “财界奥斯卡”CGMA全球管理会计2021年度中国大奖榜单揭晓
  5. 爱否赢了?华为拍月亮方法已申请专利
  6. android热补丁原理完bb霜,answered
  7. 小学数学计算机教案模板,小学数学信息化教学设计模板.doc
  8. 算法学习之Trie树
  9. 目标检测-VOC数据集txt文件制作方法
  10. 岩土数值分析对学计算机有用吗,岩土数值分析1、2、3章2013.ppt