15、java中的集合(2)

       说一下单列集合,java中的单列集合的顶级接口是Collection,它有两个子接口:List、Set,本篇介绍一下List接口及其实现类的功能方法和基本实现原理。

       List集合是有序集合,这里的有序并不是指存入List集合的元素会被自动排序,而是指数据存储和数据读取的顺序是一样的,就是说按着1,2,3的顺序依次添加元素后,遍历集合时元素也会按着1,2,3的顺序进行打印。List集合不会对添加的元素进行排序,并且允许存储相同的元素。实现List接口的实现类有:ArrayList、LinkedList、Vector、Stack等,接下来详细的介绍一下一般常用的几个List集合。

       先说一下ArrayList,ArrayList集合的底层数据结构是数组,其实ArrayList就是依靠数组进行存储数据,但是这个数组可以进行扩容操作,所以才显得List集合的容量大小可变,正因为依赖数组,所以ArrayList集合具有查询快、增删慢的特点,另外ArrayList的底层实现并未用到同步机制,所以是线程不安全的集合,效率高。

         ArrayList集合中的功能方法在这里就不做介绍了,按着API测试即可,挺简单的。这里说一下它的底层实现,底层代码及分析如下:

//实现了RandomAccess接口说明可以快速随机访问
//实现了Cloneable接口说明可以被克隆
//实现了Serializable接口说明可以被序列化
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**********************************************成员变量****************************************************************///capacity 默认容量private static final int DEFAULT_CAPACITY = 10;//这个就是ArrayList底层维护的那个数组,用于存储数据用//elementData 不参与序列化,因为他本质上只是一个地址,而并不是数据transient Object[] elementData;//就是声明一个空数组,当实例化一个容量大小为0的ArrayList时就用EMPTY_ELEMENTDATA实例化elementDataprivate static final Object[] EMPTY_ELEMENTDATA = {};//当使用空构造方法创建一个集合实例时,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATAprivate static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//记录集合的大小private int size;//集合容量的最大值private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/**********************************************构造方法****************************************************************///实例化一个容量为initialCapacity的集合public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}//实例化一个空的集合public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//根据一个集合来实例化一个集合,顺序不变public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {if (elementData.getClass() != Object[].class)//类型不是Object[].class则复制一个elementData = Arrays.copyOf(elementData, size, Object[].class);} else {this.elementData = EMPTY_ELEMENTDATA;}}//类似于字符串的trim操作,就是将当前集合的容量缩减为这个集合实际含有有效元素的长度大小//正因为这个方法才使得集合的大小就是元素的个数public void trimToSize() {//modCount 是AbstractList中的属性,表示集合在结构上修改的次数。就是集合大小修改的次数//主要作用在于迭代集合元素时判断是否发生并发修改异常modCount++;if (size < elementData.length) {elementData = (size == 0)? EMPTY_ELEMENTDATA: Arrays.copyOf(elementData, size);}}/**********************************************添加元素****************************************************************///在集合的尾部添加一个元素public boolean add(E e) {//扩容ensureCapacityInternal(size + 1);elementData[size++] = e;//在尾部添加元素,并将集合的大小加一return true;}//容量检查private void ensureCapacityInternal(int minCapacity) {//如果是刚进来初始化的话,就将数组的长度设置成默认长度10if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}//如果集合容量已经比10大了,则检查是否需要扩容ensureExplicitCapacity(minCapacity);}//容量检查private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果  底层数组的长度<要填加元素到数组的下标    则进行扩容if (minCapacity - elementData.length > 0)//进行具体的扩容操作grow(minCapacity);}//扩容机制private void grow(int minCapacity) {// overflow-conscious code//先获取到当前数组的长度int oldCapacity = elementData.length;//新数组的长度 = 原来数组的长度 + 原来数组长度的一半   也就是传说中的扩容1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);//如果扩容1.5倍后还是不足以添加新的元素,则将新数组的容量设置成将要添加的数组下标if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//判断一下新的数组大小是否比集合的最大容量要大   if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 创建一个新数组,长度大小为newCapacity ,然后将原来的数组中的元素复制到新数组中// 在外界看来就像是数组扩容了,其实就是新创建了一个长度更大的数组而已elementData = Arrays.copyOf(elementData, newCapacity);}private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}/**********************************************删除元素****************************************************************///删除下标index处的元素public E remove(int index) {rangeCheck(index);//下标合法性检查modCount++;//获取都指定下标处的元素用于返回E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)//调用c或c++的方法将删除元素后的所有元素往前移一位System.arraycopy(elementData, index+1, elementData, index, numMoved);elementData[--size] = null;return oldValue;}//如果删除的元素下标>集合容量,报错private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}/**********************************获取指定元素在集合中第一次出现的下标*****************************************************/   public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i]==null)return i;} else {//就是循环遍历底层数组,只要碰到相同指定元素值的元素直接返回下标结束程序for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}return -1;}/**********************************获取指定下标处的元素*****************************************************/     public E get(int index) {rangeCheck(index);//下标合法性校验//返回元素return elementData(index);}/**********************************序列化和反序列化*****************************************************/   //序列化private void writeObject(ObjectOutputStream s) throws java.io.IOException{int expectedModCount = modCount;s.defaultWriteObject();s.writeInt(size);//将数组的每一个元素进行写入for (int i=0; i<size; i++) {s.writeObject(elementData[i]);}}//反序列化private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {elementData = EMPTY_ELEMENTDATA;s.defaultReadObject();s.readInt();if (size > 0) {ensureCapacityInternal(size);Object[] a = elementData;for (int i=0; i<size; i++) {a[i] = s.readObject();}}}/**********************************迭代集合*****************************************************/ public Iterator<E> iterator() {return new Itr();}//使用内部类实现Iterator接口的方式实现迭代器private class Itr implements Iterator<E> {// 下一个元素的下标int cursor;int lastRet = -1;int expectedModCount = modCount;//集合修改次数//是否还有下一个元素public boolean hasNext() {return cursor != size;}//获取下一个节点元素public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}//判断是否发生并发修改异常,使用迭代器遍历集合时,不能修改集合,然可以使用迭代器修改集合final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}
}

       再说一下另一个常用的集合LinkedList,它的底层数据结构是链表,依赖一个双向链表存储数据,具有增删快、查询慢的特点,代码分析如下:

