概述
//TODO
//未完成
//不过先发了吧
//包含Collection(集合),List,Set,Map(图),以及其Iterator,Comparator ,Cloneable,还有常用的具体实现类
//List<List<String>>集合的嵌套使用
//1、是否允许空
//2、是否允许重复数据
//3、是否有序,有序的意思是读取数据的顺序和存放数据的顺序是否一致
//4、是否线程安全
JAVA SE 8官方文档
目录
概览
瞎说几句
对第一张图(集合类的关系)的解析
具体的几个集合类(List,Set,Map)
List
ArrayList
LinkedList
Set
HashSet
TreeSet
Map
HashMap
TreeMap
概览
瞎说几句
JAVA中有许多的集合,常用的有List,Set,Queue,Map。
其中List,Set,Queue都是Collection(集合),其每个元素都是单独的一个对象,如List<String>,Set<Integer>等,String和Integer就是单独的一个对象。
而Map是一种图,其每个元素都是两个对象的一一对应,如Map<Integer, String>中的Integer是键 (key),String是这个键所对应的值(value)。每个元素都是一对Integer和String
tip 1: List<String>中<>的内容表示其中元素的类型,是泛型的一种使用。
tip 2: Integer是一个对象(可从首字母大写的命名方式中看出),可以粗略地将其视作int。
tip 3: 由于这些集合类的元素必须是对象或者由对象构成,所以不能直接使用int这种简单数据类型将Map定义为Map<int, String>,而应该使用Integer将Map定义为Map<Integer, String>。
tip 4: 不能直接使用简单数据类型的更深层次的原因在于:
集合类(比如Set)在进行各种 "操作" ( 如contains()) 时都会调用元素本身提供的 "方法" ( 如hashCode()和equals()),而不是由集合类自身去实现这些 "方法"。这就要求如果某人想要用这个集合执行某些 "操作",那就必须在要加入集合的元素中实现相应的 "方法"。
由于简单数据类型 (如int),只是单纯的一个数值,而无法在其中实现方法,所以应该使用实现了各种所需"方法"的类 (如Integer) 作为元素。
对第一张图(集合类的关系)的解析
从第二行 java.util.AbstractCollection<E> (implements java.util.Collection<E>) 看起
- AbstractCollection<E>是一个抽象类,实现了Collection<E>接口,这个抽象类实现了接口中定义的部分方法,同时也有一些抽象方法交由AbstractCollection<E>的子类去实现。
- 然后看AbstractCollection<E>的子类,共有四个。分别是AbstractList<E>,AbstractQueue<E>,AbstractSet<E>,ArrayDeque<E>。
- AbstractList<E>:抽象类,继承于AbstractCollection<E>,实现了List<E>接口。其下有一些具体实现类如LinkedList<E>,ArrayList<E>等。
- AbstractQueue<E>:抽象类,继承于AbstractCollection<E>,实现了Queue<E>接口。其下有一个具体实现类PriorityQueue<E>
- AbstractSet<E>:抽象类,继承于AbstractCollection<E>,实现了Set<E>接口。其下有一些具体实现类如EnumSet<E>,HashSet<E>,LinkedHashSet<E>(是HashSet<E>的子类),TreeSet<E>。
- ArrayDeque<E>:AbstractCollection<E>的一个具体实现类。是一个双端队列。内部使用数组来进行元素存储,可以高效地进行元素查找和尾部插入取出,可用作队列,双端队列和栈,性能极佳。
接着看 java.util.AbstractMap<K,V> (implements java.util.Map<K,V>) 这一行
AbstractMap<K,V>是一个抽象类,实现了java.util.Map<K,V>接口,具有图的功能(即key和value的一一对应)。其下有几个具体的实现类,分别是:EnumMap<K,V>,HashMap<K,V>,LinkedHashMap<K,V>,IdentityHashMap<K,V>,TreeMap<K,V>,WeakHashMap<K,V>
具体的几个集合类(List,Set,Map)
List
List(列表),相当于数组。长度可变,元素存放有一定的顺序,下标从0开始。
tip
在JDK中,List作为接口,本身已经声明好了所有的方法(比如add(), contains()......),所以不管是选择ArrayList还是LinkedList,完成各种操作的时候依然是使用List中已经声明过的这一套方法,对使用者来说没有区别。
二者只是内部实现逻辑不同,所以在不同的应用场景下会有不同的效率。
ArrayList
先回答四个问题:
是否允许空元素(即null) | 是 |
是否允许重复数据 | 是 |
是否有序 | 是 |
是否线程安全 | 否 |
//如何使用ArrayList创建一个List
//不指定存放的元素类型
//默认容量为10
List list = new ArrayList();
//容量为6
List list = new ArrayList(6);
Array即数组,顾名思义,ArrayList是用数组实现的一个List(列表)。它实现了List接口声明的的所有方法,而且允许加入包括null在内的所有元素。除了实现列表接口之外,这个类还提供了一些方法来操作内部用于存储列表的数组的大小。
在每一个ArrayList实例中,都有专门保存容量的capacity属性,我在jdk1.8中找到了它的默认容量大小是
DEFAULT_CAPACITY = 10,如下是jdk1.8中对DEFAULT_CAPACITY的定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
/*
some code
*/
private static final int DEFAULT_CAPACITY = 10;
/*
some code
*/
}
接下来讨论ArrayList中对capacity的控制
举个栗子,ArrayList的add()方法,首先需要说明几个变量的含义,我从jdk1.8中找来了这些变量的声明语句包括其注释:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/*
some code
*/
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/*
some code
*/
}
其中各个变量的含义如下:
private static final int DEFAULT_CAPACITY = 10;
是默认的容量大小。当不指定容量大小的时候,就是用默认容量10
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这两个作用类似,在创建一个新的ArrayList对象的时候引用。初始化ArrayList中的数组(即elementData变量)。
两个Object数组本身并没有区别,定义这样没区别的两个是为了区分使用的是哪种构造方法。在ArrayList()方法中用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而在ArrayList(int initialCapacity)和ArrayList(Collection<? extends E> c)方法中用的是EMPTY_ELEMENTDATA。
transient Object[] elementData;
tip:这里有一个transient关键字,是关于序列化和反序列化的。简单说一下,序列化就是把对象转换成字节序列的形式,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可以用于网络传输。一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。序列化之后再反序列化就能得到原来的java对象。而transient修饰的数据将不会被自动序列化。但是,elementData会被writeObject方法手动序列化,这个不多说了。
这个变量就是ArrayList中的Array,ArrayList中的元素就是保存在这里。
private int size;
数组中元素的个数,当我们调用size()方法的时候返回的就是这个size。跟容量(capacity)是两个不同的概念,不要混淆。
protected transient int modCount = 0;
还有一个继承自其父类AbstractList的属性,modCount,表示发生结构化改变的次数。关于结构化改变(Structural modifications),AbstractList中是这样子解释的
The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
意即当size改变了,这个modCount就会+1。
测试代码是这样的:
public class Test{
public static void main(String[] args) {
List<String> list = new ArrayList();
for(int i = 0; i <= 10; i++){
//向list中依次添加“ii”。
//例如,当i为0时,向list添加"00";当i为1时,向list添加"11"。
StringBuilder sb = new StringBuilder();
sb.append(i)
.append(i);
list.add(sb.toString());
}
}
}
运行结果是ArrayList的size为11,容量为15
下面是ArrayList在执行add()方法的时候所涉及的方法,我给放在一个页面里方便查找。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
/*
some code
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*
some code
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/*
some code
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
some code
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/*
some code
*/
}
当i = 0时,此时是第一次向list中添加元素,elementData容量不足,需要加大elementData的容量。初始的list是一个空的Object[],依次调用add(), ensureCapacityInternal(), ensureExplicitCapacity(), grow(), calculateCapacity()方法。
在calculateCapacity()方法中,由于list是由ArrayList的无参构造方法构造的,所以elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的结果为true,执行语句:return Math.max(DEFAULT_CAPACITY, minCapacity);会得到DEFAULT_CAPACITY即10。
返回的结果交给ensureExplicitCapacity(),modCount先自增一次,表示ist必然会做一次结构性修改。此时minCapacity - elementData.length > 0结果为true(nimCapacity = 10, elementData.length = 0),执行grow(minCapacity);
在grow()方法中,oldCapacity = 0,newCapacity = 0,minCapacity = 10。(oldCapacity >> 1相当于oldCapacity / 2)故会进入if (newCapacity - minCapacity < 0)条件判断体中的语句,执行newCapacity = minCapacity,令newCapacity = 10,再执行Array.copyOf()方法,将elementData扩充到容量为10。
之后,回到add()方法体中,elementData[size++] = e; 将elemtntData[0]的值赋为"00",然后size再将赋值为1。
可见,扩容发生在grow()方法中,而在ensureExplicitCapacity()方法中决定是否要扩容。
i = 1~9的过程中,elementData的容量都没有发生改变。不做叙述。
当i = 10时,容量为10的elementData已经被填充满了,需要再次扩容,经过之前提到的方法调用顺序,得到新的容量应该为15。进行扩容。
Set
不包含重复元素的集合。更确切地说,是不同时包含使得e1.equals(e2)成立的e1和e2(因为e1与e2的equals()逻辑可以由使用者自己定义)。并且最多包含一个空元素。这个接口是数学集合的一个抽象建模。
注意:如果可变的(mutable)对象用作集合元素,则必须非常小心。当一个对象是集合中的元素时,以影响equals()比较结果的方式更改对象的值,则无法预测集合的行为。有一个特殊情况是,不允许一个集合作为一个元素来包含它自己。所以还是尽量使用immutable的对象作为Set的元素。
HashSet
先回答四个问题:
是否允许空元素(即null) | 是 |
是否允许重复数据 | 否 |
是否有序 | 否 |
是否线程安全 | 否 |
//如何使用HashSet创建一个Set
//不指定存放的元素类型
//默认容量为16
Set set = new HashSet();
//容量为6
Set set = new HashSet(6);
HashSet是基于HashMap实现的,所以需要先理解HashMap。但是我希望这篇文章是List,Set,Map的顺序,并且并不打算换序,所以还不理解HashMap的点这里跳转到后面。
在HashSet中,保存数据的变量名为map:
private transient HashMap<E,Object> map;
一个HashMap,map的key是E类型,即HashSet中的元素;每个key的value是Object类型,这是HashSet类中定义的一个常量,名为PRESENT
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
这只是为了使用HashMap而创建的一个Object对象,用来占掉value的位置,无其他意义 。
HashSet不保证集合的迭代顺序;具体来说,它不保证顺序将随时间保持不变(除非你自己实现一个迭代器去保证迭代顺序)。它的一些基本操作(add()、remove()、contains()和size())的时间复杂度是常量级的,前提是散列函数在存储桶中正确地分散元素(即要把hashCode()写好,让equals()方法判定为相等的两个对象的hashCode也相等,尽量让equals()方法判定为不等的两个对象的hashCode不等)。
迭代此集合需要与哈希集实例的大小(即HashSet中元素的个数)加上支持哈希映射实例的“容量”(存储桶数)之和成比例的时间。因此,如果迭代性能很重要,那么不要将初始容量设置得太高(或者负载系数设置得太低)。
TreeSet
先回答四个问题:
是否允许空元素(即null) | 是 |
是否允许重复数据 | 否 |
是否有序 | 是 |
是否线程安全 | 否 |
//如何使用TreeSet创建一个Set
//不指定存放的元素类型
//无参构造方法
Set set = new TreeSet();
同样是借助Map实现的,不过这次是TreeMap。还不理解TreeMap的点这里跳转到后面。
基于TreeMap的NavigableSet实现。元素是使用它们的自然顺序来排序的,或者通过在设置的创建时提供的比较器来排序的,这取决于使用的是哪个构造函数。
此实现为基本操作(add()、remove()和contains())提供了保证的log(n)时间成本。
注意,如果要正确实现集合接口,集合维护的顺序(无论是否提供显式比较器)必须与equals一致。(请参阅Comparable或Comparator以获得与equals一致的精确定义。)这是因为集合接口是根据equals操作定义的,但TreeSet实例使用其CompareTo(或Compare)方法执行所有元素比较,因此从TreeSet的角度来看,该TreeSet认为相等的两个元素应该是equals()方法返回true的两个元素。这个TreeSet的行为是清晰的,即使其中元素的顺序与equals()方法不一致;它只是不遵守Set接口的总规约(即spec)。
注意,这个实现是不同步的。如果多个线程同时访问一个树集,并且至少有一个线程修改了该集,则必须在外部对其进行同步。这通常是通过在自然封装集合的某个对象上进行同步来完成的。如果不存在此类对象,则应使用collections.synchronizedSortedSet方法“包装”集合。这最好在创建时完成,以防止意外的对集合的非同步访问:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
Map
将key映射到value的对象。Map不能包含重复的key;每个key最多可以映射到一个value
HashMap
TreeMap
最后
以上就是如意盼望为你收集整理的java中常用的几个集合类概览具体的几个集合类(List,Set,Map)的全部内容,希望文章能够帮你解决java中常用的几个集合类概览具体的几个集合类(List,Set,Map)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复