关于集合:JAVA-集合

1、List,Set都是继承自Collection接口,Map则不是 2、List特点:元素有放入程序,元素可反复 ,Set特点:元素无放入程序,元素不可反复,反复元素会笼罩掉,(留神:元素尽管无放入程序,然而元素在set中的地位是有该元素的HashCode决定的,其地位其实是固定的,退出Set 的Object必须定义equals()办法 ,另外list反对for循环,也就是通过下标来遍历,也能够用迭代器,然而set只能用迭代,因为他无序,无奈用下标来获得想要的值。) 3.Set和List比照: Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素地位扭转。 List:和数组相似,List能够动静增长 查找元素效率高,插入删除元素效率低,因为会引起其余元素地位扭转。4.Map适宜贮存键值对的数据 5.线程平安汇合类与非线程平安汇合类 LinkedList、ArrayList、HashSet是非线程平安的,Vector是线程平安的;HashMap是非线程平安的,HashTable是线程平安的;StringBuilder是非线程平安的,StringBuffer是线程平安的。

February 27, 2023 · 1 min · jiezi

关于集合:Java-集合有哪些内容

明天咱们就来简略的理解下java中的汇合,有理解过得敌人都晓得,也都用过,比如说做罕用的List,还有Set、Map,而且像List和Set都是用于存储单列数据的汇合,他们的父接口都是Conllection,List的特点是存储的值是有序且容许反复的,Set的特点是存储的元素是惟一、不可反复且无序的,而Map的特点则是用于存储双列数据的汇合,存储的数据时无序的,键不能够反复,值能够反复。 接下来咱们就把它们拆出来具体的来聊一聊。 List接口(通常有些面试官会问这样一个比拟根底的问题:List是接口还是类?大家感觉呢,有教训的小伙伴必定一下子就能答复进去,然而有一些刚开始学习的小白同学可能会有一些犹豫哦,那这次我都会给大家说说滴,小白同学看完当前有人再问你这样的问题就不会再犹豫啦) 首先咱们要明确:咱们java中的汇合分为单列汇合和双列汇合,单列汇合的顶级接口是:Conllection,双列汇合的顶级接口是:Map 而List就是属于单列汇合Conllection的子接口,List接口的实现类如下:(所以List是接口不是类哦,通过上面能够多理解理解List接口的实现类都有哪些): 1.ArrayList(List接口的实现类):底层数据结构是数组,查问快,增删慢,因为家喻户晓ArrayList的底层数据结构是数组,向ArrayList中退出元素add的时候,有可能会导致List的扩容, 因为ArrayList底层是数组构造,但数组不反对动静扩容,所以ArrayList的扩容机制就是再创立一个新数组,把就数组数据迁徙到新数组,而后再退出新元素,这样一个过程下来也就是造成ArrayList减少元素慢的外围起因了。 而ArrayList在删除元素时底层是将删除索引地位起到最初索引地位完结中所有的元素,一一向前复制一个索引地位,再将最初索引地位元素设置为null,简略来说就是,假如我当初要删除这个汇合中0索引地位上的元素,我将0索引地位下面的元素删除之后,并不是将这个地位的元素间接设置为null,而是前面1索引地位到最初索引地位上的所有元素都要走一个一一向前复制一个索引地位过程,直到最初一个索引元素向前复制完结之后,将开端地位上空缺的元素复制为null,所以也就是说在删除元素的时候会影响其余索引地位上的元素的地位,所以也会升高删除元素的效率。 (ArrayList汇合删除元素之后其余索引地位上的元素挪动地位的变动过程)了解完增删慢的起因,其实查问快的起因大家就曾经很好了解了,ArrayList在查问元素时底层是通过拜访数组元素形式进行查问。数组(Array)是一种线性表(线性表就是数据排成像一条线一样的构造。每个线性表上的数据最多只有前和后两个方向)数据结构。 它用一组间断的内存空间,来存储一组具备雷同类型的数据。并且申明一个数组时,会在内存中申请一块间断相邻的内存空间。当要通过索引拜访数组元素时,可通过数组地址值和偏移量,间接计算出要查找元素的内存地址,所以数组反对通过索引间接拜访元素,效率十分高。数组中的元素都是有下标的,所以依据下标就能够很快的找到你要查问的元素了(应用get()办法, 就能够间接取的是数组对应下标中的值)。 2.LinkedList(List接口的实现类):底层数据结构是链表,查问慢,增删快 LinkedList之所以查问慢是因为,LinkedList底层是一个链表, 链表必定没有下标的概念,所以他不能像ArrayList一样,应用get()办法, 就能够间接取的是数组对应下标中的值,它只能是对总数遍历,而后取循环下标的值,所以如果LinkedList链表size越大,那么会使for循环的次数增多导致遍历的工夫也会越长,查问也就慢了(然而如果大家在编码过程中还是须要思考应用LinkedList的话在查问的时候能够应用getFirst()、getLast() 办法缩小for循环的次数来绝对减少查问的速度)。 而LinkedList之所以减少元素时快次要是因为,LinkedList 应用add(E e)间接增加元素或者add(int index, E e)指定地位增加元素,这两种办法插入元素,绝对与ArrayList效率是较高的,因为ArrayList减少元素的时候可能须要扩容和元素的拷贝,减少了开销,而LinkedList 是断开指定地位的链接把新节点增加进来即可,所以整个增加过程中,零碎也只做了两件事件,增加一个新的节点,而后保留该节点和前节点的援用关系,简略来说就是新减少了一个链接而已,所以效率高。 删除元素的时候也是依据remove(Object o) 删除元素或者remove(int index)删除指定地位元素这两种办法进行元素的删除,所以简略来说删除效率高次要是因为只须要删除某个链接,再链接新的元素即可,不须要批改列表中残余的元素。 3.Vector(List接口的实现类):底层数据结构是数组,正因为它的底层是数组所以他的特点跟ArrayList一样是查问快,增删慢的特点,这一点能够参考下面ArrayList的解释。Set接口也是单列汇合Conllection的子接口,接下来咱们就持续看看他的实现类有哪些吧! 1.HashSet(Set接口的实现类):底层数据结构是哈希表,它是基于 HashMap 实现的,底层采纳 HashMap 来保留元素,HashSet就是为了进步查问效率的,意思就是在查问某个值是否存在的时候,ArrayList须要遍历能力获取到某个值的地位,而HashSet能够通过HashCode疾速进行定位,而且HashSet是依据哈希算法来进行对象的存取的,存取速度很快,当HashSet中元素的个数超出数组本来的大小,就会进行扩容,而哈希表其实次要依赖hashcode()和equals()这两个办法。简略来讲就是首先通过hashcode()来判断hashcode值是否雷同,如果雷同的话就会执行equals()来查看他们的返回值是否雷同,如果返回true的话,就阐明这两个元素反复,则不进行增加,如果返回是false的话,就间接增加到汇合当中。如果hashcode值不雷同就能够间接增加到汇合当中,以上就是针对于HashSet的特点的简略介绍。 2.LinkedHashSet(Set接口的实现类):底层数据结构是链表+哈希表,由链表保障元素的有序,由哈希表保障元素的惟一,LinkedHashSet是一个基于LinkedHashMap实现的有序去重汇合列表,LinkedHashSet中的元素没有反复;LinkedHashSet中的元素有程序,保护了增加程序;LInkedHashSet能够存储null值;这个容器不晓得大家在平时的工作用的多吗,反正我基本上没有用过,所以就是就是我集体针对于他的特点做的简略介绍。 3.TreeSet(Set接口的实现类):底层数据结构是红黑树,特点是元素惟一,且有序,由天然排序和比拟器排序来保障有序的特点,依据返回值是否是0来判断元素是否惟一。TreeSet是有序的Set汇合,因而反对add、remove、get等办法,TreeSet不反对疾速随机遍历,只能通过迭代器进行遍历,如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口,或者实现一个比拟器,在类上implements Comparable,重写compareTo()办法,在办法内定义比拟算法, 依据大小关系, 返回负数正数或零,在应用TreeSet存储对象的时候, add()办法外部就会主动调用compareTo()办法进行比拟, 依据比拟后果应用二叉树模式进行存储。 简略写了一个排序大家能够粗鲁看看 这是排序的一个运行后果图,遍历两次是因为应用了两种不同的遍历形式接下来就该说说双列汇合Map了,大家能够看看他的实现类有哪些。 1.HashMap(Map接口的实现类):底层数据结构是数组+链表,基于哈希表的 Map 接口的实现。并容许应用 null 值和 null 键,是以 key-value 存储模式存在,每一个键值对也叫做一个Entry,依据键的hashCode值存储数据,大多数状况下能够间接定位到它的值,键key为null的记录至少只容许呈现一条,值value为null的记录能够有多条,HashMap 的实现不是同步的,这意味着它不是线程平安的,HashMap是由数组+链表+红黑树(JDK1.8后减少了红黑树局部,链表长度超过阈值(8)时会将链表转换为红黑树)实现的。对于HashMap常常会有面试官问HashSet和HashMap的区别?其实针对于这个问题,大家通过上述HashSet的解释也能总结进去了, 相同点: 1.都是采纳的Hash散列算法调配的数据, 2.都是线程不平安的, 3.数据查问是无序的; 不同点: 1.继承的父类不同 2.set的单键,而hashmap是能够存键值对 ...

December 27, 2022 · 1 min · jiezi

关于集合:利用共享内存实现比-NCCL-更快的集合通信

作者:曹彬 | 旷视 MegEngine 架构师 简介从 2080Ti 这一代显卡开始,所有的民用游戏卡都勾销了 P2P copy,导致训练速度显著的变慢。针对这种状况下的单机多卡训练,MegEngine 中实现了更快的汇合通信算法,对多个不同的网络训练绝对于 NCCL 有 3% 到 10% 的减速成果。 MegEngine v1.5 版本,能够手动切换汇合通信后端为 shm(默认是 nccl),只须要改一个参数。(因为 shm 模式对 CPU 有额定的占用,且只有在特定卡下能力提高效率,因而并没有默认关上) gm = GradManager()gm.attach(model.parameters(), callbacks=[dist.make_allreduce_cb("sum", backend="shm")])目前只实现了单机版本,多机暂不反对背景在大规模训练中,数据并行是最简略最常见的训练形式,每张卡运行齐全一样的网络结构,而后加上参数同步就能够了。 对于 数据并行的参数同步,目前有两种罕用的办法,Parameter Server 和 Gradient AllReduce: Parameter Server 计划须要额定机器作为参数服务器来更新参数,而且核心式的通信形式对带宽的压力很大,减少训练机器的同时通信量也线性减少;而 AllReduce 计划只是参加训练的机器之间相互同步参数,不须要额定的机器,可扩展性好。MegEngine 目前也是应用 AllReduce 计划作为数据并行的参数同步计划。而在 AllReduce 计划中,大家目前罕用的是 NCCL,Nvidia 自家写的 GPU 汇合通信库,通信效率很高。 看到这里,能够失去一个论断,数据并行的状况,用 NCCL 通信库能达到不错的成果。 到这里就完结了?当然不是,在 2080Ti 8 卡训练的状况下,在多个网络下,咱们绝对 NCCL 有 3% 到 10% 的性能晋升。(以 2080Ti 为例子是因为游戏卡不反对 P2P 通信,相对来说通信较慢,通信工夫长,节俭通信工夫能取得的收益较大) 这是怎么做到的呢,咱们一步一步来剖析(以下数据都是用 megengine.utils.profiler 导出,相干文档在 profiler文档)。 ...

