HashMap存取效率高缘由、ArrayList和LinkedList区别、JAVA实现链表的基本功能

1、HashMap存取效率高缘由

一、Hash

也叫散列、哈希。html

主要用于信息安全领域中的算法,把长度不一样的信息转化为杂乱的128位的编码,找到一种数据内容与地址之间的映射关系。java

注意:不一样的输入可能会散列成相同的输出node

咱们最熟悉的Object类中就提供了hashcode的方法。算法

public native int hashCode();

 

二、数据结构

Java集合的实现底层大都是基本数据结构的又一层封装。数组

数组:寻址容易,插入和删除困难安全

链表正好相反。数据结构

HashMap正好将两者互补了一下,推出了链表+数组的组合方式,也叫链表散列、“拉链法”。性能

结构示意图:测试

这里写图片描述

放入元素时,根据key值经过hashcode找到对应数组的位置,放入横向数组的某个格子中。由于前面说到hashcode值不能保证惟一,若是以后hashcode值对应的数组位置中已经有值,就放到相连的链表中。优化

查找元素也是按这个过程来进行。

代码实现:

注意:每一个Node中都持有下一个节点的引用。 
这里写图片描述 

三、算法优化

由上面的数据结构介绍,能够看出,在查找的时候,尽可能避免查找链表可以大大提升存取效率。

目标:元素尽量均匀分布,这样查找的时候没必要查找链表,效率很高。

思路一:

取模运算,实现是能够实现,但取模运算消耗大、效率不高。

思路二:

首先,&运算比取模运算效率高。 
hashmap采用的是下面这种与运算。

这里写图片描述

大同小异,都是为了减小碰撞,避免hash到同一个位置,使元素分布更均匀。在实现的基础上,考虑性能问题。

2、Java中ArrayList和LinkedList区别

ArrayList和LinkedList的大体区别以下:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
2.对于随机访问get和set,ArrayList以为优于LinkedList,由于LinkedList要移动指针。 
3.对于新增和删除操做add和remove,LinedList比较占优点,由于ArrayList要移动数据。 

上代码:

static final int N=50000;
    static long timeList(List list){
        long start=System.currentTimeMillis();
        Object o = new Object();
        for(int i=0;i<N;i++) {
            list.add(0, o);
        }
        return System.currentTimeMillis()-start;
    }
    static long readList(List list){
        long start=System.currentTimeMillis();
        for(int i=0,j=list.size();i<j;i++){

        }
        return System.currentTimeMillis()-start;
    }

    static List addList(List list){
        Object o = new Object();
        for(int i=0;i<N;i++) {
            list.add(0, o);
        }
        return list;
    }
    public static void main(String[] args) {
        System.out.println("ArrayList添加"+N+"条耗时:"+timeList(new ArrayList()));
        System.out.println("LinkedList添加"+N+"条耗时:"+timeList(new LinkedList()));

        List list1=addList(new ArrayList<>());
        List list2=addList(new LinkedList<>());
        System.out.println("ArrayList查找"+N+"条耗时:"+readList(list1));
        System.out.println("LinkedList查找"+N+"条耗时:"+timeList(list2));
    }

当咱们在集合中装5万条数据,测试运行结果以下:

显然咱们能够看出ArrayList更适合读取数据,linkedList更多的时候添加或删除数据。

 

ArrayList内部是使用可増长数组实现的,因此是用get和set方法是花费常数时间的,可是若是插入元素和删除元素,除非插入和删除的位置都在表末尾,不然代码开销会很大,由于里面须要数组的移动。
LinkedList是使用双链表实现的,因此get会很是消耗资源,除非位置离头部很近。可是插入和删除元素花费常数时间。

3、关于JAVA实现链表的基本功能

链表结构,一般包含表头,节点1,节点2...节点n,其中节点又包含了数据内容和下个节点的地址。和数组结构(应该叫作顺序表吧大概......)不同,链表并不用占据连续的内存,它们的区别就很少说了,相信你们都知道。  

  说说怎么实现吧,既然要用引用的方式来代替指针,那么就须要一个特别的类结构:须要同名的成员保存下一个节点的信息。

public class Node {

      private String data;
      private Node nextNode;

     public String getData() {
          return data;
      }

      public void setData(String data) {
          this.data = data;
      }

      public Node getNextNode() {
          return nextNode;
      }

      public void setNextNode(Node nextNode) {
          this.nextNode = nextNode;
      }

  }

该怎么使用呢?让咱们来初始化一个链表吧!

private Node InitNode() {
        // 当前节点
        Node curNode = new Node();

        // 构建头结点
        Node head = new Node();
        head.setData("head");
        head.setNextNode(null);
        // 当前节点位于头结点
        curNode = head;

        // 新增第一个节点
        Node n1 = new Node();
        // 获取到当前节点,使得的下一个节点设置为n1
        curNode.setNextNode(n1);
        n1.setData("node1");
        n1.setNextNode(null);
        // 当前节点位于第一个节点
        curNode = n1;

        // 第二个节点
        Node n2 = new Node();
        curNode.setNextNode(n2);
        n2.setData("node2");
        n2.setNextNode(null);
        curNode = n2;

        // 第三个节点
        Node n3 = new Node();
        curNode.setNextNode(n3);
        n3.setData("node3");
        n3.setNextNode(new Node());
        curNode = n3;

        // 第四个节点
        Node n4 = new Node();
        curNode.setNextNode(n4);
        n4.setData("node4");
        n4.setNextNode(new Node());
        curNode = n4;
        return head;
    }

注意curNode的变更,使得当前节点总落在最后一个节点上,下次插入时就不须要知道前面一个节点的名字了,经过curNode就能够直接插入了。

  到底成功了没有,咱们来遍历一下。

LinkMain m = new LinkMain();
        Node testNode = m.InitNode();
        Node iter = testNode.getNextNode();
        while (null != iter) {
            if (null != iter.getData()) {
                System.out.println(iter.getData());
            }
            iter = iter.getNextNode();
        }

输出结果以下:

其中testNode是这样的:

  ----------------------------分割线---------------------------------------

  OK,搞定了初始化和遍历,让咱们来试试插入一个节点吧,需求是在某个链表中,第N个位置插入一个节点temp:

  新增原理:对于temp节点来讲

  红色表明的是以前链接,黑色的是以后应该作的。

  private Node addNode(Node head, int n, Node temp) {
        int i = 0;
        while (null != head) {
            if (i == n) {
                temp.setNextNode(head.getNextNode());
                head.setNextNode(temp);
                return head;
            } else {
                head = head.getNextNode();
                i++;

            }
        }
        return head;
    }

 新增后再遍历一下

    // 新增一个节点

Node temp = new Node();
        temp.setData("tempNode");
        temp.setNextNode(null);
        Node n3 = m.addNode(testNode, 2, temp);

        iter = testNode.getNextNode();
        while (null != iter) {
            if (null != iter.getData()) {
                System.out.println(iter.getData());
            }
            iter = iter.getNextNode();
        }

   那么效果如何呢?

 

 其中testNode的内容应该是

  

  OK,结果正确。

  ----------------------------分割线---------------------------------------

链表的删除节点功能
        一开始还搞不定删除,后来画图分析了下,终于解决,放上代码。

难点是设置P,Q两点的指向。其中q是p的下个节点。

应该先找到须要删除的位置。

  原理很简单,就是要绕过q来链接p和后后(q后面的节点)节点。  

  删除前:

 

  删除后:

相关文章
相关标签/搜索