𝓝𝓮𝓶𝓸&博客

【Java】(机考常用)类集

类集框架集合框架)是一个用来代表和操纵集合的统一架构。所有的类集框架都包含如下内容:

  • 接口:是代表类集的抽象数据类型。之所以定义多个接口,是为了以不同的方式操作集合对象。

例如:Collection、List、Set、Map、Queue等。

  • 实现(类):是类集接口的具体实现。从本质上讲,它们是可重复使用的数据结构。

例如:ArrayList、LinkedList、HashSet、HashMap。

  • 算法:是实现类集接口的对象里的方法执行的一些有用的计算。

例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

部分类集框架体系如图:

类集的关系框架图:

集合的框架结构

|----Collection接口:单列集合,用来存储一个一个的对象
     |----List接口:存储有序的、可重复的数据。  -->“动态”数组
           |----ArrayList:作为List接口的主要实现类,线程不安全的,效率高;底层采用Object[] elementData数组存储
           |----LinkedList:对于频繁的插入删除操作,使用此类效率比ArrayList效率高底层采用双向链表存储
           |----Vector:作为List的古老实现类,线程安全的,效率低;底层采用Object[]数组存储
           
     |----Set接口:存储无序的、不可重复的数据   -->数学概念上的“集合”
           |----HashSet:作为Set接口主要实现类;线程不安全;可以存null值(hash只是一种实现方式,集合用哈希使关键值与地址一一映射,方便查找重复)
                |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加顺序遍历;对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
           |----TreeSet:可以按照添加对象的指定属性,进行排序。


|----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
     |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value(hash只是一种实现方式,map用哈希使关键值与地址一一映射,方便查找键值对)
          |----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
                    原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
                    对于频繁的遍历操作,此类执行效率高于HashMap。
     |----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
                      底层使用红黑树
     |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
          |----Properties:常用来处理配置文件。key和value都是String类型

List、Set、Map类均有一个of()类方法,可以返回包含任意数量的元素的不可拆卸列表。有关详细信息,请参阅不可修改的列表
我们有时候需要将数据封装成一个不修改的变量传给方法,就可以使用这个of()方法。

List 中存不同的数据类型

List 中是可以存不同的数据类型的。

但是在定义的时候需要定义成:List testList = new ArrayList<>();,不能为要使用的 List 指定数据类型。

当为我们使用的 List 不指定数据类型的话,所有存到 List 中的对象都会被转换为 Object 类型。

而当我门再从 list 中取出该数据时,就会发现数据类型已经改变。

Object类是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。

在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类。

例如下面的代码:

List testList = new ArrayList<>();
testList.add(1L);
testList.add("iSharkFly");
testList.add(2);

因为我们没有为我们的 List 指定数据类型,那么这个 List 内是可以放任何数据类型的,你甚至可以放一个对象进去也没有问题的。

上面的代码编译器是不会报错的。

常用类集

下面较为常用的方法我都用粗体标识了,希望大家注意。
下面的类都是集合,即 一组数据,而不是单一的一个。

容器的移除节点是各种remove,而字符串的删除是delete

Collection接口

Collection:集合

方法 返回类型 描述 备注
add(E o) boolean 向集合中插入对象
addAll(Collection<? extends E> c) boolean 将一个集合的内容插入进来
**clear() ** void 清除此集合中的所有元素
**contains(Object o) ** boolean 判断某一个对象是否在集合中存在
containsAll(Collection<?> c) boolean 判断某一组对象是否在集合中存在
**equals(Object o) ** boolean 对象比较
hashCode() int 哈希码
isEmpty() boolean 集合是否为空
iterator() Iterator<E> 为Iterator接口实例化
remove(Object o) boolean 删除指定对象
removeAll(Collection<?> c) boolean 删除一组对象
retainAll(Collection<?> c) boolean 保存指定内容
size() int 求出集合的大小
toArray() Object[] 将一个集合变为对象数组 返回的是Object类型的数组
toArray(E[] a) <E> E[] 指定好返回的对象数组类型 最好使用这个,自己设置返回的数组类型
toArray(new Integer[10])

在一般的开发中,往往很少直接使用Collection接口进行开发,基本上都是使用其子接口。

子接口主要有:List、Set、Queue、SortedSet。

注意是contains
英语语法的问题。
list.insert(x):用自然语言来说是 insert x into list 主语反正不是list,没必要用第三人称单数。
list.remove(x):同理相当于:remove x from list
list.contains(x) 用自然语言表述是:The list contains x. 主语是list,用第三人称单数没有毛病。

List接口

List:线性表

方法 返回类型 描述 备注
get(int index) E 返回指定位置的元素
indexOf(Object o) int 查找指定元素的位置
set(int index, E element) E 替换指定位置的元素
remove(int index) E 按指定的位置删除元素
add(int index, E element) void 在指定位置增加元素
addAll(int index, Collection<? extends E> c) boolean 在指定位置增加一组元素
lastIndexOf(Object o) int 从后往前查找指定元素的位置
listIterator() ListIterator<E> 为ListIterator接口实例化
subList(int fromIndex, int toIndex) List<E> 取出集合中的子集合

