【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[0]) | 
注意:这里是可以直接
list.toArray(new Integer[0])的,toArray方法会取list的长度,源代码是这样写的:
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
在一般的开发中,往往很少直接使用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、
技巧
foreach在使用前都要对列表进行判空处理,不然列表如果为null,会报错。
如果不想使用if嵌套的方式判空的话,可以采用默认值的方式,默认值设为空列表
if (list == null) {
    list = new ArrayList<>();
}
for (Object item : list) {}
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的区别:
- Set 接口实例存储的是无序的,不重复的数据。
 List 接口实例存储的是有序的,可以重复的元素。
- Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
- 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);
}
笔者将不定期更新【考研或就业】的专业相关知识以及自身理解,希望大家能【关注】我。
如果觉得对您有用,请点击左下角的【点赞】按钮,给我一些鼓励,谢谢!
如果有更好的理解或建议,请在【评论】中写出,我会及时修改,谢谢啦!
本文来自博客园,作者:Nemo&
转载请注明原文链接:https://www.cnblogs.com/blknemo/p/12469580.html


 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号