//双向链表
//实现了Deque接口说明具有双向队列的特性
//实现了Cloneable接口说明可以被克隆
//实现了Serializable接口说明可以被序列化
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{//用一个静态内部来声明节点private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}}//集合大小transient int size = 0;//头结点transient Node<E> first;//尾节点transient Node<E> last;//构造一个集合public LinkedList() {}//根据已有集合构建一个LinkedList集合public LinkedList(Collection<? extends E> c) {this();addAll(c);}/**********************************************添加元素****************************************************************/    //直接用addAll(c)这个方法为例看public boolean addAll(Collection<? extends E> c) {return addAll(size, c);}//相当于添加多个结点public boolean addAll(int index, Collection<? extends E> c) {//index = size//检查传入的集合大小是否合理checkPositionIndex(index);Object[] a = c.toArray();//集合转成数组int numNew = a.length;//获取数组长度if (numNew == 0)//如果长度是0,没有元素,直接返回return false;//声明两个结点,前驱结点pred 、 后继结点succNode<E> pred, succ;if (index == size) {//这里true,index表示在哪里开始添加,如果添加下标和链表大小相同,则就是在末尾添加succ = null;//后继节点为空pred = last;//前驱节点指向链表中的最后一个结点} else {succ = node(index);//获取到index位置的结点,succ指向此节点,也就是新建结点的后继结点指向index位置的结点pred = succ.prev;//新建结点的前驱结点指向index位置结点的原来的前一个结点结点}for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o;Node<E> newNode = new Node<>(pred, e, null);//利用上面的前驱和后继结点创建一个结点,添加到链表if (pred == null)//前驱结点是null,链表中没结点first = newNode;//则初始化头节点elsepred.next = newNode;//让pred的后继结点指向新添加的节点,也就是index位置原先的前一个结点的后继结点指向新节点,大白话就是在index位置和index-1位置直接新添加一个结点pred = newNode;//然后让pred节点指向新添加的节点}if (succ == null) {//如果后继结点是空,初始化尾节点last = pred;} else {pred.next = succ;//pred的下一个节点指向succsucc.prev = pred;//succ的上一个节点指向pred}size += numNew;//链表中元素个数modCount++;//链表改变标记+1return true;}}  /**********************************************获取元素****************************************************************/      //获取到index处的结点  public E get(int index) {checkElementIndex(index);return node(index).item;}//获取元素时,需要挨个遍历,所以查询效率低。  Node<E> node(int index) {if (index < (size >> 1)) {//如果结点所在的位置在链表的前半段,则从前往后遍历//一直将指针指到index处,获取到结点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;}}/**********************************************删除元素****************************************************************/ //确定删除元素的结点public E remove(int index) {checkElementIndex(index);return unlink(node(index));}//根据结点中的前驱结点和后继结点删除(链表删除结点操作)E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;final Node<E> prev = x.prev;if (prev == null) {first = next;} else {prev.next = next;x.prev = null;}if (next == null) {last = prev;} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;}/**********************************************存在元素****************************************************************/ //就是遍历集合,然后依次比较返回相同元素所在的下标public int indexOf(Object o) {int index = 0;if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null)return index;index++;}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item))return index;index++;}}return -1;}}

再说一下这两个集合的遍历方式,代码如下:

1)for循环ArrayList<String> list = new ArrayList<>();list.add("1111");list.add("2222");list.add("3333");for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}
2)迭代器ArrayList<String> list = new ArrayList<>();list.add("1111");list.add("2222");list.add("3333");Iterator<String> iterator = list.iterator();while(iterator.hasNext()) {System.out.println(iterator.next());}
3)增强forArrayList<String> list = new ArrayList<>();list.add("1111");list.add("2222");list.add("3333");for (String string : list) {System.out.println(string);}---------------------------------------------------------------------------1)for循环LinkedList<String> list = new LinkedList<>();list.add("1111");list.add("2222");list.add("3333");for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}2)迭代器LinkedList<String> list = new LinkedList<>();list.add("1111");list.add("2222");list.add("3333");Iterator<String> iterator = list.iterator();while(iterator.hasNext()) {System.out.println(iterator.next());}3)增强forLinkedList<String> list = new LinkedList<>();list.add("1111");list.add("2222");list.add("3333");for (String string : list) {System.out.println(string);}