ArrayList

ArrayList:数组

方法 返回类型 描述 备注
get(int index) E 返回指定位置的元素
indexOf(Object o) int 查找指定元素的位置
set(int index, E element) E 替换指定位置的元素
remove(int index) E 按指定的位置删除元素
add(int index, E element) void 在指定位置增加元素
addAll(int index, Collection<? extends E> c) boolean 在指定位置增加一组元素
lastIndexOf(Object o) int 从后往前查找指定元素的位置
listIterator() ListIterator<E> 为ListIterator接口实例化
subList(int fromIndex, int toIndex) List<E> 取出集合中的子集合

注意:一般用List接口实例化ArrayList类,即 List<String> all = new ArrayList<String>();

原因:编程是要面向对象编程,针对抽象(接口),而非具体。List 是接口,ArrayList是实现。
这是一种很好的设计模式.一个接口有多种实现,这种写法是java面向对象的一种思想,依赖倒置原则,即 依赖于抽象不依赖于实现(具体)。
从Java语法上,这种方式是使用接口引用指向具体实现,这样大大提高了代码使用的灵活性。
比如,你若希望用LinkedList的实现来替代ArrayList的话,只需改动一行即可。

LinkedList

LinkedList:双向链表,可以当双向队列使用

方法 返回类型 描述 备注
addFirst(E o) void 在链表开头增加元素
addLast(E o) void 在链表结尾增加元素
offer(E o) boolean 将指定元素增加到链表的结尾
removeFirst() E 删除链表的第一个元素
removeLast() E 删除链表的最后一个元素

输出

Iterator

Iterator:迭代输出

方法 返回类型 描述 备注
hasNext() boolean 判断是否有下一个值 是否有下一个
next() E 后移取出当前元素 取出
remove() void 移除当前元素 移除

注意:Iterator是从头节点(无数据)开始的,而不是第一个有效节点,所以我们用hasNext()判断是否有值,next()来取值。

例子:

List<String> all = new ArrayList<String>();	//实例化List接口
all.add("hello");
all.add("_");
all.add("world");

Iterator<String> iter = all.iterator();	//直接实例化Iterator接口

while(iter.hasNext()) {
	System.out.print(iter.next() + "、");
}

程序运行结果:

hello、_、world、

ListIterator

ListIterator:双向迭代输出

方法 返回类型 描述 备注
remove() void 移除当前元素
add(E o) void 将指定元素增加集合
hasNext() boolean 判断是否有下一个值
next() E 取出当前元素
nextIndex() int 返回下一个元素的索引号
hasPrevious() boolean 判断是否有上一个元素
previous() E 取出当前元素
previousIndex() int 返回上一个元素的索引号
set(E o) void 替换元素

例子:

List<String> all = new ArrayList<String>();
all.add("hello");
all.add("_");
all.add("world");

ListIterator<String> iter = all.listIterator();	// 实例化ListIterator接口

System.out.print("由前向后输出:");
while(iter.hasNext()) {
	String s = iter.next();
	System.out.print(str + "、");
}

System.out.print("由后向前输出:");
while(iter.hasPrevious()) {
	String s = iter.previous();
	System.out.print(str + "、");
}

程序运行结果:

由前向后输出:hello、_、world、
由后向前输出:world、_、hello、

foreach

格式:对象遍历集合数组

for(类 对象: 集合数组) {
    //操作
}

例子:

List<String> all = new ArrayList<String>();
all.add("hello");
all.add("_");
all.add("world");

for(String s: all) {
	System.out.print(s + "、");
}

程序运行结果:

hello、_、world、

Java中ArrayList和LinkedList的遍历与性能分析

这篇文章主要给大家介绍了ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论。相信对大家的理解和学习具有一定的参考价值,有需要的朋友们下面跟着小编一起来学习学习吧。

前言

通过本文你可以了解List的五种遍历方式及各自性能和foreach及Iterator的实现,加深对ArrayList和LinkedList实现的了解。下面来一起看看吧。

一、List的五种遍历方式

foreach能否修改数据?

“foreach循环迭代数组元素时,不能改变数组元素的值,因此,不要对foreach的循环变量进行赋值”。

  • 基本类型数组,不可改变。

    如果需要改变基本类型数组,可以直接使用一般的for循环,num[0]=520,这样就能改变了。

  • 引用类型数组(除String类型),可以改变,因为改变的是里面的属性值,不改变地址。
1、for each循环
List<Integer> list = new ArrayList<Integer>();
for (Integer j : list) {
 // use j
}
2、显示调用集合迭代器
List<Integer> list = new ArrayList<Integer>();
for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
 iterator.next();
}

