JAVA中的集合类型的理解及应用

news2024/9/22 13:34:33

目录

概述

List和Queue

Map和Set

HashTable和HashMap的区别

 Queue和Deque

BlockingQueue

并发集合


概述

 写程序免不了处理一些批量数据,不同数据结构和算法,会带来不同的性能效果。大学的计算机课程中就有一门叫《数据结构》的课程,这门课程就是研究处理批量数据的结构和算法。Java中的集合类型就是数据结构和算法的具体实现,我们直接拿来用就行了。不过,在用之前需要了解不同集合类型的具体数据结构定义和操作,了解其算法特点和性能,这样在实际开发中才能得心应手。我们先看一下,Java中的集合类型有哪些。官方教程给了一张图: 

官方细化类型

 图中Queue和Deque可以算一种,SortedSet是在Set的基础增加了排序存储和算法也是一回事,Set在具体实现类中用的散列表存储的,和Map也算是一回事。所以总结下来,实际就三种集合:List、Queue、Map。

有了初步映像后,接下来我们再一一比较它们的具体实现类型间的区别。

List和Queue

List有两个具体实现,一个是ArrayList,另一个是LinkedList,按照字面差不多就知道这两个类型分别对应数据结构中的线性表和链表。线性表是以数据方式存放数据,链表是以链接方式存放数据,其区别是线性的数据是连续的,而链表是分散的。因为这种差别,在频繁的增改操作上,链表的性能要好于线性表,而且性能表容易造成空间浪费。

 我们看一下ArrayList的数据增加方式:

private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

从代码中可以看出,当元素的个数超过存放数据的数组容量的时候,就会计算新的长度,重新生成创建一个数组,这样就增加了性能的开销。

再看ArrayList的移除操作,其实就是把后边的数据往前移,但这个Array的长度还在,造成空间浪费:

    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

LinkList增删元素只要修改相应的前后驱引用就行了,明显性能会好很多,移除的元素也会被JVM回收,不会造成空间浪费。

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

 但对于只读数据来讲,线性表反倒优于链表。链表不能通过索引值直接定位到具体元素,而线性表可以。

ArrayList获取元素,通过索引值直接得到数组中的元素:

    E elementData(int index) {
        return (E) elementData[index];
    }

LinkedList获取元素,通过计算元素在链接中的位置,返回相应的元素:

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            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;
        }
    }

Map和Set

Map有三个实现:HashMap, TreeMap  LinkedHashMap。对应的Set也有三个实现:HashSet, TreeSet, 和 LinkedHashSet。Map和Set区别是Set使用元素自身做索引键,Map需要显式给定键和值。

往HashSet中添加元素的实现代码:

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

往HashMap中添加元素:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

知道了Map和Set是一回事之后,我们区分出Map三种具体实现的差别,就知道如何应用了。