August 9, 2021 · 2 min · jiezi

关于集合:7-集合

2.maplinkedHashMap在hashMap上减少一条双向链表,使hashMap构造放弃键值对插入程序,并实现了按程序拜访treeMap 红黑树(自均衡的排序二叉树) 一 list3.arraylistarraylist线程不平安,底层数组,反对快速访问,尾部有空余空间在尾减少删除工夫复杂度都是O(1),在指定地位减少删除工夫复杂度是O(n),因为要一个一个挪 vector线程平安,底层数组 4.linkedlist线程不平安底层双向链表(1.6以前循环链表),双向链表是由两个单向链形成的,双向循环链表是由两个单向环形成的不反对快速访问在头尾减少删除工夫复杂度是O(1),在指定地位减少删除工夫复杂度是O(n),因为要一个一个找到地位每个元素要存前驱和后继元素的地位 5.RandomAccess 接口标识此类是否有疾速定位拜访的能力,arraylist有,linkedlist没有 二 set7.hashSethashSet 线程不平安,能够存null,基于hashmap实现 linkedHashSetlinkedHashSet 能够按增加程序遍历,基于linkedHashMap实现 TreeSettreeSet能够按增加程序遍历,能够天然排序或定制排序,基于红黑树 三 map8 hashTable线程平安,因为外部办法根本都通过synchronized润饰根本淘汰,且kv都不能为null否则会npe初始大小11,扩容为2n+1,如果给出初始,会用你给的jdk1.8前 数组+链表 拉链法解决抵触 jdk1.8后 链表长度大于阈值(默认为8)时将链表转化为红黑树缩小搜寻工夫(转成红黑树前会判断,如果数组长度小于64会先扩容)② Hashtable(同一把锁) :应用 synchronized 来保障线程平安,效率十分低下。当一个线程拜访同步办法时,其余线程也拜访同步办法,可能会进入阻塞或轮询状态,如应用 put 增加元素,另一个线程不能应用 put 增加元素,也不能应用 get,竞争会越来越强烈效率越低。 hashMap非线程平安,通过 (n - 1) & hash 判断以后元素寄存的地位,初始大小16,扩容为2的幂次(求地位的时候速度快),如果给出初始,会用比你给的略微大一点的2的幂次多线程操作导致死循环问题次要起因在于并发下的 Rehash 会造成元素之间会造成一个循环链表。 treemap相比于HashMap来说 TreeMap 次要多了对汇合中的元素依据键排序的能力以及对汇合内元素的搜寻的能力。 相等hashCode()的默认行为是对堆上的对象产生独特值。即便两个对象的数据相等,因为是两个对象,他们的hash也不会相等。根本对象 == 是比拟值,援用对象、包装对象是比拟地址 ConcurrentHashMapJDK1.7 的 ConcurrentHashMap 底层采纳 分段的数组+链表 实现,JDK1.8 采纳的数据结构跟 HashMap1.8 的构造一样,数组+链表/红黑二叉树。实现线程平安的形式(重要): 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了宰割分段(Segment),每一把锁只锁容器其中一部分数据,多线程拜访容器里不同数据段的数据,就不会存在锁竞争,进步并发访问率。 到了 JDK1.8 的时候曾经摒弃了 Segment 的概念,而是间接用 Node 数组+链表+红黑树的数据结构来实现,并发管制应用 synchronized 和 CAS 来操作。(JDK1.6 当前 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程平安的 HashMap,尽管在 JDK1.8 中还能看到 Segment 的数据结构,然而曾经简化了属性,只是为了兼容旧版本; ...

June 7, 2021 · 1 min · jiezi

Java中的集合类ListSetMap

1.List1.1 Arraylist 与 LinkedList 区别是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n)。是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。注意:ArrayList遍历时使用普通for循环较快.LinkedList遍历时使用迭代器较快. 1.2 List中的迭代器for(Object obj : list){ list.remove(obj) }注意:一般list中迭代器不能使用此种方法移除元素,会触发 ConcurrentModifyException,如果要删除可以使用Iterator.remove()此处如果切换成CopyOnWriteArrayList则可以正常删除1.3 ArrayList的扩容机制详见: https://github.com/Snailclimb... Map2.1 Java1.8底层实现底层=数组+链表(大小超过8,转换为红黑树) HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。 ...

September 10, 2019 · 1 min · jiezi

简洁方便的集合处理Java-8-stream流