List<Integer> list = new ArrayList<Integer>();
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
 iterator.next();
}
3、下标递增循环,终止条件为每次调用size()函数比较判断
List<Integer> list = new ArrayList<Integer>();
for (int j = 0; j < list.size(); j++) {
 list.get(j);
}
4、下标递增循环,终止条件为和等于size()的临时变量比较判断
List<Integer> list = new ArrayList<Integer>();
int size = list.size();
for (int j = 0; j < size; j++) {
 list.get(j);
}
5、下标递减循环
List<Integer> list = new ArrayList<Integer>();
for (int j = list.size() - 1; j >= 0; j--) {
 list.get(j);
}

List五种遍历方式的性能测试及对比

以下是性能测试代码,会输出不同数量级大小的ArrayList和LinkedList各种遍历方式所花费的时间。

package cn.trinea.java.test;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
 * JavaLoopTest
 * 
 * @author www.trinea.cn 2013-10-28
 */
public class JavaLoopTest {
 public static void main(String[] args) {
  System.out.print("compare loop performance of ArrayList");
  loopListCompare(getArrayLists(10000, 100000, 1000000, 9000000));
  System.out.print("\r\n\r\ncompare loop performance of LinkedList");
  loopListCompare(getLinkedLists(100, 1000, 10000, 100000));
 }
 public static List<Integer>[] getArrayLists(int... sizeArray) {
  List<Integer>[] listArray = new ArrayList[sizeArray.length];
  for (int i = 0; i < listArray.length; i++) {
   int size = sizeArray[i];
   List<Integer> list = new ArrayList<Integer>();
   for (int j = 0; j < size; j++) {
    list.add(j);
   }
   listArray[i] = list;
  }
  return listArray;
 }
 public static List<Integer>[] getLinkedLists(int... sizeArray) {
  List<Integer>[] listArray = new LinkedList[sizeArray.length];
  for (int i = 0; i < listArray.length; i++) {
   int size = sizeArray[i];
   List<Integer> list = new LinkedList<Integer>();
   for (int j = 0; j < size; j++) {
    list.add(j);
   }
   listArray[i] = list;
  }
  return listArray;
 }
 public static void loopListCompare(List<Integer>... listArray) {
  printHeader(listArray);
  long startTime, endTime;
  // Type 1
  for (int i = 0; i < listArray.length; i++) {
   List<Integer> list = listArray[i];
   startTime = Calendar.getInstance().getTimeInMillis();
   for (Integer j : list) {
    // use j
   }
   endTime = Calendar.getInstance().getTimeInMillis();
   printCostTime(i, listArray.length, "for each", endTime - startTime);
  }
  // Type 2
  for (int i = 0; i < listArray.length; i++) {
   List<Integer> list = listArray[i];
   startTime = Calendar.getInstance().getTimeInMillis();
   // Iterator<Integer> iterator = list.iterator();
   // while(iterator.hasNext()) {
   // iterator.next();
   // }
   for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
    iterator.next();
   }
   endTime = Calendar.getInstance().getTimeInMillis();
   printCostTime(i, listArray.length, "for iterator", endTime - startTime);
  }
  // Type 3
  for (int i = 0; i < listArray.length; i++) {
   List<Integer> list = listArray[i];
   startTime = Calendar.getInstance().getTimeInMillis();
   for (int j = 0; j < list.size(); j++) {
    list.get(j);
   }
   endTime = Calendar.getInstance().getTimeInMillis();
   printCostTime(i, listArray.length, "for list.size()", endTime - startTime);
  }
  // Type 4
  for (int i = 0; i < listArray.length; i++) {
   List<Integer> list = listArray[i];
   startTime = Calendar.getInstance().getTimeInMillis();
   int size = list.size();
   for (int j = 0; j < size; j++) {
    list.get(j);
   }
   endTime = Calendar.getInstance().getTimeInMillis();
   printCostTime(i, listArray.length, "for size = list.size()", endTime - startTime);
  }
  // Type 5
  for (int i = 0; i < listArray.length; i++) {
   List<Integer> list = listArray[i];
   startTime = Calendar.getInstance().getTimeInMillis();
   for (int j = list.size() - 1; j >= 0; j--) {
    list.get(j);
   }
   endTime = Calendar.getInstance().getTimeInMillis();
   printCostTime(i, listArray.length, "for j--", endTime - startTime);
  }
 }
 static int     FIRST_COLUMN_LENGTH = 23, OTHER_COLUMN_LENGTH = 12, TOTAL_COLUMN_LENGTH = 71;
 static final DecimalFormat COMMA_FORMAT  = new DecimalFormat("#,###");
 public static void printHeader(List<Integer>... listArray) {
  printRowDivider();
  for (int i = 0; i < listArray.length; i++) {
   if (i == 0) {
    StringBuilder sb = new StringBuilder().append("list size");
    while (sb.length() < FIRST_COLUMN_LENGTH) {
     sb.append(" ");
    }
    System.out.print(sb);
   }
   StringBuilder sb = new StringBuilder().append("| ").append(COMMA_FORMAT.format(listArray[i].size()));
   while (sb.length() < OTHER_COLUMN_LENGTH) {
    sb.append(" ");
   }
   System.out.print(sb);
  }
  TOTAL_COLUMN_LENGTH = FIRST_COLUMN_LENGTH + OTHER_COLUMN_LENGTH * listArray.length;
  printRowDivider();
 }
 public static void printRowDivider() {
  System.out.println();
  StringBuilder sb = new StringBuilder();
  while (sb.length() < TOTAL_COLUMN_LENGTH) {
   sb.append("-");
  }
  System.out.println(sb);
 }
 public static void printCostTime(int i, int size, String caseName, long costTime) {
  if (i == 0) {
   StringBuilder sb = new StringBuilder().append(caseName);
   while (sb.length() < FIRST_COLUMN_LENGTH) {
    sb.append(" ");
   }
   System.out.print(sb);
  }
  StringBuilder sb = new StringBuilder().append("| ").append(costTime).append(" ms");
  while (sb.length() < OTHER_COLUMN_LENGTH) {
   sb.append(" ");
  }
  System.out.print(sb);
  if (i == size - 1) {
   printRowDivider();
  }
 }
}