HashMap是创建了一个二维表格,每一行数据由哈稀值做id,存放位置也是根据哈稀值得到,因为是一个顺序不可控的数据集合,另外相同的对象获取的哈稀值是一样的,所以集合中不会出现两个相同的键,因为HashSet可以用于数据的去重:

    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 1st
                            treeifyBin(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 key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

TreeMap用的树存储,通过比较前后键内容从上到下,从左到右顺序摆放:

    public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

LinkedHashMap在HashMap的基础上,为键值对增加了前后驱,这样就可以按插入顺序进行元素查找。

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

HashTable和HashMap的区别

这两个类大同小异,只不过HashTable不允许空值,如果放入空值则直接抛出异常,同时写入方法上加了同步锁,所以是线程安全的,但默认加锁可能带来一些不必要的性能开销:

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

 Queue和Deque

Queue本是个List,定义这个类是为了实现FIFO的效果,于是在LinkedList上直接实现了标准队列效果。Deque继承了Queue,并增加了插队的功能,及两端都可以push和pop。

BlockingQueue<E>

JDK1.5之后,增加了java.util.concurrent 包 ,里边增加了一些同步集合。

顾名思义,这是个阻塞队列,即当队列为空的时候,从队列中取元素的线程就会挂在那里,直到队列不为空为止。

//此线程往队列里塞对象
class Producer implements Runnable {
   private final BlockingQueue queue;
   Producer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while (true) { queue.put(produce()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   Object produce() { ... }
 }

//此线程从队列里取对象
 class Consumer implements Runnable {
   private final BlockingQueue queue;
   Consumer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
//如果队列中没有对象,则会挂起
       while (true) { consume(queue.take()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   void consume(Object x) { ... }
 }

 class Setup {
   void main() {
     BlockingQueue q = new SomeQueueImplementation();
     Producer p = new Producer(q);
     Consumer c1 = new Consumer(q);
     Consumer c2 = new Consumer(q);
     new Thread(p).start();
     new Thread(c1).start();
     new Thread(c2).start();
   }
 }

并发集合

并发有关的集合有:ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, CopyOnWriteArrayList, 及CopyOnWriteArraySet

 这个类跟相应的非并发类类似,只是在集合修改的时候,其它线程访问此类集合不会报集合被修改的异常(ConcurrentModificationException),这些类的目标并不是为了保证集合内容的同步,如果想保持集合数据访问一致,那就需要用 Collections.synchronizedMap(new HashMap())来生成同步集合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/32960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

总结我的 MySQL 学习历程,给有需要的人看

作者| 慕课网精英讲师 马听 你好&#xff0c;我是马听&#xff0c;现在是某零售公司的 MySQL DBA&#xff0c;身处一线的我表示有很多话要讲。 我的MySQL学习历程 在我大三的时候&#xff0c;就开始接触到 MySQL 了&#xff0c;当时我也是从最基础的 MySQL 知识&#xff08;…

6、项目第六阶段——用户名登录显示和注册验证码

第六阶段——用户名登录显示和注册验证码 1、登陆—显示用户名 UserServlet 程序中保存用户登录的信息 UserServlet程序中&#xff1a; //保存用户登录信息到Session域中 req.getSession().setAttribute("user",loginUser);修改 login_succuess_menu.jsp&#xf…

2019 国际大学生程序设计竞赛(ICPC)亚洲区域赛(银川) 7题

文章目录N.Fibonacci SequenceB.So EasyI.Base62G.Pot!!F.Function!K.Largest Common Submatrix补题链接&#xff1a;https://codeforces.com/gym/104021 难得VP打出这么好的成绩&#xff0c;虽然是有争议的西部枢纽银川站&#xff0c;虽然没能早生几年。。。。 N.Fibonacci Se…

【数据结构】基础:堆

【数据结构】基础&#xff1a;堆 摘要&#xff1a;本文主要介绍数据结构堆&#xff0c;分别介绍其概念、实现和应用。 文章目录【数据结构】基础&#xff1a;堆一、概述1.1 概念1.2 性质二、实现2.1 定义2.2 初始化与销毁2.3 入堆2.4 出堆2.5 堆的创建2.6 其他三、应用3.1 堆排…

《前端》css总结(下)

文章目录元素展示格式displaywhite-spacetext-overflowoverflow内边距和外边距marginpadding盒子模型box-sizing位置position&#xff1a;用于指定一个元素在文档中的定位方式浮动flex布局flex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-contentorderflex-g…

你最少用几行代码实现深拷贝?

问题分析 深拷贝 自然是 相对 浅拷贝 而言的。 我们都知道 引用数据类型 变量存储的是数据的引用&#xff0c;就是一个指向内存空间的指针&#xff0c; 所以如果我们像赋值简单数据类型那样的方式赋值的话&#xff0c;其实只能复制一个指针引用&#xff0c;并没有实现真正的数…

计算机组成原理4小时速成:存储器,内存ROM,RAM,Cache,高速缓存cache,外存,缓存命中率,效率

计算机组成原理4小时速成&#xff1a;存储器&#xff0c;内存ROM,RAM,Cache&#xff0c;高速缓存cache&#xff0c;外存&#xff0c;缓存命中率&#xff0c;效率 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法…

MYSQL事务原理分析

目录事务是什么ACID特性原子性&#xff08;A&#xff09;隔离性&#xff08;I&#xff09;持久性&#xff08;D&#xff09;一致性&#xff08;C&#xff09;隔离级别简介有些什么READ UNCOMMITTED&#xff08;读未提交&#xff09;READ COMMITTED&#xff08;读已提交&#xf…

【17】Java常见的面试题汇总(设计模式)

目录 1. 说一下你熟悉的设计模式&#xff1f; 2. 简单工厂和抽象工厂有什么区别&#xff1f; 1. 说一下你熟悉的设计模式&#xff1f; 单例模式&#xff1a;保证被创建一次&#xff0c;节省系统开销。 工厂模式&#xff08;简单工厂、抽象工厂&#xff09;&#xff1a;解耦…

力扣(LeetCode)60. 排列序列(C++)

枚举 枚举每一位可能的数字。 如图。算法流程如上图。 思路分析 : 一个数 nnn &#xff0c;可以组成的排列数量有 n!n!n! 。当首位确定&#xff0c;剩余位能组成的排列数量 (n−1)!(n-1)!(n−1)! &#xff0c;依次类推 (n−2)!/(n−3)!/(n−4)!/…/2!/1!/0!(n-2)!/(n-3)!/(n…

CentOS7安装MYSQL8.X的教程详解

1-首先查看系统是否存在mysql&#xff0c;无则不返回 1 # rpm -qa|grep mysql 2-安装wget 1 # yum -y install wget 3-抓取mariadb并删除包&#xff0c;无则不返回 1 # rpm -qa|grep mariadb 4-删除mariadb-libs-5.5.68-1.el7.x86_64 1 # rpm -e --nodeps mariadb-libs-…

本节作业之数组求和及其平均值、求数组最大值、最小值、数组转换为分割字符串、新增数组案例、筛选数组案例、删除指定数组元素、翻转数组、数组排序(冒泡排序)

本节作业之数组求和及其平均值、求数组最大值、最小值、数组转换为分割字符串、新增数组案例、筛选数组案例、删除指定数组元素、翻转数组、数组排序<冒泡排序>求数组[2,6,1,7,4]里面所有的元素的和以及平均值求数组[2,6,1,77,52,25,7]中的最大值求数组[2,6,1,77,52,25,7…

Linux - netstat 查看系统端口占用和监听情况

文章目录功能语法示例显示 tcp&#xff0c;udp 的端口和进程Show both listening and non-listening socketsList all tcp ports.List all udp portsList only listening portsList only listening TCP ports.List only listening UDP ports.List only the listening UNIX port…

Android 性能优化方法论【总结篇】

作为一位多年长期做性能优化的开发者&#xff0c;在这篇文章中对性能优化的方法论做一些总结&#xff0c;以供大家借鉴。 性能优化的本质 首先&#xff0c;我先介绍一下性能优化的本质。我对其本质的认知是这样的&#xff1a;性能优化的本质是合理且充分的使用硬件资源&#x…

分享感恩节联系客户话术

看了一眼在手边的日历&#xff0c;发现今天是11月24日&#xff0c;一年一度的感恩节又到了&#xff0c;不得不感叹时间过得真快&#xff0c;每年十一月的第四个星期四是感恩节。 随着各国多元文化的发展&#xff0c;感恩节也越来越被世界各地传颂&#xff0c;多少都会有一些影…

WANLSHOP 直播短视频种草多用户电商系统源码自营+多商户+多终端(H5+小程序+APP)

WANLSHOP高级版 可用于自营外包项目(多主体)、 可用于外包定制开发项目、 提供开源源码&#xff0c;私有化部署、一款基于 FastAdmin Uni-APP 开发的 多终端&#xff08;H5移动端、APP、微信小程序、微信公众号&#xff09;、多用户商城系统拥有多种运营模式B2B2C/B2C&#xf…

python安装依赖库

一、安装pip 1、打开终端&#xff0c;输入&#xff1a; pip3 install tushare -i https://pypi.douban.com/simple 2、验证是否安装成功 打开终端&#xff0c;输入&#xff1a;pip3 出现以上页面&#xff0c;则安装成功 二、安装flask 1、打开终端&#xff0c;输入&…

mybatis-plus学习笔记

文章目录1 简介2 初始化项目2.1引入pom2.2 引入lombok插件2.3 配置信息2.4 创建实体类2.5 创建mapper2.6 配置注解MapperScan2.7 编写测试类2.8 配置MyBatis日志3 测试基本的CRUD3.1 新增3.2 查询3.3 修改3.4 删除4 自定义动态sql5 Service 层使用mybatis-plus方法5.1 service层…

使用hive进行大数据项目分析

目录 一、首先要创建表&#xff1a;在txt记事本中先输入创建语句备用&#xff0c;创建class1~class5的表。 二、启动hadoop集群&#xff0c;MySQL&#xff0c;hive。 三、创建数据库zhh&#xff0c;用户为zhh&#xff0c;之后将之前写在txt记事本里的创建表class1~class5的命…

浅谈企业信息化安全建设中的三大误区

伴随着信息化的深度建设与应用&#xff0c;与之相伴的信息安全事件也层出不穷&#xff01;很多企业开始关注信息安全问题、关注信息安全建设&#xff0c;大家的共识已经达到前所未有的高度。 虽然许多的企业虽然认识到信息安全的重要性&#xff0c;在实际实施过程中却又无从下…