就先说一下这两个,其实List集合底层封装的都是数组或者链表,对数据结构有点了解的话很容易就理解了。

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

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

相关文章

P2467-[SDOI2010]地精部落【dp】

正题 题目链接:https://www.luogu.org/problem/P2467 题目大意 求长度为nnn的波动序列的个数。 解题思路 我们先考虑第一个是上升的&#xff0c;然后乘2即可。 设fi,jf_{i,j}fi,j​表示填1∼i1\sim i1∼i个&#xff0c;最前面的是jjj的个数。然后我们只要是1∼i−j11\sim i…

双向广搜 8数码问题

转载自&#xff1a;http://blog.sina.com.cn/s/blog_8627bf080100ticx.html Eight 题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1043讲到双向广搜&#xff0c;那就不能不讲经典的八数码问题&#xff0c;有人说不做此题人生不完整 。所谓双向广搜&am…

使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

Hypermedia As The Engine Of Application State (HATEOAS)HATEOAS&#xff08;Hypermedia as the engine of application state&#xff09;是 REST 架构风格中最复杂的约束&#xff0c;也是构建成熟 REST 服务的核心。它的重要性在于打破了客户端和服务器之间严格的契约&…

U94222-循环往复【tarjan,DAGdp】

正题 题目链接:https://www.luogu.org/problem/U94222?contestId23574 题目大意 nnn个点若条有向边,求一条路径要求 经过一个酒店经过权值最大消费最小起点编号最小 按照顺序满足。 解题思路 将强连通分量缩成一个点&#xff0c;然后用gi,0/1g_{i,0/1}gi,0/1​到达第iii个…

16、java中的集合(3)

说一下双列集合&#xff0c;顶级接口是Map&#xff0c;实现类有HashMap、LinkedHashMap、TreeMap、HashTable等&#xff0c;使用键值对的格式存储数据&#xff0c;键不可以重复&#xff0c;值可以重复。接下来对实现类做一下详细介绍。 HashMap是最常用的Map集合&#xff0c;它…

搜索训练1 [8数码问题]

HDU1043、以及POJ1077上面都有这道题目&#xff0c;可以说是搜索里的非常经典的题目了。 poj上面的数据真的是弱&#xff0c;由于只有一组数据&#xff0c;简单bfs直接就可以过掉。 前前后后捣鼓了能有6个小时&#xff0c;才把这道题目在HDU上以4500ms的微弱优势通过。。。。…

【招聘(北京)】.NETCORE开发工程师(微服务方向)

组织&#xff1a;华汽集团北京研发中心位置&#xff1a;北京市朝阳区焦奥中心官网&#xff1a;www.sinoauto.com邮箱&#xff1a;taoxu.weisinoauto.com 项目&#xff1a;打造面向国内汽车后市场用户的一站式云服务平台&#xff08;华汽云&#xff09;&#xff0c;形态包括B2B、…

2017西安交大ACM小学期数据结构 [分块,区间修改,单点查询]

Problem A 发布时间: 2017年6月28日 09:29 最后更新: 2017年6月28日 13:03 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a1, a2, ..., an给出q个操作, 操作分为两种 对于形如1xyz的操作, 将下标介于[x,y]的元素加上z, 满足1≤x≤y≤n, 1≤z≤105对于形如2…

17、java中的集合(4)

之前单列集合只说过了List系列的集合&#xff0c;接下来再说一下Set集合系列&#xff0c;Set集合是无序集合&#xff08;存取顺序不一致&#xff09;&#xff0c;不允许添加相同元素&#xff0c;Set的实现依赖于Map集合&#xff0c;可以将Set集合看作Map集合键的集合&#xff0…