PS:如果运行报异常in thread “main” java.lang.OutOfMemoryError: Java heap space,请将main函数里面list size的大小减小。
其中getArrayLists函数会返回不同size的ArrayList,getLinkedLists函数会返回不同size的LinkedList。
loopListCompare函数会分别用上面的遍历方式1-5去遍历每一个list数组(包含不同大小list)中的list。
print开头函数为输出辅助函数。

测试环境为Windows7 32位系统 3.2G双核CPU 4G内存,Java 7,Eclipse -Xms512m -Xmx512m
最终测试结果如下:

compare loop performance of ArrayList
-----------------------------------------------------------------------
list size    | 10,000 | 100,000 | 1,000,000 | 10,000,000
-----------------------------------------------------------------------
for each    | 1 ms  | 3 ms  | 14 ms  | 152 ms 
-----------------------------------------------------------------------
for iterator   | 0 ms  | 1 ms  | 12 ms  | 114 ms 
-----------------------------------------------------------------------
for list.size()  | 1 ms  | 1 ms  | 13 ms  | 128 ms 
-----------------------------------------------------------------------
for size = list.size() | 0 ms  | 0 ms  | 6 ms  | 62 ms  
-----------------------------------------------------------------------
for j--    | 0 ms  | 1 ms  | 6 ms  | 63 ms  
-----------------------------------------------------------------------
  
compare loop performance of LinkedList
-----------------------------------------------------------------------
list size    | 100  | 1,000  | 10,000 | 100,000
-----------------------------------------------------------------------
for each    | 0 ms  | 1 ms  | 1 ms  | 2 ms  
-----------------------------------------------------------------------
for iterator   | 0 ms  | 0 ms  | 0 ms  | 2 ms  
-----------------------------------------------------------------------
for list.size()  | 0 ms  | 1 ms  | 73 ms  | 7972 ms 
-----------------------------------------------------------------------
for size = list.size() | 0 ms  | 0 ms  | 67 ms  | 8216 ms 
-----------------------------------------------------------------------
for j--    | 0 ms  | 1 ms  | 67 ms  | 8277 ms 
-----------------------------------------------------------------------

第一张表为ArrayList对比结果,第二张表为LinkedList对比结果。
表横向为同一遍历方式不同大小list遍历的时间消耗,纵向为同一list不同遍历方式遍历的时间消耗。
PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

遍历方式性能测试结果分析

1、foreach介绍

foreach是Java SE5.0引入的功能很强的循环结构,for (Integer j : list)应读作for each int in list。
for (Integer j : list)实现几乎等价于

Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
 Integer j = iterator.next();
}

foreach代码书写简单,不必关心下标初始值和终止值及越界等,所以不易出错

2、ArrayList遍历方式结果分析

a. 在ArrayList大小为十万之前,五种遍历方式时间消耗几乎一样
b. 在十万以后,第四、五种遍历方式快于前三种,get方式优于Iterator方式,并且

int size = list.size();
for (int j = 0; j < size; j++) {
 list.get(j);
}

用临时变量size取代list.size()性能更优。我们看看ArrayList中迭代器Iterator和get方法的实现

private class Itr implements Iterator<E> {
 int cursor;  // index of next element to return
 int lastRet = -1; // index of last element returned; -1 if no such
 int expectedModCount = modCount;
  
 public boolean hasNext() {
  return cursor != size;
 }
  
 @SuppressWarnings("unchecked")
 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];
 }
 ……
}
  
public E get(int index) {
 rangeCheck(index);
  
 return elementData(index);
}

从中可以看出get和Iterator的next函数同样通过直接定位数据获取元素,只是多了几个判断而已。
c. 从上可以看出即便在千万大小的ArrayList中,几种遍历方式相差也不过50ms左右,且在常用的十万左右时间几乎相等,考虑foreach的优点,我们大可选用foreach这种简便方式进行遍历。

3、LinkedList遍历方式结果分析

a. 在LinkedList大小接近一万时,get方式和Iterator方式就已经差了差不多两个数量级,十万时Iterator方式性能已经远胜于get方式。
我们看看LinkedList中迭代器和get方法的实现