背景java 8已经发行好几年了,前段时间java 12也已经问世,但平时的工作中,很多项目的环境还停留在java1.7中。而且java8的很多新特性都是革命性的,比如各种集合的优化、lambda表达式等,所以我们还是要去了解java8的魅力。 今天我们来学习java8的Stream,并不需要理论基础,直接可以上手去用。 我接触stream的原因,是我要搞一个用户收入消费的数据分析。起初的统计筛选分组都是打算用sql语言直接从mysql里得到结果来展现的。但在操作中我们发现这样频繁地访问数据库,性能会受到很大的影响,分析速度会很慢。所以我们希望能通过访问一次数据库就拿到所有数据,然后放到内存中去进行数据分析统计过滤。 接着,我看了stream的API,发现这就是我想要的。 一、Stream理解在java中我们称Stream为『流』,我们经常会用流去对集合进行一些流水线的操作。stream就像工厂一样,只需要把集合、命令还有一些参数灌输到流水线中去,就可以加工成得出想要的结果。这样的流水线能大大简洁代码,减少操作。 二、Stream流程原集合 —> 流 —> 各种操作(过滤、分组、统计) —> 终端操作Stream流的操作流程一般都是这样的,先将集合转为流,然后经过各种操作,比如过滤、筛选、分组、计算。最后的终端操作,就是转化成我们想要的数据,这个数据的形式一般还是集合,有时也会按照需求输出count计数。下文会一一举例。 三、API功能举例首先,定义一个用户对象,包含姓名、年龄、性别和籍贯四个成员变量: import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import lombok.extern.log4j.Log4j;@Data@NoArgsConstructor@AllArgsConstructor@Log4j@Builderpublic class User { //姓名 private String name; //年龄 private Integer age; //性别 private Integer sex; //所在省市 private String address;}这里用lombok简化了实体类的代码。 然后创建需要的集合数据,也就是源数据: //1.构建我们的listList<User> list= Arrays.asList( new User("钢铁侠",40,0,"华盛顿"), new User("蜘蛛侠",20,0,"华盛顿"), new User("赵丽颖",30,1,"湖北武汉市"), new User("詹姆斯",35,0,"洛杉矶"), new User("李世民",60,0,"山西省太原市"), new User("蔡徐坤",20,1,"陕西西安市"), new User("葫芦娃的爷爷",70,0,"山西省太原市"));3.1 过滤1)创建流 stream() / parallelStream()stream() : 串行流parallelStream(): 并行流2)filter 过滤(T-> boolean)比如要过滤年龄在40岁以上的用户,就可以这样写: ...

June 25, 2019 · 2 min · jiezi

全栈之路JAVA基础课程六集合20190615v10

欢迎进入JAVA基础课程 博客地址:https://blog.csdn.net/houjiyu...本系列文章将主要针对JAVA一些基础知识点进行讲解,为平时归纳所总结,不管是刚接触JAVA开发菜鸟还是业界资深人士,都希望对广大同行带来一些帮助。若有问题请及时留言或加QQ:243042162。 寄语:再走长征路,回顾过往峥嵘岁月,重温重要历史事件,砥砺前行,用脚步丈量新时代的长征路。工作道路上,我们也要弘扬这种长征精神,坚持不懈,一步一个脚印,脚踏实地,朝着自己的目标前行。集合1. 集合框架图(1)缩略版 (2)详细版 2.集合和数组区别 3.Collection接口 List接口:元素按进入先后有序保存,可重复 (1)LinkedList:底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素(2) ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素 (3) Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素 Set 接口: 仅接收一次,不可重复,并做内部排序 HashSet 使用hash表(数组)存储元素 LinkedHashSet 链表维护元素的插入次序TreeSet 底层实现为二叉树,元素排好序HashSet和TreeSet区别: (1)Treeset 中的数据是自动排好序的,不允许放入 null 值。 (2)HashSet 中的数据是无序的,可以放入 null,但只能放入一个 null,两者中的值都不能重复,就如数据库中唯一约束。 (3)HashSet 要求放入的对象必须实现 HashCode()方法,放入的对象,是以 hashcode 码作为标识的,而具有相同内容的 String 对象,hashcode 是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 4.Map HashMap和HashTableHashMap 和 Hashtable 都实现了 Map 接口,因此很多特性非常相似。但是,他们有以下不同点:(1)HashMap 允许键和值是 null,而 Hashtable 不允许键或者值是 null。(2)Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线程环境,而 Hashtable 适合于多线程环境。 TreeMap 5.集合遍历(1)list遍历 public class CollectionMain { public static void main(String[] args) { List<String> testList = new ArrayList<>(); testList.add("1"); testList.add("2"); testList.add("3"); System.out.println("使用Iterator迭代....."); Iterator<String> iterator = testList.iterator(); while (iterator.hasNext()){ String value = iterator.next(); System.out.printf("%s ",value); } //在使用ListIterator迭代时,开始也需要正向迭代,然后在倒序迭代 System.out.println("\n\n使用ListIterator迭代....."); System.out.println("正向遍历....."); ListIterator<String> listIterator = testList.listIterator(); while (listIterator.hasNext()){ String value = listIterator.next(); System.out.printf("%s ",value); } System.out.println("\n反向遍历....."); while (listIterator.hasPrevious()){ String value = listIterator.previous(); System.out.printf("%s ",value); } }}输出结果 ...

June 16, 2019 · 1 min · jiezi

数据结构与算法概述

了解和学习一种知识的最好方法是带着相关的问题去探索,当我们把一些常见的问题全部解答了,我们也就能对这种事物有一些初步的了解了。试着回答下面的几个问题,让我们对数据结构和算法有一个基本的认识吧。 什么是数据结构?为什么要了解数据结构?作为一个前端,日常工作当中,我们接触的数据结构有哪几种?数据结构和算法是什么关系?如何判断一个算法是否是最优?什么是数据结构?从字面意思来理解就是一种数据的多种表现方法,什么是结构——由组成整体的各部分的搭配和安排(百度百科)。我的理解是:数据的排列,不同的数据结构就是数据的多种排列形式,然后根据排列的情况我们通常用一个学名来表示它,比如说:数组,集合,树,图等。 为什么要了解数据结构?当我们了解了数据结构之后,在实际的编程过程当中,我们遇到某一类的数据的时候,我们就能够找到一种最合适的数据结构来表示他们了。这样再处理跟数据相关问题的时候就会变得高效,因为确定了数据结构,我们也就确定了针对该结构的数据,使用那些方法来对数据进行相关的操作了。比如说,我们需要一种数据结构来记录每天的天气情况,当我们说到某一天的时候,就能立刻知道当天的天气是怎么样的时候我们可以用一个数组来表现。又如,我们要描述一个家族的族谱的时候,用树型结构比较合适;描述一个人的年龄,身高,体重,民族,学历这种用集合比较合适;描述排队的情况用队列。 作为前端,通常我们接触到的数据结构有哪几种?最常用的应该有两种了:数组,对象。到了ES6又增加了两种新的数据结构Set和Map,其实Set和Map应该算是数组和对象的一种变种,但总的来说它们又是另外两种类型的数据结构; Set类数组结构——成员唯一,没有重复的值Map类对象结构——不同Object,键值通常是字符串,Map的键值可以是任何类型。数据结构和算法的关系数据结构只不过是我们描述数据的一种手段,但我们最终的目的通常是对这些数据进行相关的操作,比如:添加,删除,查找,排序等。所谓的算法就是如何去实现这种操作的一种计算方式。但算法往往不止一种,有道是条条道路通罗马,通常要达到某种目的,我们可能会有很多种的算法。比如一个数组我们要给他进行去重,就有很多种方法。你可以循环遍历,把每个值跟其他的值比较一遍,也可以把数组转成Set结构的数据。 如何判断一个算法是否最优?上面说到实现某种操作的方法有很多种,但是哪一种是最好的,我们要如何判断呢。我们可以通过算法的执行时间对吧,那种算法执行的速度越快,当然那种算法就最好。但计算机当中,还要考虑到算法的空间复杂度,也就是算法执行过程当中,可能占用的内存空间,一个算法执行的速度非常块,但执行的时候,需要1T的内存空间,这就不行。所以好的算法往往有两个条件: 运算的速度快占用的内存(存储空间) 小我还有个第三点,那就是代码便于理解,但这部分优秀的算法,往往涉及到很多数学相关的问题,如果没有这部分相关的概念,理解起来是非常不容易的。 其它涉及到前端数据结构和算法的一些学习笔记,我放到了github上面,内容还在更新,尽量一周分享一个,欢迎大家一起来讨论并参与分享,github地址如下: https://github.com/mmcai/Stru...

April 26, 2019 · 1 min · jiezi

面试官问你数组和ArrayList怎么答?

我在想每个人在面试的时候都会被问到集合相关的问题,有好大一部分人在回答的时候并没有那么多的逻辑性,通常都是想到哪里说到哪里,这篇文章大概的捋一捋关于集合的相关问题。在每种编程语言中,都会有循环、数组、流程控制语句,数组是一种线性表数据结构,内存空间是连续的,保存的数据类型也是一致的。正是因为这两点,数组的随机访问才会非常的高效,这同时也是一把双刃剑,使得数组的其他操作效率变得很低,比如说,增加,删除,为了保持数组里面数据的连续性,就会做大量的消耗性能的数据迁移操作。针对数组这种类型,java中有容器类,比如ArrayList,ArrayList是对数组的包装,在底层就是数组实现的,因为数组在定义的时候必须是指定的长度,定义之后就无法再增加长度了,就是说不可能在原来的数组上接上一段,所以ArrayList就解决了这个问题,当超过数组容量的时候,ArrayList会进行扩容,扩容之后的容量是之前的1.5倍,然后再把之前数组中的数据复制过来。接下来,我们看下ArrayList的源码: private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;源码中数组最大的容量是Integer.MAX_VALUE -8,为什么要减去8 呢,这个是上面的定义上面的注释: /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */首先是一些VM在数组中保存的头信息;尝试着去分配更大的数组可能会导致OutOfMemoryError,请求的数组大小超过了VM的限制。在看这句代码的时候,脑中有没有出现两个大大的问号??首先,有没有想到为什么这个数组属性需要用 transient修饰?(想知道这个关键字是干什么的,可以看下我之前的一篇文章:面试问你java中的序列化怎么答?)transient Object[] elementData; // non-private to simplify nested class access大家可以随便的想一下,如果是面试的时候,你会怎么回答?由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 transient 修饰,可以防止被自动序列化。因此 ArrayList 自定义了序列化与反序列化,具体可以看 writeObject 和 readObject 两个方法。需要注意的一点是,当对象中自定义了 writeObject 和 readObject 方法时,JVM 会调用这两个自定义方法来实现序列化与反序列化。第二个问题:这个属性的类型为什么是Object而不是泛型?这里和大家说下:java中泛型运用的目的就是对象的重用,就是同一个方法,可以支持多种对象类型,Object和泛型在编写的时候其实没有太大的区别,只是JVM中没有T这个概念,T只是存在编写的时候,进入虚拟机运行的时候,虚拟机会对这种泛型标志进行擦除,也就是替换T到指定的类型,如果没有指定类型,就会用Object替换,同时Object可以new Object(),就是说可以实例化,而T则不能实例化。在反射方面来说,从运行时,返回一个T的实例时,不需要经过强制转换,然后Object则需要经过转换才能得到。当我们试图向ArrayList中添加一个元素的时候,java会自动检查,以确保集合中确实还有容量来添加新元素,如果没有,就会自动扩容,下面是核心代码,我已经在代码里加了注释,帮助大家能够更好的理解:private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity);}上面这段代码中有个 变量 modCount++,看到这里的时候确实有些疑惑,我找了下,这个变量是在AbstractList中定义的protected修饰的全局变量,这个变量是记录了结构性改变的次数,结构性改变就是说修改列表大小的操作。ArrayList是一个线程不安全的类,这个变量就是用来保证在多线程环境下使用迭代器的时候,同时又对集合进行了修改,同一时刻只能有一个线程修改集合,如果多于一个,就会抛出ConcurrentModficationException。private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //核心的扩容代码:扩容之后的容量, int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) //扩容之后的容量与本次操作需要的容量对比,取更大的 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) //在与数组的最大容量对比,如果比最大的容量大,进入下一个方法 newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //接下来,是把原数组d 数据复制到新的数组里 elementData = Arrays.copyOf(elementData, newCapacity);} private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //当 Integer-8 依然无法满足需求,就会取Integer的最大值 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;}接下来我们看下,向指定位置添加元素是什么样的:public void add(int index, E element) { rangeCheckForAdd(index); //扩容校验 ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++;}可以看到,当一个集合足够大的时候,add操作向数组的尾部添加元素效率还是非常高的,但是当向指定的位置添加元素的时候,也是需要大量的移动复制操作:System.arraycopy()。到这里,ArrayList最大的优势是什么呢?我们在平时的开发中涉及到容器的时候为什么会选择List家族的成员,而不直接选择数组呢?大家回想一下自己之前敲代码的经历,答案也就出来了:ArrayList封装了大部分数组的操作方法,比如插入、删除、搬移数据等等,都在集合内部帮你做好了,还有就是支持动态扩容,这点是数组不能比拟的。这里需要注意一点,当我们在开始定义集合的时候,如果知道我们需要多大的集合,就应该在一开始就指定集合的大小,因为在集合的内部来进行数据的搬移,复制也是非常耗时的。那么数组在什么时候会用到呢?1、java ArrayList无法存储基本类型,int,long,如果保存的话,就需要封装为Integer、Long,而自动的拆装箱,也有性能的消耗,所以总结下这点就是说如果要保存基本类型,同时还特别关注性能,就可以使用数组。2、如果对数据的数量大小已知,操作也非常简单,也不需要ArrayList中的大部分方法,也是可以直接使用数组的。如果对本文有任何异议,可以加我好友(有没有问题都欢迎大家加我好友),也可以在下面留言区留言,我会及时修改。希望这篇文章能帮助大家在面试路上乘风破浪。这样的分享我会一直持续,你的关注、转发和好看是对我最大的支持,感谢。关注我,我们一起成长。 ...

April 18, 2019 · 1 min · jiezi

聊一聊Iterable与Iterator的那些事!

前言欢迎关注公众号:Coder编程获取最新原创技术文章和相关免费学习资料,随时随地学习技术知识!在上一篇文章通过面试题,让我们来了解Collection,我们会发现Collection接口之上还有一个接口Iterable,Iterable接口里面又有Iterator接口,那他们到底有什么区别呢?我们接下来就来了解下Iterable与Iterator相关内容,也就是本章的主要内容了,说不定在我们面试过程中,也会遇到一些问题呢。那我们就开始吧涉及面试题:1.说一说Iterable?2.Iterable结构?3.说一说Iterator?4.Iterator结构?5.forEachRemaining()与forEach()方法的区别?6.Iterator如何使用?7.Iterable与Iterator之间的区别与联系?上面的面试题可以看出,其实都是一回事,只是换了一种提问方式,只要我们能掌握核心要点,随便面试官怎么提问,我们都能轻松应对!来源百度百科:Iterable: 百度的时候,我却只看到了百度翻译:可迭代的; 可重复的; 迭代的; 因此我们可以知道,实现了这个接口的集合对象支持迭代,是可迭代的。Iterator: Iterator我们一般叫迭代器,它就是提供迭代机制的对象,具体如何迭代,都是Iterator接口规范的。通过UML图,我们也可以看出Iterable接口是Java集合框架的顶级接口,实现此接口使集合对象可以通过迭代器遍历自身元素。同时在Java设计模式中也有一种模式——迭代器模式.(在后续的文章我们会介绍相关设计模式,敬请关注)可以看出Iterator是迭代器模式最好的一个应用例子!1.说一说Iterable?由源码图可以看出,Iterable有三个方法,分别是Iterator<T> iterator();default void forEach(Consumer<? super T> action){}; JDK 1.8后新增default Spliterator<T> spliterator(){}; JDK 1.8后新增接下来我们简单介绍下这里面的方法。1.1 Iterable接口中的iterator() 方法Iterator<T> iterator();该接口主要是返回T类型的元素上的一个迭代器。下面再详细介绍Iterator。1.2 Iterable接口中的forEach() 方法default void forEach(Consumer<? super T> action) { // 验证action是否为null,如果action为null,则抛出NullPointerException Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }该方法是循环输出,对内部元素进行遍历,并对元素进行指定的操作。例如:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.forEach(integer -> System.out.println(integer));对源码注释翻译:对这个Iterable的每一个元素执行给定的动作指导所有元素都被处理或者动作抛出一个异常为止。除非被实现类指定,动作将以迭代的顺序执行(如果一个迭代的顺序被指定。)被动作抛出的异常将被传递给调用者1.3 Iterable接口中的spliterator() 方法default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0);}该方法提供了一个可以并行遍历元素的迭代器,以适应现在cpu多核时代并行遍历的需求。简单说:分割,增加并行处理能力对源码注释翻译:创建一个被这个Iterable接口描述的元素上Spliterator。默认实现从iterable的Iterator中创建一个早期绑定的spliterator。这个spliterator继承iterable的iterator的fail-fast性质。 默认实现应该总是被重写。被默认实现返回的spliterator拥有不好分解能力,是不依据指定大小定制的,而且不报告任何spliterator的性质。实现类差不多总是能提供更好的实现。2.说一说Iterator?2.1 Iterator是什么?Iterator被称之为顺序遍历迭代器,jdk中默认对集合框架中数据结构做了实现。Iterator在实际应用中有一个比较好的点就是,可以一边遍历一遍删除元素。后面我会通过ArrayList中的Iterator()方法进行说明。2.2 Iterator结构?由源码图Iterator接口中定义了四个方法,分别是boolean hasNext():如果被迭代遍历的集合还没有被遍历完,返回TrueObject next():返回集合里面的下一个元素remove():删除集合里面上一次next()方法返回的元素void forEachRemaining(Consumer action):JDK 1.8后新增默认方法 使用Lambda表达式来遍历集合元素2.3 Iterator接口中的forEachRemaining() 方法在JDK1.8之后Iterator中增加的一个默认方法//使用方法List<String> arr=new ArrayList<>();arr.add(“hello”);arr.add((“world”));arr.iterator().forEachRemaining(str-> System.out.println(str));2.3.1 forEachRemaining()与forEach()方法之间的区别?forEachRemaining()源码:default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next());}forEach()源码:default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); }}通过源码,我们可以看出他们之间的区别与联系。相同点:都可以遍历集合都是接口的默认方法都是1.8版本引入的区别:forEachRemaining()方法内部是通过使用迭代器Iterator的所有元素,forEach()方法内部使用的是增强for循环。forEach()方法可以多次调用,forEachRemaining()方法第二次调用不会做任何操作,因为不会有下一个元素。3.Iterator如何使用?简单举个小栗子List list = new ArrayList();list.add(“公众号”);list.add(“Coder编程”);for (Iterator iter = list.iterator(); iter.hasNext();) {String str = (String)iter.next();System.out.println(str);}/迭代器用于while循环Iterator iter = list.iterator();while(iter.hasNext()){String str = (String) iter.next();System.out.println(str);}/推荐阅读带你了解Collection相关知识!一篇让你理解进程与线程的区别与联系!文末本章节主要介绍了Iterable与Iterator之间的区别与联系,以及其他方面的小知识点,也是面试过程中会出现的内容点。欢迎关注公众号:Coder编程获取最新原创技术文章和相关免费学习资料,随时随地学习技术知识!Github个人主页目录Gitee个人主页目录欢迎大家关注并Star~ ...

March 25, 2019 · 1 min · jiezi

【J2SE】java编程思想之数组与集合学习总结

数组简述数组是一种效率最高的存储和随机访问对象引用的一个简单的线性序列,虽然访问快速,但为之付出的代价是数组的大小固定,并且在其生命周期中不可改变。数组与其他容器之间的区别在于:效率、类型和保存基本类型的能力。但随着自动包装机制的出现,容器已经可以与数组几乎一样方便,而数组仅存的优点就是效率。应该 “优先选择容器而不是数组”,只有在已经证明性能称为问题(并且切换到数组对性能提高有所帮助)时,你才应该将程序重构为使用数组数组标识符就是一个引用,用以保存指向其他对象的引用,数组的一个单元指向在堆中创建的一个真实对象。对像数组与基本类型数组的区别在于,对象保存的是引用,基本类型数组保存的是基本类型的值。数组的 length属性,表示数组的大小,而不是实际保存的元素个数。新生成的对象数组,会被默认的初始化为 null,基本类型数组则会初始化为对应基本类型的默认值,对于对象数组,可以检测其中的引用是否为 null,进而确定某个位置是否存在对象。当数组不被需要时,垃圾回收器会负责清理数组,否则将一直存在。实用数组方法System.arraycopy():复制一个数组比用 for 循环复制要快得多,且该方法对所有类型做了重载。当复制对象数组时,只是复制对象的引用(属浅拷贝)。Arrays.asList(T… a):将多个元素转换成 List 列表,底层表示的还是数组,当尝试进行 add()、delete()操作时将在运行时报错。Arrays.sort(T[] a, Comparator<? super T> c):按照给定的比较器对指定的数组进行排序。Arrays.binarySearch(T[] a,T key, Comparator<? super T> c):对一个有序的数组采用二分查找获取对象下标,如果数组存在重复元素,可能返回的不精确。<span color=“red”>注:如果使用了Comparator排序了一个对象数组,则调用此方法时传入的Comparator必须是同一个。</span>Comparator比较器comparator用于指定元素的排列顺序,在常见的数据类型中已经被默认实现,对于需要按照某种规则进行排序的时候,提供Comparator或者实现 java.lang.Comparable接口。class DemoComparator implements Comparator<Demo> { @Override public int compare(Demo o1, Demo o2) { if (o1.value == o2.value) { return 0; } return o1.value < o2.value ? 1 : -1; } }容器Collection一个独立元素的序列,这些元素都服从一条或多条规则,提供了存放一组对象的方式。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照队列排队规则来确定对象产生的顺序。PriorityQueue:优先级队列,声明下一个弹出最需要的元素(具有最高的优先级),通过默认排序或提供 Comparator。当调用 peek()、poll()、remove()方法时,获取的元素是队列中优先级最高的。List类描述ArrayList常用于随机访问元素,但是在 List的中间插入和删除元素时较慢。LinkedList对中间插入和删除元素的操作中提供了优化的顺序列表,在随机访问方面相对比较慢,但是它的特性集较 ArrayList更大。ListIterator:Iterator的子类,只用于对各种 List 类的访问,可以实现双向移动。hasNext() / next()、hasPrevious() / previous()对于随机访问的 get/set操作,背后有数组支持的 List 比 ArrayList 稍快一点,但是对于 LinkedList,将会变得很慢,因为它不是针对随机访问操作而设计的。最佳的做法是将 ArrayList 作为默认首选,当因为经常插入和删除操作而性能下降时,才去选择 LinkedList。如果使用的是固定数量的元素,既可以选择List,也可以选择数组。Set类描述Set (interface)存入Set的每一个元素都必须要唯一,因为Set不允许重复元素。加入Set的元素必须定义 equals()方法以确保对象的唯一性。Set 和 Collection 有完全一样的接口。 Set 接口不保证维护元素的次序。HashSet为快速查找而设计的Set。存入 HashSet的元素必须定义 hashCode()。TreeSet保持次序的 Set,底层为数据结构。使用它可以从 Set中提取有序的序列。元素必须实现 Comparator接口。LinkedHashSet具有 HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代遍历 Set时,结果会按元素插入的次序显示。元素必须定义 hashCode()方法。HashSet以某种顺序保存元素,LinkedHashSet按照元素插入的顺序保存元素,而 TressSet按照排序维护元素。HashSet的性能基本上总是比 TreeSet好,特别在添加和查询元素时。 TreeSet存在的唯一原因是它维持元素的排序顺序,因此迭代的时候,TreeSet通常比用HashSet要快。MapMap的各个实现类的行为特性的不同在于,效率、键值对的保存及呈现次序、对象的保存周期、映射表如何在多线程程序中工作和判定 ”键“等价的策略等方面。类描述HashMapMap基于散列表的实现(取代HashTable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能。LinkedHashMap类似于HashMap,但是迭代遍历它时,取得 “键值对” 的顺序就是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点;而在迭代访问时反而快,因为它使用链表维护内部次序。TreeMap基于红黑树的实现。查看 “键” 或“键值对”时,它们会被排序。TreeMap的特点在于,所得到的的结果是经过排序的。TreeSet是唯一一个带有 subMap()方法的Map,它可以返回一个子树。WeekHashMap弱键映射,允许释放映射所指向的对象,这是为解决某类特殊问题而设计的。如果映射 之外没有引用指向某个“键”,则此“键”可以被垃圾收集器回收。ConcurrentHashMap一种线程安全的Map,它不涉及同步加锁。IdentityHashMap使用 ==代替equals()对“键”进行比较的散列映射。专为解决特殊问题而设计的使用Map时,首选HashMap,TreeMap维护着散列数据结构的同时还要维护链表,在迭代的时候速度快,其余方面比HashMap要慢。插入操作随着Map尺寸的变大,速度会明显变慢(除了 IdentityHashMap),但是查找所耗费的代价比插入小很多。对于插入操作,由于维护链表所带来的的开销,导致LinkedHashSet 比 HashSet的代价高很多。HashMap的性能因子:容量:表中的桶位数。初始容量:表在创建时所拥有的桶位数。尺寸:表中当前存储的项数。负载因子:尺寸/容量。 空表的负载因子为 0,半满表为 0.5。负载越轻的表产生的冲突性越小,HashMap默认负载因子为 0.75,达到默认值时,将进行散列。散列与散列码散列码是一个无符号的十六进制数,散列的目的在于使用一个对象来查找另一个对象;散列的价值在于能够快速进行查询。在Map上的实现:通过键对象生成一个数字,这个数字就是散列码。将散列码作为数组的下标,该数组的一个单元指向一个链表,链表上的一个节点就是一个 Entry 对象。数组的容量是固定的,不同的键可以产生相同的下标,这将导致 “冲突”。Example:参考Java编程思想 P493static final int SIZE = 1024;LinkedList<Entry<K, V>>[] buckets = new LinkedList[SIZE]; public V put(K key, V value) { V oldValue = null; // 对散列值取余获取数组下标 int index = Math.abs(key.hashCode()) % SIZE; if (buckets[index] == null) { buckets[index] = new LinkedList<Entry<K,V>>(); } // 获取下标所指向的链表 LinkedList<Entry<K, V>> bucket = buckets[index]; Entry<K, V> pair = new Entry<>(); boolean found = false; // 获取迭代器进行迭代,判断是否存在,存在则覆盖 ListIterator<Entry<K, V>> it = bucket.listIterator(); while (it.hasNext()) { Entry<K, V> iPair = it.next(); if (iPair.getKey().equals(key)) { oldValue = iPair.getValue(); it.set(pair); found = true; break; } } // 没有找到就添加 if (!found) { buckets[index].add(pair); } return oldValue;}equals()自反性。对称性。传递性。一致性。对任何不是 null的 x, x.equals(null) 一定返回 false。hashcode()对同一个对象调用hashcode() 都应该生成同样的值。基于对象的内容生成散列码,让其富有意义。散列码不必独一无二,应关注生成速度。通过 hashCode()和equals(),必须能够完全确定对象的身份。好的 hashCode()应该产生分布均匀的散列码。赋予一个非零常量值。public int hashCode() { int result = 19; return result * field.hashCode();} ...

March 18, 2019 · 2 min · jiezi

Java 8中处理集合的优雅姿势——Stream

在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、改、查、聚合、统计、过滤等操作。相比之下,关系型数据库中也同样有这些操作,但是在Java 8之前,集合和数组的处理并不是很便捷。不过,这一问题在Java 8中得到了改善,Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。本文就来介绍下如何使用Stream。特别说明一下,关于Stream的性能及原理不是本文的重点,如果大家感兴趣后面会出文章单独介绍。Stream介绍Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。Stream有以下特性及优点:无存储。Stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。为函数式编程而生。对Stream的任何修改都不会修改背后的数据源,比如对Stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新Stream。惰式执行。Stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。可消费性。Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。我们举一个例子,来看一下到底Stream可以做什么事情:上面的例子中,获取一些带颜色塑料球作为数据源,首先过滤掉红色的、把它们融化成随机的三角形。再过滤器并删除小的三角形。最后计算出剩余图形的周长。如上图,对于流的处理,主要有三种关键性操作:分别是流的创建、中间操作(intermediate operation)以及最终操作(terminal operation)。Stream的创建在Java 8中,可以有多种方法来创建流。1、通过已有的集合来创建流在Java 8中,除了增加了很多Stream相关的类以外,还对集合类自身做了增强,在其中增加了stream方法,可以将一个集合类转换成流。List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”, “Hello”, “HelloWorld”, “Hollis”);Stream<String> stream = strings.stream();以上,通过一个已有的List创建一个流。除此以外,还有一个parallelStream方法,可以为集合创建一个并行流。这种通过集合创建出一个Stream的方式也是比较常用的一种方式。2、通过Stream创建流可以使用Stream类提供的方法,直接返回一个由指定元素组成的流。Stream<String> stream = Stream.of(“Hollis”, “HollisChuang”, “hollis”, “Hello”, “HelloWorld”, “Hollis”);如以上代码,直接通过of方法,创建并返回一个Stream。Stream中间操作Stream有很多中间操作,多个中间操作可以连接起来形成一个流水线,每一个中间操作就像流水线上的一个工人,每人工人都可以对流进行加工,加工后得到的结果还是一个流。以下是常用的中间操作列表:filterfilter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤掉空字符串:List<String> strings = Arrays.asList(“Hollis”, “”, “HollisChuang”, “H”, “hollis”);strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);//Hollis, , HollisChuang, H, hollismapmap 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().map( i -> i*i).forEach(System.out::println);//9,4,4,9,49,9,25limit/skiplimit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保理4个元素:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().limit(4).forEach(System.out::println);//3,2,2,3sortedsorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().sorted().forEach(System.out::println);//2,2,3,3,3,5,7distinctdistinct主要用来去重,以下代码片段使用 distinct 对元素进行去重:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().distinct().forEach(System.out::println);//3,2,7,5接下来我们通过一个例子和一张图,来演示下,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会发生什么。代码如下:List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”, “Hello”, “HelloWorld”, “Hollis”);Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3) .distinct();过程及每一步得到的结果如下图:Stream最终操作Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常:java.lang.IllegalStateException: stream has already been operated upon or closed俗话说,“你永远不会两次踏入同一条河”也正是这个意思。常用的最终操作如下图:forEachStream 提供了方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:Random random = new Random();random.ints().limit(10).forEach(System.out::println);countcount用来统计流中的元素个数。List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”,“Hollis666”, “Hello”, “HelloWorld”, “Hollis”);System.out.println(strings.stream().count());//7collectcollect就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果:List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”,“Hollis666”, “Hello”, “HelloWorld”, “Hollis”);strings = strings.stream().filter(string -> string.startsWith(“Hollis”)).collect(Collectors.toList());System.out.println(strings);//Hollis, HollisChuang, Hollis666, Hollis接下来,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会,在分别使用不同的最终操作可以得到怎样的结果:下图,展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。 总结本文介绍了Java 8中的Stream 的用途,优点等。还接受了Stream的几种用法,分别是Stream创建、中间操作和最终操作。Stream的创建有两种方式,分别是通过集合类的stream方法、通过Stream的of方法。Stream的中间操作可以用来处理Stream,中间操作的输入和输出都是Stream,中间操作可以是过滤、转换、排序等。Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流装换成集合、以及元素的遍历等。本文作者:hollischuang阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 14, 2019 · 1 min · jiezi

利用有序高效实施交并差集合运算

【摘要】 看起来很简单的集合运算放在大数据的场景下,如果还想获得高性能就需要充分了解数据特征和计算特征才能设计出高效算法。充分利用序运算就是一种好办法!不妨去乾学院看看:利用有序高效实施交并差集合运算 交并差是常见的集合运算,SQL 中对应的 intersect/union/minus 计算也很简单。不过当数据量较大时,这类集合运算性能往往偏低,尤其当参与计算的数据量超过内存容量时,性能表现会十分糟糕。 本文专门针对这种情况下的高性能计算(HPC)需求,讨论如何使用集算器 SPL 语言通过有序计算思路显著提高大数据量下交并差三类集合运算的性能。下面讨论中使用了一个实际用户在数据库选型时的评测用例:数据基于数据库的 2 个表,共计 105 亿行数据,执行相关运算后,以输出第一批 500 条记录所用时间来衡量哪个数据库性能更优。数据描述索引样例数据a1-a52 列值:2018-01-07 00:00:00,8888888888,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa8,ppppaaaavv,gggggttttt2018-01-07 00:00:00,4444444444,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR2018-01-07 00:00:00,9999999999,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv–88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP测试用例l 交集(intersect)select * from A, B where a1>‘2018-01-07 02:14:43’ and a1 < ‘2018-01-07 04:14:43’ and a3=b1 or a7 = b2intersectselect * from A, B where a1>‘2018-01-07 12:14:43’ and a1 < ‘2018-01-07 14:14:43’ and a3=b1 or a7=b2l 并集(union)select * from A, B where a1>‘2018-01-07 02:14:43’ and a1 < ‘2018-01-07 04:14:43’ and a3=b1 or a7 = b2unionselect * from A, B where a1>‘2018-01-07 12:14:43’ and a1 < ‘2018-01-07 14:14:43’ and a3=b1 or a7=b2l 差集(minus)select * from A, B where a1>‘2018-01-07 02:14:43’ and a1 < ‘2018-01-07 04:14:43’ and a3=b1 or a7 = b2minusselect * from A, B where a1>‘2018-01-07 12:14:43’ and a1 < ‘2018-01-07 14:14:43’ and a3=b1 or a7=b2用例分析 分析上述 SQL 可以发现,此计算场景为大数据量的多对多集合运算。查询条件的前半段(a1>‘2018-01-07 02:14:43’ and a1 < ‘2018-01-07 04:14:43’ and a3=b1)是 A 表 2 个小时内的数据与 B 表进行多对多关联;而后半段(or a7 = b2)则是 A 表全量数据和 B 表进行多对多关联。因此,这个用例主要考察的是大表 A 和小表 B 多对多关联后的集合运算性能。 实测时,该 SQL 使用 MPP 数据库得不到查询结果(运行时间超过 1 小时),因为数据量很大,内存无法容纳全部数据,从而造成数据库在运算时频繁进行磁盘交互,导致整体性能极低。 按照我们一贯的思路,要实施高性能计算必须设计符合数据特征和计算特征的算法,而不是简单地使用通用的算法。这里,为了避免过多的磁盘交互(这也是大数据规模计算的首要考虑目标),最好只遍历一次 A 表就能完成计算。观察数据可以发现,A 表包含时间字段(a1),而且在时间字段(a1)和关联字段(a3、a7)上均建有索引,同样 B 表的两个字段(b1、b2)也建有索引,这样,我们就可以设计出这样的算法:1) 根据 A 表数据生成的特点,逐秒读取 A 表数据(每秒 24000 条);2) 针对每秒的数据循环处理,根据过滤条件逐条与 B 表关联,返回关联后结果;3) 对两部分数据,即用于交并差的两个集合进行集合运算。通过以上三步就可以完成全部计算,而整个过程中对 A 表只遍历了 2 次(分别得到用于交并差的两个集合)。当然,整个过程中由于数据量太大,集算器将通过延迟游标的方式进行归并,游标归并时数据需要事先排序,所以在 1)和 2) 步之间还需要对每秒的 24000 条数据按照关联字段和其他字段排序,会产生一些额外的开销。下面是具体的集算器 SPL 脚本。SPL 实现 这里分主子两个程序,主程序调用子程序分别获得交 / 并 / 差运算的两个集合并进行集合运算,子程序则根据参数计算集合,也就是说用例中的交并差三类计算可以使用同一个子程序脚本。子程序脚本(case1_cursor.dfx)A1:在 otherCols 中记录 A 表 52 个字段中除参与运算的 a1,a3,a7 外其他所有字段名称,用于生成 SQL 查询A2:连接数据库A3:SQL 语句串,用于根据条件查询 A 表所有列数据A4:查询 B 表数据,针对 b1,b2 进行分组计数(以便在后续计算中减少比较次数),并按 b1,b2 排序(用于后续有序归并)A5:按照 5 天时间内的秒数进行循环B5:每次循环中在起始时间(2018-01-07 00:00:00)上加相应的秒数,查询那一秒产生的数据(24000 条)B6:按照关联字段以及其他字段排序B7:循环处理一秒内的每条 A 表数据C7:根据单条 A 表数据,在 B 表中查找符合条件的记录C8:返回计算后包含 A 表和 B 表所有字段值的结果集,这里使用了 A.news() 函数,用来计算得到序表 / 排列的字段值合并生成的新序表 / 排列,具体用法请参考http://doc.raqsoft.com.cn/esproc/func/news.html主程序脚本交集(intersect)A1,A2:通过 cursor()函数调用子程序脚本,并传入 2 个时间段参数;cursor() 函数原理请参考:《百万级分组大报表开发与呈现》A3:根据子程序返回的游标序列进行归并,使用 @i 选项完成交集运算A4:从游标中取出 500 条记录,并关闭游标(@x 选项)并集(union)A3:使用 @u 选项完成并集计算,其他 SPL 脚本完全相同差集(minus)A3:使用 @d 选项完成并集计算,其他 SPL 脚本完全相同性能表现下表对集算器 SPL 和数据库 SQL 分别输出第一个 500 条结果集的时间进行了比较:显然,交集和并集计算的性能得到了极大的提升。为什么差集运算很慢?差集运算依然很慢的原因是由数据特征所决定的。由于多对多关联后重复记录较多,要计算出符合条件的差集仍旧要遍历完 A 表(而另外两个计算获得 500 条结果集就可以不再遍历了),因此性能主要消耗在 IO 取数上。总结高性能算法需要根据数据和计算特征进行针对性设计,这要求程序猿首先能够想出高性能算法,然后以不太复杂的手段加以实现,否则就没有可行性了。对于 SQL 体系来说,由于其封闭性原因,一些高效算法可能即使能设计出来也很难,甚至无法实现。而集算器 SPL 则极大地改善了这个问题,使用者可以在设计出高性能算法后,基于 SPL 体系快速实现。 ...