U92904-画地为佬【二分,结论】

正题 题目链接:https://www.luogu.org/problem/U92904?contestId23574 题目大意 用mmm根长度为1的火柴求能够圈住的最多块的地。 解题思路 显然如果刚好能够围成一个正方形那么一定是最优的&#xff0c;那么我们先将能够围成的围成一个最大的正方形&#xff0c;然后剩下的在…

确保线程安全下使用Queue的Enqueue和Dequeue

场景是这样&#xff0c;假设有一台设备会触发类型为Alarm的告警信号&#xff0c;并把信号添加到一个Queue结构中&#xff0c;每隔一段时间这个Queue会被遍历检查&#xff0c;其中的每个Alarm都会调用一个相应的处理方法。问题在于&#xff0c;检查机制是基于多线程的&#xff0…

2017西安交大ACM小学期数据结构 [分块、二维矩阵]

Problem B 发布时间: 2017年6月28日 10:06 最后更新: 2017年6月28日 16:35 时间限制: 2000ms 内存限制: 32M 描述 给定一个nm的矩形, 其中第i行第j列的值为ai,j给出q个操作, 操作有两种 对于形如1x1y1x2y2z的操作, 将(x1,y1)-(x2,y2)这段矩形区域的所有元素加上z, 满足1≤…

18、java中的泛型

之前介绍集合时&#xff0c;可以看到有List<String>这样的写法&#xff0c;那么尖括号里的内容是什么呢&#xff1f;这是泛型&#xff0c;意思就是说声明的这个List集合只能存放String类型的元素。 泛型是什么&#xff1f; ‘泛’指一般、不深入&#xff0c;在这里可以认…

编写一个Java程序,其中包含三个线程: 厨师(Chef)、服务员(Waiter)和顾客(Customer)

编写一个Java程序&#xff0c;其中包含三个线程: 厨师(Chef)、服务员(Waiter)和顾客(Customer)。他们的行动如下: 厨师准备菜肴&#xff0c;每次准备一个。服务员等待菜肴准备好&#xff0c;然后将其送到顾客那里。顾客等待服务员送来菜看后才开始吃。所有三个角色应该循环进行…

U86650-群鸡乱舞【矩阵乘法】

正题 题目链接:https://www.luogu.org/problem/U86650?contestId23574 题目大意 第一年有nnn只鸡&#xff0c;每只大于等于两岁的鸡每年可以生一只&#xff0c;在ttt岁时不会生鸡而会暴毙。 现在给出每只鸡的年龄&#xff0c;求第mmm年鸡的总数量。 解题思路 用fif_{i}fi​…

2017西安交大ACM小学期数据结构 [线段树]

Problem B 发布时间: 2017年7月1日 02:08 最后更新: 2017年7月1日 02:10 时间限制: 1000ms 内存限制: 64M 描述 给定一个长度为n的序列a1, a2, ..., an, 满足这个序列是一个1~n的排列 如果一个序列满足: 将序列排序后, 任意两个相邻的元素的差为1, 那么就称这个序列为&qu…

19、java中枚举

枚举是什么&#xff1f; 枚举就是将一个有限集合中的所有元素列举出来&#xff0c;在java中使用可以使用enum关键字来声明一个枚举类。 为什么使用枚举&#xff1f; 之前当用到一些常量时&#xff0c;便临时声明一个&#xff0c;这样使得代码看起来很乱&#xff0c;这里一个…

Hangfire使用ApplicationInsigts监控

起因我司目前使用清真的ApplicationInsights来做程序级监控。&#xff08;ApplicationInsights相关文档: https://azure.microsoft.com/zh-cn/services/application-insights/ &#xff09;其实一切都蛮好的&#xff0c;但是我们基于Hangfire的Job系统却无法被Ai所监控到&#…

nssl1446-小智的旅行【dp】

正题 题目大意 求一条最大的权值严格上升的路径。 解题思路 将边权排序&#xff0c;然后从fxf_xfx​转移到fy1f_y1fy​1即可&#xff0c;要注意的是因为严格上升&#xff0c;所以此次转移用的fff不能是相同权值转移时转移的。 codecodecode #include<cstdio> #include…

2017西安交大ACM小学期数据结构 [树状数组]

Problem C 发布时间: 2017年6月28日 11:38 最后更新: 2017年6月28日 16:38 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a1, a2, ..., an, 其中ai∈[1,10]给出q个操作, 操作分为两种 对于形如1xy的操作, 将ax改为y, 满足1≤x≤n, 1≤y≤10对于形如2xyz的操…