private class ListItr implements ListIterator<E> {
 private Node<E> lastReturned = null;
 private Node<E> next;
 private int nextIndex;
 private int expectedModCount = modCount;
  
 ListItr(int index) {
  // assert isPositionIndex(index);
  next = (index == size) ? null : node(index);
  nextIndex = index;
 }
  
 public boolean hasNext() {
  return nextIndex < size;
 }
  
 public E next() {
  checkForComodification();
  if (!hasNext())
   throw new NoSuchElementException();
  
  lastReturned = next;
  next = next.next;
  nextIndex++;
  return lastReturned.item;
 }
 ……
}
  
public E get(int index) {
 checkElementIndex(index);
 return node(index).item;
}
  
/**
 * Returns the (non-null) Node at the specified element index.
 */
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;
 }
}

从上面代码中可以看出LinkedList迭代器的next函数只是通过next指针快速得到下一个元素并返回。而get方法会从头遍历直到index下标,查找一个元素时间复杂度为哦O(n),遍历的时间复杂度就达到了O(n2)。
所以对于LinkedList的遍历推荐使用foreach,避免使用get方式遍历。

4、ArrayList和LinkedList遍历方式结果对比分析

从上面的数量级来看,同样是foreach循环遍历,ArrayList和LinkedList时间差不多,可将本例稍作修改加大list size会发现两者基本在一个数量级上。
但ArrayList get函数直接定位获取的方式时间复杂度为O(1),而LinkedList的get函数时间复杂度为O(n)。

再结合考虑空间消耗的话,建议首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。

结论总结

通过上面的分析我们基本可以总结下:

  • 无论ArrayList还是LinkedList,遍历建议使用foreach,尤其是数据量较大时LinkedList避免使用get遍历。
  • List使用首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。
  • 可能在遍历List循环内部需要使用到下标,这时综合考虑下是使用foreach和自增count还是get方式。

为什么使用迭代器iterator遍历Linkedlist要比普通for快

大家可以搜索一下普通情况遍历linkedlist应该是O(n)但是使用iterator就是常数,这让我很好奇。于是我去查了源码。。
一路顺着找找到了Collection,确实有一个iterator但是是个interface还没有实现。

网上找list,有一个listiterator还是这样。

只能去linked找了,找到了如下源码

 	private static final class LinkIterator<ET> implements ListIterator<ET> {
61        int pos, expectedModCount;
62
63        final LinkedList<ET> list;
64
65        Link<ET> link, lastLink;
66
67        LinkIterator(LinkedList<ET> object, int location) {
68            list = object;
69            expectedModCount = list.modCount;
70            if (location >= 0 && location <= list.size) {
71                // pos ends up as -1 if list is empty, it ranges from -1 to
72                // list.size - 1
73                // if link == voidLink then pos must == -1
74                link = list.voidLink;
75                if (location < list.size / 2) {
76                    for (pos = -1; pos + 1 < location; pos++) {
77                        link = link.next;
78                    }
79                } else {
80                    for (pos = list.size; pos >= location; pos--) {
81                        link = link.previous;
82                    }
83                }
84            } else {
85                throw new IndexOutOfBoundsException();
86            }
87        }
88
89        public void add(ET object) {
90            if (expectedModCount == list.modCount) {
91                Link<ET> next = link.next;
92                Link<ET> newLink = new Link<ET>(object, link, next);
93                link.next = newLink;
94                next.previous = newLink;
95                link = newLink;
96                lastLink = null;
97                pos++;
98                expectedModCount++;
99                list.size++;
100                list.modCount++;
101            } else {
102                throw new ConcurrentModificationException();
103            }
104        }
105
106        public boolean hasNext() {
107            return link.next != list.voidLink;
108        }
109
110        public boolean hasPrevious() {
111            return link != list.voidLink;
112        }
113
114        public ET next() {
115            if (expectedModCount == list.modCount) {
116                LinkedList.Link<ET> next = link.next;
117                if (next != list.voidLink) {
118                    lastLink = link = next;
119                    pos++;
120                    return link.data;
121                }
122                throw new NoSuchElementException();
123            }
124            throw new ConcurrentModificationException();
125        }
126
127        public int nextIndex() {
128            return pos + 1;
129        }
130
131        public ET previous() {
132            if (expectedModCount == list.modCount) {
133                if (link != list.voidLink) {
134                    lastLink = link;
135                    link = link.previous;
136                    pos--;
137                    return lastLink.data;
138                }
139                throw new NoSuchElementException();
140            }
141            throw new ConcurrentModificationException();
142        }
143
144        public int previousIndex() {
145            return pos;
146        }
147
148        public void remove() {
149            if (expectedModCount == list.modCount) {
150                if (lastLink != null) {
151                    Link<ET> next = lastLink.next;
152                    Link<ET> previous = lastLink.previous;
153                    next.previous = previous;
154                    previous.next = next;
155                    if (lastLink == link) {
156                        pos--;
157                    }
158                    link = previous;
159                    lastLink = null;
160                    expectedModCount++;
161                    list.size--;
162                    list.modCount++;
163                } else {
164                    throw new IllegalStateException();
165                }
166            } else {
167                throw new ConcurrentModificationException();
168            }
169        }
170
171        public void set(ET object) {
172            if (expectedModCount == list.modCount) {
173                if (lastLink != null) {
174                    lastLink.data = object;
175                } else {
176                    throw new IllegalStateException();
177                }
178            } else {
179                throw new ConcurrentModificationException();
180            }
181        }
182    }
183