March 8, 2019 · 2 min · jiezi

SQL 难点解决:记录的引用

【摘要】 SQL 虽然是针对记录的集合进行运算, 但在记录的多次利用以及有序运算却经常要重复计算,效率不佳。而集算器 SPL 则要直观许多,可以按自然思维习惯写出运算。这里对 SQL 和集算器 SPL 在记录的利用及有序运算方面进行了对比,如果需要了解更多,请前往乾学院:SQL 难点解决:记录的引用!1、 求最大值 / 最小值所在记录 示例 1 :计算招商银行 (600036)2017 年收盘价达到最低价时的所有交易信息。 MySQL8: with t as (select * from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’) select * from t where close=(select min(close) from t); 集算器SPL: A3: 计算 A2 中 close 为最小值的所有记录示例 2:计算招商银行 (600036)2017 年最后的最低价和最早的最高价相隔多少自然日 MySQL8: with t as (select , row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’), t1 as (select * from t where close=(select min(close) from t)), t2 as (select * from t where close=(select max(close) from t)), t3 as (select * from t1 where rn=(select max(rn) from t1)), t4 as (select * from t2 where rn=(select min(rn) from t2)) select abs(datediff(t3.tdate,t4.tdate)) inteval from t3,t4; 集算器SPL: A3: 从后往前查找 close 第 1 个最小值的记录 A4: 从前往后查找 close 第 1 个最大值的记录 2、 查找满足条件的记录 示例 1:计算招商银行 (600036)2017 年收盘价超过 25 元时的交易信息 MySQL8: with t as (select * from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’) select * from t where tdate=(select min(tdate) from t where close>=25); 集算器SPL: A3: 从前往后查找收盘价超过25元的第1条记录 示例 2:计算招商银行 (600036) 上一周的涨幅(考虑停牌) MySQL8: with t1 as (select * from stktrade where sid=‘600036’), t11 as (select max(tdate) tdate from t1), t2 as (select subdate(tdate, weekday(tdate)+3)m from t11), t3 as (select max(tdate) m from t1,t2 where t1.tdate<=t2.m), t4 as (select subdate(m, weekday(m)+3)m from t3), t5 as (select max(tdate) m from t1,t4 where t1.tdate<=t4.m) select s1.close/s2.close-1 from (select * from t1,t3 where t1.tdate=t3.m) s1, (select * from t1,t5 where t1.tdate=t5.m) s2; 集算器SPL: A3: 求最后1个交易日所在周的周日(周日为一周的第一天) A4: 从后往前查找上周5以前的第1条记录,即上一交易周的最后一条记录 A5: 求上一个交易周的周日 A6: 从后往前查找上一个交易周的前一个周5的第1第记录,即上上交易周的最后一条记录 示例 3:重叠部分不重复计数时求多个时间段包含的总天数 MySQL8: with t(start,end) as ( select date'2010-01-07’,date'2010-01-9’ union all select date'2010-01-15’,date'2010-01-16’ union all select date'2010-01-07’,date'2010-01-12’ union all select date'2010-01-08’,date'2010-01-11’), t1 as (select , row_number() over(order by start,end desc) rn from t), t2 as (select * from t1 where not exists(select * from t1 s where s.rn=t1.end)) select sum(end-start+1) from t2; 集算器SPL: A3: 按起始时间升序、结束时间降序进行排序 A4: 选取结束时间比前面所有记录的结束时间都要晚的记录 A5: 计算总天数,max(start,end[-1])选起始时间和上一个结束时间较大者,interval计算2个日期相差天数 注:A4也可以改成 =A3.run(end=max(end,end[-1])) 示例 3:列出超 42% 人口使用的语言有 2 种以上的国家里使用人口超 42% 的语言的相关信息 MySQL8: with t as (select * from world.countrylanguage where percentage>=42), t1 as (select countrycode, count() cnt from t group by countrycode having cnt>=2) select t. from t join t1 using (countrycode); 集算器SPL: A3: 按国家编码分组 A4: 对成员数超过2个的组求和集 3、 求前 n 个表达式值最小的记录 示例 1:计算招商银行 (600036)2017 年成交量最大的 3 天交易信息 MySQL8: select * from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’ order by volume desc limit 3; 集算器SPL: A3: 根据-volume排序,然后取前 3 条记录 示例 2:计算招商银行 (600036) 最近 1 天的涨幅 MySQL8: with t as (select *, row_number() over(order by tdate desc) rn from stktrade where sid=‘600036’) select t1.close/t2.close-1 rise from t t1 join t t2 where t1.rn=1 and t2.rn=2; 集算器SPL: A3: 按交易日期倒序取最后 2 条记录 (效果等同于 A2.top(2;-tdate)),最后一天的交易记录序号为 1,倒数第 2 天的交易记录序号为 2 A4: 计算涨幅 示例 3:计算每个国家最大城市中人口前 5 的城市的相关信息 MySQL8: with t as (select *,row_number() over(partition by countrycode order by population desc) rn from world.city), t1 as (select id,name,countrycode,district,population from t where rn=1) select * from t1 order by population desc limit 5; 集算器SPL: A3: 按国家分组,分组返回人口最多的城市的记录 A4: 取所有国家最大城市中人口前 5 的城市记录 4、 外键引用记录 示例 1:计算亚洲和欧洲人口前 3 城市的相关信息 MySQL8: with t as ( select co.Continent, co.name CountryName, ci.name CityName, ci.Population, row_number()over(partition by continent order by population desc) rn from world.country co join world.city ci on co.code=ci.countrycode where continent in (‘Asia’,‘Europe’) ) select Continent, group_concat(cityname,’,’,countryname, ‘,’, population order by population desc separator ‘;’) Cities from t where rn<=3 group by continent; 集算器SPL: A4: 将 A2 中序表的键设为 Code 字段 A5: 将 A3 中序表 CountryCode 字段转换为 A2 中相应记录,无对应记录时删除 A6: 先根据 Continent 分组,再计算每组人口前 3 的城市,然后将每条记录中的城市名称、国家名称和人口拼成串,最后将每组中的串相连 示例 2:以“上级姓名 / 下级姓名”的形式返回指定雇员的所有上级 MySQL8: with recursive emp(id,name,manager_id) as ( select 29,‘Pedro’,198 union all select 72,‘Pierre’,29 union all select 123,‘Adil’, 692 union all select 198,‘John’,333 union all select 333,‘Yasmina’,null union all select 692,‘Tarek’, 333 ), t2(id,name,manager_id,path) as( select id,name,manager_id,cast(name as char(400) ) from emp where id=(select manager_id from emp where id=123) union all select t1.id,t1.name, t1.manager_id, concat(t1.name,’/’,t2.path) from t2 join emp t1 on t2.manager_id=t1.id) select path from t2 where manager_id is null; 集算器SPL: A3: 将manager_id转换成A2中与manager_id相等的id所在的记录 A4: 查找id为123的记录 A5: 依次列出A4上级、上级的上级、……,直到最高上级(即manager_id为null) A6: 将所有上级按从最高上级到最下上级排列,然后将所有上级的姓名用/分隔相连 ...

