【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、
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