我们仔细察看next方法

public ET next() {
115            if (expectedModCount == list.modCount) {
116                LinkedList.Link<ET> next = link.next;
117                if (next != list.voidLink) {
118                    lastLink = link = next;
119                    pos++;
120                    return link.data;
121                }
122                throw new NoSuchElementException();
123            }
124            throw new ConcurrentModificationException();
125        }
126

这里里面有一个类是叫link,代码如下

private static final class Link<ET> {
49        ET data;
50
51        Link<ET> previous, next;
52
53        Link(ET o, Link<ET> p, Link<ET> n) {
54            data = o;
55            previous = p;
56            next = n;
57        }
58    }

可见list就是一个双向链表的link,没有什么特殊之处。到这里我彻底懵逼了,为什么呢为什么呢,为什么你遍历就是常数呢?
我们仔细对比一下for循环

for(int i =0;i<list.size();i++){
  list.get(i);
}

但是iterator和他对比起来少了一个list.get(i);其实就遍历而言它们两个差距并不大。但是其中调用了一次get(i).这个时间复杂度应该是O(n)所以嵌套一个for循环是O(n^2),但是在iterator中因为next的存在get当前项不需要时间所以循环下来应该是O(n),原来差距就在get和iterator这里了

注意:由于 ForEach 循环底层使用的也是迭代器,所以和迭代器性能类似。

参考文章:https://blog.csdn.net/u014629433/article/details/51586589

Stack

Stack:栈

其实,栈的底层还是一个数组,只不过是被FIFO规定限制的数组,你压入1、2、3、4、5,输出整个栈,得到的还是顺着的数字1、2、3、4、5,而不是一个一个出栈输出5、4、3、2、1.

方法 返回类型 描述 备注
empty() boolean 测试栈是否为空 检查是否空
push(E item) E 入栈 入栈
pop() E 出栈,同时删除 出栈
peek() E 查看栈顶,但不删除 栈顶
search(Object o) int 在栈中查找 查找

例子:

Stack<String> s = new Stack<String>();
s.push("A");
s.push("B");
s.push("C");

System.out.print(s.pop + '、');
System.out.print(s.pop + '、');
System.out.print(s.pop + '、');

程序运行结果:

C、B、A、

这里比较有意思的是栈的遍历,forEach(),它并不是从栈顶一个一个弹出来遍历的,而是从栈底开始,0~n,顺着遍历。

Queue

Queue:队列
作为一个接口,它没有实现任何具体功能(都是抽象方法),所以我们只能用其他的类来实现Queue接口。

LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用
Queue<String> queue = new LinkedList<String>();

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

方法 返回类型 描述 备注
offer(E o) boolean 将指定元素增加到队尾 如果队列已满,则返回false
poll() E (轮询)找到并删除队头 (获取)如果队列为空,则返回null
peek() E 找到但不删除队头 (读取)如果队列为空,则返回null
add(E o) void 将指定元素增加到队尾 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove() E 找到并移除队头 (移除本应有的)如果队列为空,则抛出一个NoSuchElementException异常
element() E 找到队头 (找到本应有的元素)如果队列为空,则抛出一个NoSuchElementException异常
  • offer,add 区别:
    一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
    这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。
  • poll,remove 区别:
    remove() 和 poll() 方法都是从队列中删除第一个元素。remove() 的行为与 Collection 接口的版本相似, 但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。
  • peek,element区别:
    element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

注意:add()和remove()是继承于Collection类的,所以我们尽量用前三个方法来操作队列。

例子:

注意:Queue接口是用LinkedList类实现的,Queue<String> queue = new LinkedList<String>();

Queue<String> queue=new LinkedList<String>();
queue.offer("Hello");
queue.offer("World!");
queue.offer("你好!");

System.out.println(queue.size());

for(String str: queue){
    System.out.printf(str + " ");
}
System.out.printf("\n");

System.out.println(queue.size());
String str;

while((str=queue.poll()) != null) {
    System.out.printf(str + " ");
}
System.out.println();
System.out.println(queue.size());

程序运行结果:

3
Hello World! 你好!
3
Hello World! 你好!
0

Deque

栈:Stack
队列:Queue