February 13, 2019 · 4 min · jiezi

SQL 难点解决:集合及行号

【摘要】SQL 虽然有集合概念,但对于集合运算、特别是有序集合运算,提供的支持却很有限,经常要采用很费解的思路才能完成,计算效率也不佳。而集算器 SPL 在方面则要直观许多,可以按自然思维习惯写出运算。这里对 SQL 和集算器 SPL 在集合运算和行号相关运算方面进行了对比,如果需要了解更多,请前往乾学院:SQL 难点解决:集合及行号!1、 和集示例 1:重叠部分不重复计数时求多个时间段包含的总天数MySQL8:with recursive t(start,end) as (select date'2010-01-07’,date'2010-01-9’union all select date'2010-01-15’,date'2010-01-16’union all select date'2010-01-07’,date'2010-01-12’union all select date'2010-01-08’,date'2010-01-11’),t1(d,end) as (select start,end from tunion all select d+1,end from t1 where dselect count(distinct d) from t1;说明:此例先将各时间段转成时间段内所有日子对应的日期,然后再求不同日期的个数集算器SPL:A3: 对 A2 中的每一个时间段构造从 start 到 end 的日期序列A4: 求 A3 中所有日期序列的和A5: 求 A4 中不重复日期的个数2、 差集示例 1:列出英语人口和法语人口均超过 5% 的国家MySQL8:with t1(lang) as (select ‘English’ union all select ‘French’)select name from world.country cwhere not exists(select * from t1 where lang not in (select language from world.countrylanguage where percentage>=5 and countrycode=c.code));说明:此SQL只是演示通过双重否定实现差集为空集算器SPL:A4: 选出[“English”,”French”]与本组语言集合的差为空的组,意思就是选出语言集合包含English和French的组3、 交集示例 1:列出英语人口、法语人口、西班牙语人口分别超过 0.3%、0.2%、0.1% 的国家代码MySQL8:with t1 as (select countrycode from world.countrylanguage where language=‘English’ and percentage>0.3),t2 as (select countrycode from world.countrylanguage where language=‘French’ and percentage>0.2),t3 as (select countrycode from world.countrylanguage where language=‘Spanish’ and percentage>0.1)select countrycodefrom t1 join t2 using(countrycode) join t3 using(countrycode);说明:此例只是演示如何求解多个集合的交集集算器SPL:A3: 按次序依次查询英语人口超0.3%、法语人口超0.2%、西班牙语超0.1%的国家代码,并转成序列A5: A3中所有序列交集4、 根据行号取数据示例 1:计算招商银行 (600036) 2017 年第 3 个交易日和倒数第 3 个交易日的交易信息MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’)select tdate,open,close,volume from t where rn=3union allselect tdate,open,close,volume from t where rn=(select max(rn)-2 from t);集算器SPL:A3: 第 3 条记录和倒数第 3 条记录的和集示例2:计算招商银行(600036)最近20个交易日的平均收盘价MySQL8:with t as (select *, row_number() over(order by tdate desc) rn from stktrade where sid=‘600036’)select avg(close) avg20 from t where rn<=20;集算器SPL:A2: 将600036的交易记录按日期排序A3: 取从倒数20条到末尾的所有记录A4: 求A3中所有记录收盘价的平均值5、 求满足条件的记录的行号示例 1:计算招商银行 (600036)2017 年经过多少交易日收盘价达到 25 元MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’)select min(rn) from t where close>=25;集算器SPL:A3: 从前往后查找第 1 个收盘价达到 25 元的记录位置示例2:计算格力电器(000651) 2017年涨幅(考虑停牌)MySQL8:with t as (select * from stktrade where sid=‘000651’),t1(d) as (select max(tdate) from t where tdate<‘2017-01-01’),t2(d) as (select max(tdate) from t where tdate<‘2018-01-01’)select s2.close/s1.close-1 risefrom (select * from t,t1 where tdate=d) s1,(select * from t,t2 where tdate=d) s2;集算器SPL:A2: 数据按交易日从小到大排序A3: 从后往前查找交易日在2017-01-01之前的最后一条记录在序列中的行号A4: 求2016年收盘价A5: 求2017年收盘价,其中A2.m(-1)取倒数第1条记录,即2017年最后一个交易日对应的记录示例3:列出2017年信息发展(300469)交易量超过250万股时的交易信息及各日涨幅(考虑停牌)MySQL8:with t as (select *, row_number() over(order by tdate) rnfrom stktrade where sid=‘300469’ and tdate<=date ‘2017-12-31’),t1 as (select * from t where tdate>=date'2017-01-01’ and volume>=2500000)select t1.tdate, t1.close, t.volume, t1.close/t.close-1 risefrom t1 join t on t1.rn=t.rn+1;集算器SPL:A3: 求出2017年交易量超250万股所有记录的行号A4: 根据行号计算相应的日期、收盘价、交易量、涨幅6、 求最大值或最小值所在记录的行号示例 1:计算招商银行 (600036) 2017 年最早的最低价与最早的最高价间隔多少交易日MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’),t1 as (select * from t where close=(select min(close) from t)),t2 as (select * from t where close=(select max(close) from t))select abs(cast(min(t1.rn) as signed)-cast(min(t2.rn) as signed)) intevalfrom t1,t2;集算器SPL:A3: 从前往后找最大收盘价在序列中的行号A4: 从前往后找最小收盘价在序列中的行号示例2:计算招商银行 (600036) 2017 年最后的最低价与最后的最高价间隔多少交易日MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’),t1 as (select * from t where close=(select min(close) from t)),t2 as (select * from t where close=(select max(close) from t))select abs(cast(max(t1.rn) as signed)-cast(max(t2.rn) as signed)) intevalfrom t1,t2;集算器SPL:A3: 从后往前找最大收盘价在序列中的行号A4: 从后往前找最小收盘价在序列中的行号7、 有序集合间的对位计算示例 1:求 2018 年 3 月 6 日到 8 日创业板指 (399006) 对深证成指 (399001) 的每日相对收益率MySQL8:with t1 as (select *,close/lag(close) over(order by tdate) rise from stktrade where sid=‘399006’ and tdate between ‘2018-03-05’ and ‘2018-03-08’),t2 as (select *, close/lag(close) over(order by tdate) rise from stktrade where sid=‘399001’ and tdate between ‘2018-03-05’ and ‘2018-03-08’)select t1.rise-t2.risefrom t1 join t2 using(tdate)where t1.rise is not null;集算器SPL:A2: 依次查询399006和399001从2018年3月5日到8日的交易数据A4: 依次计算A2中2个序表从第2条记录到第4条记录的涨幅,也就是399006和399001从2018年3月6日到8日的每天涨幅A5: 对位相减,即可算出每日相对收益率 ...

January 23, 2019 · 3 min · jiezi

「 深入浅出 」集合Set

系列文章「 深入浅出 」集合List 「 深入浅出 」java集合Collection和MapSet继承自Collection接口,不能包含有重复元素。本篇文章主要讲Set中三个比较重要的实现类:HashSet、TreeSet。SetSet是一个存储无序且不重复元素的集合。在使用Set集合的时候,应该注意两点为Set集合里的元素的实现类重写equals()和hashCode()方法()若传入重复的元素,重复元素会被忽略(可以用于做集合的去重)扩展判断两个元素相等的标准:两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。HashSetHashSet是Set接口的典型实现,是哈希表结构,主要利用HashMap的key来存储元素,计算插入元素的hashCode来获取元素在集合中的位置,因此具有很好的存取和查找性能。主要特点1.不允许出现重复元素2.存储的元素是无序的3.不是同步的,如果多个线程同时访问一个HashSet,则必须通过代码来保证其同步。4.集合元素值可以是null。HashSet基本操作public class HashSetTest { public static void main(String[] agrs){ //创建HashSet集合: Set<String> hashSet = new HashSet<String>(); System.out.println(“HashSet初始容量大小:"+hashSet.size()); //元素添加: hashSet.add(“my”); hashSet.add(“name”); hashSet.add(“is”); hashSet.add(“ken”); hashSet.add(“hello”); hashSet.add(“everyone”); System.out.println(“HashSet容量大小:"+hashSet.size()); //迭代器遍历: Iterator<String> iterator = hashSet.iterator(); while (iterator.hasNext()){ String str = iterator.next(); System.out.print(str+” “); } System.out.print("\n”); //元素删除: hashSet.remove(“ken”); System.out.println(“HashSet元素大小:” + hashSet.size()); hashSet.clear(); System.out.println(“HashSet元素大小:” + hashSet.size()); //集合判断: boolean isEmpty = hashSet.isEmpty(); System.out.println(“HashSet是否为空:” + isEmpty); boolean isContains = hashSet.contains(“hello”); System.out.println(“HashSet是否为空:” + isContains); }}//out:HashSet初始容量大小:0HashSet容量大小:6ken everyone name is hello my HashSet元素大小:5HashSet元素大小:0HashSet是否为空:trueHashSet是否为空:false细节注意事项可看以下例子://重写类A的equals方法总是返回true,但没有重写其hashCode()方法。//不能保证当前对象是HashSet中的唯一对象class A { @Override public boolean equals(Object obj) { return true; }}//重写类B的hashCode()方法总是返回1,但没有重写其equals()方法。//不能保证当前对象是HashSet中的唯一对象class B { @Override public int hashCode() { return 1; }}//重写重写类C的hashCode()方法总是返回2,且有重写其equals()方法class C { @Override public int hashCode() { return 2; } @Override public boolean equals(Object obj) { return true; }}public class HashSetTest { public static void main(String[] args) { HashSet books = new HashSet(); //分别向books集合中添加两个A对象,两个B对象,两个C对象 books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); }}//out[B@1, B@1, C@2, A@3bc257, A@785d65]可以看出,只有当同时重写了equals方法和hashCode方法时,才能按照自己的意图判断对象是否相等,否则均判定为不相等,可加入Set集合中。TreeSet与HashSet集合类似,TreeSet也是基于Map来实现,其底层结构为红黑树(特殊的二叉查找树)与HashSet不同的是,TreeSet具有排序功能,分为自然排序(123456)和自定义排序两类,默认是自然排序具有如下特点:对插入的元素进行排序,是一个有序的集合(主要与HashSet的区别)底层使用红黑树结构,而不是哈希表结构允许插入Null值不允许插入重复元素线程不安全TreeSet基本操作public class TreeSetTest { public static void main(String[] agrs){ TreeSet<String> treeSet = new TreeSet<String>(); System.out.println(“TreeSet初始化容量大小:"+treeSet.size()); //元素添加: treeSet.add(“my”); treeSet.add(“name”); treeSet.add(“ken”); treeSet.add(“hello”); treeSet.add(“world”); treeSet.add(“1”); treeSet.add(“2”); treeSet.add(“3”); System.out.println(“TreeSet容量大小:” + treeSet.size()); System.out.println(“TreeSet元素顺序为:” + treeSet.toString()); System.out.println(“遍历元素升序:”); //迭代器遍历:升序 Iterator<String> iteratorAesc = treeSet.iterator(); while(iteratorAesc.hasNext()){ String str = iteratorAesc.next(); System.out.print(str + " “); } System.out.println("\n”); System.out.println(“遍历元素降序:”); //迭代器遍历:降序 Iterator<String> iteratorDesc = treeSet.descendingIterator(); while(iteratorDesc.hasNext()){ String str = iteratorDesc.next(); System.out.print(str + " “); } System.out.println("\n”); //元素获取:实现NavigableSet接口 String firstEle = treeSet.first();//获取TreeSet头节点: System.out.println(“TreeSet头节点为:” + firstEle); // 获取指定元素之前的所有元素集合:(不包含指定元素) SortedSet<String> headSet = treeSet.headSet(“ken”); System.out.println(“ken节点之前的元素为:"+headSet.toString()); //获取给定元素之间的集合:(包含头,不包含尾) SortedSet subSet = treeSet.subSet(“1”,“hello”); System.out.println(“1–hello之间节点元素为:"+subSet.toString()); //集合判断: boolean isEmpty = treeSet.isEmpty(); System.out.println(“TreeSet是否为空:"+isEmpty); boolean isContain = treeSet.contains(“who”); System.out.println(“TreeSet是否包含who元素:"+isContain); //元素删除: boolean kenRemove = treeSet.remove(“ken”); System.out.println(“ken元素是否被删除”+kenRemove); //集合中不存在的元素,删除返回false boolean whoRemove = treeSet.remove(“who”); System.out.println(“who元素是否被删除”+whoRemove); //删除并返回第一个元素:如果set集合不存在元素,则返回null String pollFirst = treeSet.pollFirst(); System.out.println(“删除的第一个元素:"+pollFirst); //删除并返回最后一个元素:如果set集合不存在元素,则返回null String pollLast = treeSet.pollLast(); System.out.println(“删除的最后一个元素:"+pollLast); treeSet.clear();//清空集合 }}//out:TreeSet初始化容量大小:0TreeSet容量大小:8TreeSet元素顺序为:[1, 2, 3, hello, ken, my, name, world]遍历元素升序:1 2 3 hello ken my name world 遍历元素降序:world name my ken hello 3 2 1 TreeSet头节点为:1ken节点之前的元素为:[1, 2, 3, hello]1–hello之间节点元素为:[1, 2, 3]TreeSet是否为空:falseTreeSet是否包含who元素:falsejiaboyan元素是否被删除falsewho元素是否被删除false删除的第一个元素:1删除的最后一个元素:worldTreeSet元素排序我们讲到了TreeSet是一个有序集合,可以对集合元素排序,其中分为自然排序和自定义排序自然排序(正序与反序)public class TreeSetTest { public static void main(String[] agrs){ TreeSet<String> treeSetString = new TreeSet<String>(); treeSetString.add(“a”); treeSetString.add(“z”); treeSetString.add(“d”); treeSetString.add(“b”); System.out.println(“字母正序:” + treeSetString.toString()); System.out.println(“字母反序:” + treeSetString.descendingSet().toString()); TreeSet<Integer> treeSetInteger = new TreeSet<Integer>(); treeSetInteger.add(1); treeSetInteger.add(24); treeSetInteger.add(23); treeSetInteger.add(6); System.out.println(“数字正序:” + treeSetInteger.toString()); System.out.println(“数字反序:” + treeSetInteger.descendingSet().toString()); }}//out字母顺序:[a, b, d, z]数字顺序:[1, 6, 23, 24]自定义排序当使用的是自己定义的类时,就需要做一些特殊处理,否则会报错Exception in thread “main” java.lang.ClassCastException,有两种实现方式1.实现Comparable接口public class Person implements Comparable<Person>{ private String name; private Integer age; public Person(){} public Person(String name,Integer age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public int compareTo(Person person) { //如果name长度一样,则比较年龄的大小 //需要考虑相等的情况,否则相等的情况只显示先加入集合的元素 if(this.age.equals(person.age)){ return 1; } return this.age - person.age; } @Override public String toString() { return “Person{” + “name=’” + name + ‘'’ + “, age=” + age + ‘}’; } public static void main(String[] agrs){ TreeSet<Person> treeSet = new TreeSet<Person>(); //排序对象: Person p1 = new Person(“ken”,18); Person p2 = new Person(“xiaoming”,15); Person p3 = new Person(“laowang”,15); Person p4 = new Person(“zhangsan”,25); //添加到集合: treeSet.add(p1); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); System.out.println(“TreeSet集合顺序为:"+treeSet); }}//out:TreeSet集合顺序为:[Person{name=‘xiaoming’, age=15}, Person{name=‘laowang’, age=15}, Person{name=‘ken’, age=18}, Person{name=‘zhangsan’, age=25}]2.实现Comparetor<t style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; font-size: inherit; color: inherit; line-height: inherit;">接口,并重写compare方法</t>//自定义Person类的比较器:public class PersonComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { //比较名字长度,从大到小排序 //需要考虑相等的情况,否则相等的情况只显示先加入集合的元素 if(p2.getName().length() == p1.getName().length()){ return 1; } return p2.getName().length() - p1.getName().length(); }}测试程序public static void main(String[] agrs){ TreeSet<Person> treeSet = new TreeSet<Person>(new PersonComparator()); //排序对象: Person p1 = new Person(“ken”,18); Person p2 = new Person(“xiaoming”,15); Person p3 = new Person(“laowang”,15); Person p4 = new Person(“zhangsan”,25); //添加到集合: treeSet.add(p1); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); System.out.println(“TreeSet集合顺序为:"+treeSet); }//outTreeSet集合顺序为:[Person{name=‘xiaoming’, age=15}, Person{name=‘zhangsan’, age=25}, Person{name=‘laowang’, age=15}, Person{name=‘ken’, age=18}]后续文章将对java集合中的具体实现类进行深入了解。有兴趣的话可以观看后续内容,进一步了解java集合内容。推荐阅读: 个人网站模板推荐如何在GitHub上大显身手?IDEA热部署插件JRebel坚持日更:7天您的点赞、转发是对我最大的支持! THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 3 min · jiezi

leetcode讲解--575. Distribute Candies

题目Given an integer array with even length, where different numbers in this array represent different kinds of candies. Each number means one candy of the corresponding kind. You need to distribute these candies equally in number to brother and sister. Return the maximum number of kinds of candies the sister could gain.Example 1:Input: candies = [1,1,2,2,3,3]Output: 3Explanation:There are three different kinds of candies (1, 2 and 3), and two candies for each kind.Optimal distribution: The sister has candies [1,2,3] and the brother has candies [1,2,3], too. The sister has three different kinds of candies. Example 2:Input: candies = [1,1,2,3]Output: 2Explanation: For example, the sister has candies [2,3] and the brother has candies [1,1]. The sister has two different kinds of candies, the brother has only one kind of candies. Note:The length of the given array is in range [2, 10,000], and will be even.The number in given array is in range [-100,000, 100,000].题目地址讲解要让妹妹的蜡烛种类尽量多,很简单,尝试把每种不同的蜡烛都塞给妹妹,但她最多只能拿一半。java代码class Solution { public int distributeCandies(int[] candies) { Set<Integer> set = new HashSet<>(); for(int x:candies){ set.add(x); } int result=0; if(set.size()>candies.length/2){ result = candies.length/2; }else{ result = set.size(); } return result; }} ...

January 2, 2019 · 1 min · jiezi

leetcode讲解--893. Groups of Special-Equivalent Strings

题目You are given an array A of strings.Two strings S and T are special-equivalent if after any number of moves, S == T.A move consists of choosing two indices i and j with i % 2 == j % 2, and swapping S[i] with S[j].Now, a group of special-equivalent strings from A is a non-empty subset S of A such that any string not in S is not special-equivalent with any string in S.Return the number of groups of special-equivalent strings from A.Example 1:Input: [“a”,“b”,“c”,“a”,“c”,“c”]Output: 3Explanation: 3 groups [“a”,“a”], [“b”], [“c”,“c”,“c”]Example 2:Input: [“aa”,“bb”,“ab”,“ba”]Output: 4Explanation: 4 groups [“aa”], [“bb”], [“ab”], [“ba”]Example 3:Input: [“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]Output: 3Explanation: 3 groups [“abc”,“cba”], [“acb”,“bca”], [“bac”,“cab”]Example 4:Input: [“abcd”,“cdab”,“adcb”,“cbad”]Output: 1Explanation: 1 group [“abcd”,“cdab”,“adcb”,“cbad”]Note:1 <= A.length <= 10001 <= A[i].length <= 20All A[i] have the same length.All A[i] consist of only lowercase letters.讲解这道题我刚开始又没看懂,我以为是数组是一个字符串,对这个数组进行奇偶位的swap。后来终于懂了,是对数组中的每个字符串进行奇偶位的swap。Java代码class Solution { public int numSpecialEquivGroups(String[] A) { Set<List> set = new HashSet<>(); for(String s:A){ char[] c = s.toCharArray(); List<Character> temp1 = new ArrayList<>(); if(c.length>2){ List<Character> temp2 = new ArrayList<>(); for(int i=0;i<c.length;i+=2){ temp1.add(c[i]); } Collections.sort(temp1); for(int i=1;i<c.length;i+=2){ temp2.add(c[i]); } Collections.sort(temp2); temp1.addAll(temp2); }else{ for(int i=0;i<c.length;i++){ temp1.add(c[i]); } } set.add(temp1); } return set.size(); }} ...

December 30, 2018 · 1 min · jiezi

通俗易懂,JDK 并发容器总结

该文已加入开源项目:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识的文档类项目,Star 数接近 14 k)。地址:https://github.com/Snailclimb…一 JDK 提供的并发容器总结实战Java高并发程序设计》为我们总结了下面几种大家可能会在高并发程序设计中经常遇到和使用的 JDK 为我们提供的并发容器。先带大家概览一下,下面会一一介绍到。JDK提供的这些容器大部分在 java.util.concurrent 包中。ConcurrentHashMap: 线程安全的HashMapCopyOnWriteArrayList: 线程安全的List,在读多写少的场合性能非常好,远远好于Vector.ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。BlockingQueue: 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。ConcurrentSkipListMap: 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。二 ConcurrentHashMap我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 Collections.synchronizedMap() 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。关于 ConcurrentHashMap 相关问题,我在 《这几道Java集合框架面试题几乎必问》 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题:ConcurrentHashMap 和 Hashtable 的区别ConcurrentHashMap线程安全的具体实现方式/底层具体实现三 CopyOnWriteArrayList3.1 CopyOnWriteArrayList 简介public class CopyOnWriteArrayList<E>extends Objectimplements List<E>, RandomAccess, Cloneable, Serializable在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。这和我们之前在多线程章节讲过 ReentrantReadWriteLock 读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 CopyOnWriteArravList 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,CopyOnWriteArravList 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。那它是怎么做的呢?3.2 CopyOnWriteArravList 是如何做到的?CopyOnWriteArravList 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。从 CopyOnWriteArravList 的名字就能看出CopyOnWriteArravList 是满足CopyOnWrite 的ArrayList,所谓CopyOnWrite 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。3.3 CopyOnWriteArravList 读取和写入源码简单分析3.3.1 CopyOnWriteArravList 读取操作的实现读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全。 /** The array, accessed only via getArray/setArray. / private transient volatile Object[] array; public E get(int index) { return get(getArray(), index); } @SuppressWarnings(“unchecked”) private E get(Object[] a, int index) { return (E) a[index]; } final Object[] getArray() { return array; }3.3.2 CopyOnWriteArravList 写入操作的实现CopyOnWriteArravList 写入操作 add() 方法在添加集合的时候加了锁,保证了同步,避免了多线程写的时候会 copy 出多个副本出来。 /* * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) / public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock();//加锁 try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝新数组 newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock();//释放锁 } }四 ConcurrentLinkedQueueJava提供的线程安全的 Queue 可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。从名字可以看出,ConcurrentLinkedQueue这个队列使用链表作为其数据结构.ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能,是因为其内部复杂的实现。ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。五 BlockingQueue5.1 BlockingQueue 简单介绍上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类:下面主要介绍一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,这三个 BlockingQueue 的实现类。5.2 ArrayBlockingQueueArrayBlockingQueue 是 BlockingQueue 接口的有界队列实现类,底层采用数组来实现。ArrayBlockingQueue一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);5.3 LinkedBlockingQueueLinkedBlockingQueue 底层基于单向链表实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足FIFO的特性,与ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于Integer.MAX_VALUE。相关构造方法: /* 某种意义上的无界队列 * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. / public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } / *有界队列 * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. * * @param capacity the capacity of this queue * @throws IllegalArgumentException if {@code capacity} is not greater * than zero */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); }5.4 PriorityBlockingQueuePriorityBlockingQueue 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。PriorityBlockingQueue 并发控制采用的是 ReentrantLock,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容)。简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。推荐文章:《解读 Java 并发队列 BlockingQueue》https://javadoop.com/post/java-concurrent-queue六 ConcurrentSkipListMap下面这部分内容参考了极客时间专栏《数据结构与算法之美》以及《实战Java高并发程序设计》。为了引出ConcurrentSkipListMap,先带着大家简单理解一下跳表。对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 O(logn) 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。跳表的本质是同时维护了多个链表,并且链表是分层的,最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的了集。跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素18。查找18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。从上面很容易看出,跳表是一种利用空间换时间的算法。使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。七 参考《实战Java高并发程序设计》https://javadoop.com/post/jav…https://juejin.im/post/5aeebd...ThoughtWorks准入职Java工程师。专注Java知识分享!开源 Java 学习指南——JavaGuide(12k+ Star)的作者。公众号多篇文章被各大技术社区转载。公众号后台回复关键字“1”可以领取一份我精选的Java资源哦! ...

December 10, 2018 · 2 min · jiezi