文章目录
- Java集合框架概述
- 集合框架涉及到的API
- Collection接口中的常用方法1
- Collection接口中的常用方法2
- Collection接口中的常用方法3
- Collection接口中的常用方法4
- 使用Iterator遍历Collection
- 迭代器Iterator的执行原理
- Iterator遍历集合的两种错误
- Iterator迭代器remove()的使用
- 新特性foreach循环遍历集合或项目
- List接口常用实现类的对比
- ArrayList的源码分析
- LinkedList的源码分析
- Vector的源码分析
- List接口中常用方法的测试
- Set接口实现类的对比
- Set的无序性和不可重复性
- HashSet中元素的添加过程
- 关于hashCode()和equals()的重写
- LinkedHashSet的使用
- TreeSet的自然排序
- TreeSet的定制排序
Java集合框架概述
-
Java集合就像一种容器,可以动态把多个对象的引用放入容器中
-
集合框架的概述
- 1、集合、数组都是对多个数据进行存储操作(内存层面)的结构,简称Java容器
- 2.1、数组在存储多个数据方面的特点:
-
一旦初始化之后,其长度就确定了
-
数组一旦定义好,其元素的类型也就确定了。只能操作指定类型的数据。比如:`String[] arr` ` int[] arr`
-
- 2.2、数组在存储多个数据方面的缺点:
-
一旦初始化之后,其长度就确定了
-
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不方便,同时效率也不高
-
获取数组中实现元素的个数的需求,数组没有现成的属性或方法可用
-
数组存储数据的特点:有序、可重复;对于无序、不可重复的需求,不能满足
-
集合框架涉及到的API
- Java集合可分为Collection和Map两种体系
-
Collection接口:单列集合,用来存储一个一个的对象
- List接口:有序的、可重复的数据 —>"动态"数组
- ArrayList、LinkedList、Vector
- Set接口:无序的、不可重复的数据 —>类似于高中讲的"集合"
- HashSet
- LinkedHashSet
- TreeSet
- List接口:有序的、可重复的数据 —>"动态"数组
-
Map接口:双列集合,用来存储一对一对(Key-Value)的数据 —>函数 y = f ( x ) y = f(x) y=f(x)
- HashMap
- LinkedHashMap
- TreeMap
- HashTable
- Properties
Collection接口中的常用方法1
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
//size():获取添加元素的个数
System.out.println(coll.size());
//addAll(Collection c):将c中集合的元素添加到当前集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
System.out.println(coll.size());//6
System.out.println(coll);
//isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());//false
//clear():清空集合元素
coll.clear();//并不是把coll置为空指针,而是把数据清空了
coll.isEmpty();//true
}
}
Collection接口中的常用方法2
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
coll.add(new Person("Tom",20));
//contains(Object obj):判断当前集合是否包含obj
//在判断时会调用obj对象所在类的equals()方法
//向Collection接口的实现类中添加数据obj时,要求obj所在类要重写equals()方法
boolean con = coll.contains("A");//true
System.out.println(con);
System.out.println(coll.contains(new String("CCC")));//true (调用的是equals)
System.out.println(coll.contains(new Person("Tom",20)));//true,但若没有重写equals方法则为false
//containsAll(Collection c):判断c中的所有元素是否都存在于当前集合中
Collection coll1 = Arrays.asList(123,456);//返回的是List接口
System.out.println(coll.containsAll(coll1));//false
}
}
class Person{
private String name;
private int age;
public Person(String name,int age) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
}
Collection接口中的常用方法3
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
//remove(Object obj):从该集合中删除指定元素的单个实例(如果存在),删除成功返回true,失败返回false
//如果要删除自定义类,则需要重写equals()方法
coll.remove(123);
System.out.println(coll);
//removeAll(Collection c):差集:从当前集合中移除c中的所有元素
Collection coll1 = Arrays.asList(123,"A");
coll.removeAll(coll1);
System.out.println(coll);
}
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
//retainAll():仅保留此集合中包含在指定集合中的元素,返回值为boolean.
Collection coll1 = Arrays.asList(123,456,789);
coll.retainAll(coll1);
System.out.println(coll); //[123]
//equals(Object obj):将指定的对象与此集合进行比较以获得相等性 要注意List的有序性 返回值为boolean
System.out.println(coll.equals(Arrays.asList(123))); //true
}
Collection接口中的常用方法4
public class CollectionTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
//hashCode():返回此集合的哈希值
System.out.println(coll.hashCode());
//toArray():返回一个包含此集合中所有元素的数组Object[]
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//拓展:数组-->集合:调用Arrays的静态方法asList()
List<String> list = Arrays.asList(new String[]{"AA","bb","cc"});
System.out.println(list);
List list1 = Arrays.asList(new int[]{123,456});
System.out.println(list1);//[[I@4ee285c6]
System.out.println(list1.size());//1
List list2 = Arrays.asList(new Integer[]{123,456});
System.out.println(list2.size());//2
//iterator():返回此集合中的元素的迭代器(Iterator接口的实例),用于遍历集合元素
}
}
使用Iterator遍历Collection
/**
* 集合元素的遍历操作,使用迭代器Iterator接口
* hasNext() next()
*
*/
public class IteratorTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
//方式1:不推荐
Iterator iterator = coll.iterator();
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// //异常:java.util.NoSuchElementException
// System.out.println(iterator.next());
//
// //方式2:不推荐
// for (int i=0;i<coll.size();i++){
// System.out.println(iterator.next());
// }
//方式3
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
迭代器Iterator的执行原理
Iterator遍历集合的两种错误
@Test
public void test01(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
Iterator iterator = coll.iterator();
//错误方式一:指针已经改变
while (iterator.next()!=null){ //java.util.NoSuchElementException
System.out.println(iterator.next());
}
//错误方式二 死循环(iterator()每次都生成新的迭代器对象,默认游标都在第一个元素之前
while (coll.iterator().hasNext()){
System.out.println(coll.iterator().next());
}
}
Iterator迭代器remove()的使用
/**
* 迭代器Iterator的remove():从底层集合中删除此迭代器返回的最后一个元素
* 此方法不同于集合直接调用remove
* 异常
* UnsupportedOperationException -如果 remove操作不会被这个迭代器支持
* IllegalStateException - 如果 next方法尚未被调用,或者 remove方法在上次调用 next方法之后已经被调用
*/
public class IteratorTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
if ("CCC".equals(obj)){
iterator.remove();
}
}
//再次遍历集合
Iterator iterator1 = coll.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
}
}
新特性foreach循环遍历集合或项目
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("A");
coll.add(new String("CCC"));
coll.add(false);
//for (集合元素类型 局部变量:集合对象)
//底层还是Iterator
for (Object obj:coll){
System.out.println(obj);
}
}
List接口常用实现类的对比
List接口:有序的、可重复的数据 —>"动态"数组
- ArrayList、LinkedList、Vector
- 相同点:三者都实现了List接口,并且都存储有序的、可重复的数据
- 但是ArrayList是List接口的主要实现类,线程不安全,执行效率高,底层使用Object[]存储
- Vector是List接口的古老实现类,线程安全,执行效率低,底层使用Object[]存储
- LinkedList对于频繁的插入和删除操作,使用LinkedList效率较高,因为底层使用双向链表存储。
ArrayList的源码分析
-
JDK7和JDK8情况下稍有不同
-
JDK7情况下
ArrayList list = new ArrayList();//底层创建了一个长度是10的Object[]数组elementData list.add(123); //elementData[0] = 123; ... list.add(11);//如果此次的添加导致底层elementData数组容量不够则扩容。默认情况下扩容为原来的1.5倍,同时将原有数组的数据复制到新数组中 //结论:开发中建议使用带参的构造器ArrayList list = new ArrayList(int capacity);
-
JDK8情况下
transient Object[] elementData; // non-private to simplify nested class access /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } 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); }
ArrayList list = new ArrayList();//底层Object[]数组elementData初始化为{},并没有创建长度为10的数组 list.add(123); //底层创建长度为10的数组,并将123添加到第一个位置上,后续添加与扩容操作与JDK7相同。 ... list.add(11);
-
小结:JDK7中的ArrayList对象创建类似于单例模式中的饿汉式
JDK8中的ArrayList对象创建类似于单例模式中的懒汉式,延迟了对象创建,节省内存。
LinkedList的源码分析
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
@Test
public void test(){
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node,创建Node对象
}
Vector的源码分析
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
-
JDK7和JKD8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组
在扩容方面,默认扩容为原来数组长度的2倍
List接口中常用方法的测试
- 重点:CRUD、遍历
public class ListTest {
@Test
public void test(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("A");
list.add(false);
System.out.println(list);
//add(int index, E element) 将指定的元素插入此列表中的指定位置
list.add(1,"ABC");
System.out.println(list);
//addAll(int index, Collection<? extends E> c) 从index位置开始将c中所有元素添加到当前List
List list1 = Arrays.asList(1,2,3);
list.addAll(2,list1);
System.out.println(list.size());
//indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1
System.out.println(list.indexOf(123));
//int lastIndexOf(Object o)
//返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
//E remove(int index) 删除该列表中指定位置的元素
//boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)
//boolean removeAll(Collection<?> c) 从此列表中删除包含在指定集合中的所有元素
list.remove(2);//默认index
list.remove(new Integer(123));
list.remove("A");
//E set(int index, E element) b用指定的元素替换此列表中指定位置的元素
list.set(0,999);
//List<E> subList(int fromIndex, int toIndex) 返回此列表中指定的 fromIndex (含)和 toIndex之间的视图
//(左闭右开)
List list2 = list.subList(2,4);//list本身不改变
}
}
Set接口实现类的对比
-
Set接口:存储无序的、不可重复的数据
- HashSet:作为Set接口的主要实现类;县城不安全,可以存储null值
- LinkedHashSet:extends HashSet,作为HashSet的子类,遍历其内部数据时可以按照添加的顺序遍历
- TreeSet:可以按照添加对象的指定属性进行排序
-
Set接口是Collection的子接口,set接口没有提供额外的方法
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个
Set 集合中,则添加操作失败。
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
Set的无序性和不可重复性
Set:存储无序的、不可重复的数据
Set接口中没有额外定义新的方法,使用的都是Collection中声明的方法
以HashSet为例说明:
- 无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加
- 不可重复性:保证添加的元素按照equals()方法判断时,不能返回true,即相同的元素只能添加一次。
HashSet中元素的添加过程
HashSet底层:数组+链表
以HashSet为例
向HashSet中添加元素a,首先调用元素a所在类的hashCode方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在hashSet底层数组中的存放位置(即索引位置),判断数组此位置上是否已经有元素:
-
如果此位置没有其他元素,则元素a直接添加成功;
-
如果此位置上有其他元素b(或已经存在以链表形式的多个元素),则比较元素a和元素b的hash值,如果hash值不相同,则元素a添加成功;
-
如果hash值相同,进而需要调用元素a所在类的equals()方法,equals()返回true,元素a添加失败,返回false则添加成功。
对于2和3添加成功相同的情况而言,元素a与已经存在指定索引位置上的数据以链表的方式存储
jdk7:元素a放到数组上,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
总结:七上八下(康师傅太秀了)
关于hashCode()和equals()的重写
要求:向Set中添加的数据,其所在类一定要重写equals()和hashCode()
要求:重写的equals()和hashCode()尽可能保持一致性 相等的对象必须具有相等的散列码
结论:复写equals方法的时候一般都需要同时复写hashCode方法。通
常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
LinkedHashSet的使用
-
LinkedHashSet 是 HashSet 的子类
-
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,
但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入
顺序保存的。
-
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全
部元素时有很好的性能。
-
LinkedHashSet 不允许集合元素重复。
-
涉及频繁的遍历操作时,比HashSet效率要高
TreeSet的自然排序
- 向TreeSet中添加的数据,要求是相同类的对象,否则报异常
java.lang.ClassCastException
- 两种排序方式:自然排序(实现comparable接口)和定制排序(实现Comparator接口)
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,而不再是equals()
public class User implements Comparable{
private int age;
private String name;
public User(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//按照姓名从小到大排列,若姓名相同则按年龄从小到大
@Override
public int compareTo(Object o) {
if (o instanceof User){
User user = (User)o;
//return this.name.compareTo(user.name);
int compare = this.name.compareTo(user.name);
if (compare!=0)
return compare;
else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
@Test
public void test(){
TreeSet set = new TreeSet();
set.add(new User(15,"Tom"));
set.add(new User(20,"Davis"));
set.add(new User(18,"James"));
set.add(new User(32,"Rose"));
set.add(new User(35,"Iverson"));
set.add(new User(34,"Iverson"));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
TreeSet的定制排序
- 自然排序中,比较两个对象是否相同的标准为:compare()返回0,而不再是equals()
@Test
public void test2(){
Comparator com = new Comparator() {
//按年龄从小到大排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User(15,"Tom"));
set.add(new User(20,"Davis"));
set.add(new User(18,"James"));
set.add(new User(32,"Rose"));
set.add(new User(35,"Iverson"));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}