栈和队列:Deque(double ended queue, 双端队列)(万能)
在Java Doc里建议使用Deque接口替代Stack完成栈的功能,为什么呢?
因为Stack是继承Vector,Vector是由数组实现线程安全(方法使用synchronized修饰)的集合类,它包含了大量集合处理的方法,而Stack复用了Vector的方法实现进栈和出栈的操作,基于数组实现相比于链表实现,效率上受影响。同时,因为继承Vector类,Stack可以复用Vector的方法,这是Stack设计不严谨的地方

// 双端队列
Deque<Integer> deque = new LinkedList<Integer>();
deque.offer();
deque.offerFirst();
deque.offerLast();

deque.poll();
deque.pollFirst();
deque.pollLast();

deque.peek();
deque.peekFirst();
deque.peekLast();


// 栈
Deque<Integer> stack = new LinkedList<Integer>();
stack.push(); // deque.addFirst();
stack.pop(); // deque.removeFirst();
stack.peek(); // deque.peekFirst();

// 队列
Deque<Integer> queue = new LinkedList<Integer>();
queue.offer(); // deque.offerLast();
queue.poll(); // deque.pollFirst();
queue.peek(); // deque.peekFirst();
Queue Method Equivalent Deque Method 说明
add(e) addLast(e) 向队尾插入元素,失败则抛出异常
offer(e) offerLast(e) 向队尾插入元素,失败则返回false
remove() removeFirst() 获取并删除队首元素,失败则抛出异常
poll() pollFirst() 获取并删除队首元素,失败则返回null
element() getFirst() 获取但不删除队首元素,失败则抛出异常
peek() peekFirst() 获取但不删除队首元素,失败则返回null
Stack Method Equivalent Deque Method 说明
push(e) addFirst(e) 向栈顶插入元素,失败则抛出异常
offerFirst(e) 向栈顶插入元素,失败则返回false
pop() removeFirst() 获取并删除栈顶元素,失败则抛出异常
pollFirst() 获取并删除栈顶元素,失败则返回null
peek() getFirst() 获取但不删除栈顶元素,失败则抛出异常
peekFirst() 获取但不删除栈顶元素,失败则返回null

LinkedList

LinkedList是一个由链表实现的双向队列。

如果需要实例化,最好直接使用LinkedList,进行实例化,因为使用接口Queue会限制成单向队列。

此双向队列可以addFirst()addLast()removeFirst()removeLast()

ArrayDeque

ArrayDeque是一个由数组实现的双向队列。

PriorityQueue优先队列(堆)

优先队列(priority queue)
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用数据结构来实现。

在Java中,优先队列默认为小顶堆,我们可以来做一个实验

PriorityQueue priorityQueue = new PriorityQueue();
for (int i = 50; i > 0; i--) {
    priorityQueue.add(i);
}

//        for (int i = 0; i < 100; i++) {
//            priorityQueue.add(i);
//        }

for (int i = 0; i < 50; i++) {
    System.out.print(priorityQueue.remove() + " ");
}

运行结果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 

说明无论怎么把数据放进去,出来的永远是最小的那一个,即 默认小顶堆。


大顶堆:

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>((o1, o2) -> -o1.compareTo(o2));
for (int i = 50; i > 0; i--) {
    priorityQueue.add(i);
}

//        for (int i = 0; i < 100; i++) {
//            priorityQueue.add(i);
//        }

for (int i = 0; i < 50; i++) {
    System.out.print(priorityQueue.remove() + " ");
}

运行结果:

50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 

Set

Set:集合(无序、不重复)

Set可以用于去掉重复元素,遇到重复元素不准加入。

方法 返回类型 描述 备注
add(E o) boolean 向集合中插入对象
addAll(Collection<? extends E> c) boolean 将一个集合的内容插入进来
clear() void 清除此集合中的所有元素
contains(Object o) boolean 判断某一个对象是否在集合中存在
containsAll(Collection<?> c) boolean 判断某一组对象是否在集合中存在
equals(Object o) boolean 对象比较
hashCode() int 哈希码
isEmpty() boolean 集合是否为空
iterator() Iterator<E> 为Iterator接口实例化
remove(Object o) boolean 删除指定对象
removeAll(Collection<?> c) boolean 删除一组对象
retainAll(Collection<?> c) boolean 保存指定内容
size() int 求出集合的大小
toArray() Object[] 将一个集合变为对象数组
toArray(E[] a) <E> E[] 指定好返回的对象数组类型

Set和List的区别:

  1. Set 接口实例存储的是无序的,不重复的数据。
    List 接口实例存储的是有序的,可以重复的元素。
  2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
  3. List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>。

Map

Map:映射(键值对表)(一组键值对,不是单一的一个)

Map接口定义的集合又称为查找表,用于存储所谓“key-value"映射对。
Key可以看成是Value的索引,作为key的对象在集合中不可重复。

注意:是集合,查找表,即 一组键值对,不是单一的一个。

与Collection类似,如果想要使用Map接口也必须依靠其子类实例化。Map 接口中常用的子类介绍如下:

  • HashMap:无序存放的,是新的操作类,key不允许重复。(常用
  • Hashtable:无序存放的,是旧的操作类,key不允许重复。
  • TreeMap:可以排序的Map集合,按集合中的key排序,不允许key重复。
  • WeakHashMap:弱引用的Map集合,当集合中的某些内容不再使用时清除掉无用的数据,使用gc进行回收。
  • IdentityHashMap:key可以重复的Map集合。

由于Set和Map都不可以有重复的元素,所以一般都采用Hash来实现,方便在增加元素时直接使用哈希函数,比较Hash表(改进的链地址法,数组+链表+红黑树)判断是否重复,不用一个一个遍历一遍去比较,提高效率。

put
get
getOrDefault
remove
clear:清空

遍历

这里需要特别注意:Map 是一个键值对集合表,拥有多个键值对,而 entry 才是一个键值对。
看到这里大家可能忘记了,我们的迭代器 iterator 只有 Collection 集合类才能获取使用,而 Map 并不属于 Collection 集合类,所以 Map 是没有方法能够获取迭代器 iterator 用的噢。
但是,我们可以借助其他方法,将 Map 化为集合 Set,而集合 Set 是继承于 Collection 集合类的,所以就可以使用迭代器了!!!

理解:Map 毕竟是键值对表,是哈希表,是主要用来查找的表,一般来说是不需要遍历的,直接用键来查找就好了,也没有什么序列号,所以才没有迭代器方法能获取迭代器。

Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合

代码示例:

@Test
public void test3() {
    Map map = new HashMap();
    map.put("AA", 123);
    map.put("ZZ", 251);
    map.put("CC", 110);
    map.put("RR", 124);
    map.put("FF", 662);
    System.out.println(map);//{AA=123, ZZ=251, CC=110, RR=124, FF=662}
    //遍历所有的key集:Set keySet():返回所有key构成的Set集合
    Set set = map.keySet();
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
    System.out.println("--------------");
    //遍历所有的value集:Collection values():返回所有value构成的Collection集合
    Collection values = map.values();
    for (Object obj :
         values) {
        System.out.println(obj);
    }
    System.out.println("---------------");
    //Set entrySet():返回所有key-value对构成的Set集合
    Set entrySet = map.entrySet();
    Iterator iterator1 = entrySet.iterator();
    //方式一:
    while (iterator1.hasNext()) {
        Object obj = iterator1.next();
        //entrySet集合中的元素都是entry
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey() + "-->" + entry.getValue());
    }
    System.out.println("--------------");

    //方式二:
    Set keySet = map.keySet();
    Iterator iterator2 = keySet.iterator();
    while (iterator2.hasNext()) {
        Object key = iterator2.next();
        Object value = map.get(key);
        System.out.println(key + "==" + value);
    }
}

Map初始化

传统方式

// 传统方式
Map<String, String> map = new HashMap<>();
map.put("k1", "v1");
map.put("k2", "v2");

// 这里虽然是final,但是也仅仅只是引用的地址是不可变的,地址下的值是可以改变的
private static final Map<String, String> myMap = new HashMap<>();
static {
    myMap.put("key1", "value1");
    myMap.put("key2", "value2");
}

java8新特性-双括号初始化

外层“{}”创建了一个匿名内部类,内层“{}”创建了一个对象构造块。

// java8新特性-双括号初始化
Map<String, String> map1 = new HashMap<>() {{
    put("k1", "v1");
    put("k2", "v2");
}};

// 慎用 非静态内部类/匿名内部类包含了外围实例的引用,如果拥有比外部类更长的生命周期,有内存泄露隐患
private static final HashMap<String, String> myMap1 = new HashMap<>() {{
    put("key1", "value1");
    put("key2", "value2");
}};

java9新特性-最简便的方式

// java9新特性-最简便的方式
Map<String, String> map2 = Map.of("k1", "v1", "k2", "v2");

// 不可变集合
private static final Map<String, String> myMap20 = Map.of(); 
private static final Map<String, String> myMap2 = Map.of("kye1", "value1", "key2", "value2"); 
private static final Map<String, String> myMap21 = Map.of("kye1", "value1", "key2", "value2","kye3", "value1","kye4", "value1","kye5", "value1","kye6", "value1","kye7", "value1","kye8", "value1","kye9", "value10","kye10", "value1");

com.google.guava提供的方法

// com.google.guava提供的方法
Map<String, String> myMap3 = ImmutableMap.of("kye1", "value1", "key2", "value2");
Map<String, String> myMap4 = ImmutableMap.<String, String>builder().put("kye1", "value1").put("key2", "value2").build();

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

https://blog.csdn.net/weixin_37646636/article/details/132706286

记录数组中每个元素出现的频数

我的方法:

for (int i = 0; i < nums1.length; i++) {
    if (map.get(nums1[i]) != null) {
        map.put(nums1[i], map.get(nums1[i])+1);
    } else {
        map.put(nums1[i], 1);
    }
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int num : nums1) {
    int count = map.getOrDefault(num, 0) + 1;
    map.put(num, count);
}
posted @ 2020-03-12 17:25  Nemo&  阅读(422)  评论(0编辑  收藏  举报