单值集合Collection和键值对集合Map(一)

论坛 期权论坛 编程之家     
选择匿名的用户   2021-5-31 23:54   11   0

目录

一、集合概念

1、类集的出现

2、集合结构图

二、Collection接口

1、List接口

2、ArrayList实现类

3、Vector实现类

4、ArrayList类和Vector类的区别

5、LinkedList实现类

6、Set接口

7、HashSet实现类

8、TreeSet实现类

(1)特点

(2)定义

(3)排序的说明

其他扩展:

9、关于重复元素的说明

三、集合输出Iterator接口

1、Iterator

2、双向输出ListIterator接口

3、介绍废弃的接口Enumeration

4、foreach输出集合(增强for)

四、Map集合

1、Map接口

2、Map接口与Set接口关系

3、HashMap实现类

4、哈希算法的存储实现原理

(1)存储方式:对象数组+链表

(2)哈希表扩容

(3)哈希表的重新散列

5、Hashtable类和HashMap类的区别

6、TreeMap类

7、Map集合的输出讲解

五、常见集合工具类Collections

1、分析hashCode、equals和内存泄漏

(1)equals():

(2)hashCode():

(3)java.lang.Object中对hashCode的约定:

(4)在java集合中,判断两个对象是否相等:

(5)内存溢出:

六、总结


一、集合概念

1、类集的出现

(1)普通对象数组最大的问题在于长度是固定,不能动态扩充大小;最早的时候通过链表实现动态数组。有时候把类集称为java对数据结构的实现。类集的概念是jdk1.2(java2)开始引入的。

(2)类集中最大的几个接口操作:Collection、Map、Iterator。

所有的类集操作的接口或类都在java.util包中。

2、集合结构图

  • 存放单值数据考虑使用Collection接口,存放键值对考虑使用Map接口
  • Collection接口不直接使用,一般使用List接口和Set接口;List接口存放重复数据且是有序的,Set接口存放不重复数据且是无序的;
  • List接口常用实现类:ArrayList和LinkedList,Set接口常用实现类:HashSet和TreeSet
  • ArrayList内部是数组结构,特点是增删慢、查找快,日常开发中最常用操作是查找遍历元素,所以ArrayList最常用;LinkedList内部是双向链表,增删快,存在大量首尾操作方法,可以作为堆栈和队列使用;
  • HashSet内部采用哈希表存储,哈希表实现方式数组+链表(JDK1.8以后采用数组+链表+红黑树),因此具有良好的存储和查找性能。TreeSet内部使用有序二叉树存储,输出的时候按照内部排序进行输出,因此如果存储自定义类需要实现类的Comparable接口。
  • Map接口(Map是Mapping映射的意思)常用实现类:HashMap、TreeMap;
  • HashMap采用哈希表存储,是无序的;TreeMap采用二叉树存储,是有序的,按照key进行排序,不过如果key是对象则对象必须实现Comparable接口。
  • 集合的常用操作工具类:Collections和Arrays。
  • 进行排序除了可以实现类对象自己的Comparable接口,也可以通过创建Comparator比较器实现。

二、Collection接口

(1)Collection接口是在整个java类集中保存单值的最大操作父接口,里面每次操作都只能保存一个对象的数据。

(2)接口定义:public interface Collection<E> extends Iterable<E>

(3)常用方法:

开发中不会直接使用Collection接口。而是使用其操作的子接口List、Set。

子接口List、Set都会继承上面的方法,开发中经常使用到方法(红色部分):

add()、iterator()、size()

1、List接口

list是Collection的子接口,里面的所有内容都是允许重复的。

public interface List<E> extends Collection<E>

此接口相较于Collection有如下扩充方法。

开发中常用的:add()、iterator()、size()、get()

List接口常用实现类:ArrayList(95%)、Vector(4%)、LinkedList(1%)

2、ArrayList实现类

ArrayList是List接口的子类,定义如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,Serializable

特点:

(1)AbstractList 是 List的子类,且是一个抽象类,适配器设计模式。

(2)内部使用数组结构:增加删除慢,查找快。

内部实现不定长度数据存储办法采用动态扩容。

(3)构造函数:

ArrayList其实最开始创建的数组是一个空数组,长度为0。

无参构造函数,初始创建10个长度的数组,每次扩容1.5倍。

有参构造函数,如果初始需要一个非常大容量的对象,一定要使用该构造方法。

3、Vector实现类

Vector本身也是List接口的子类,定义如下:

public class Vector<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,Serializable

使用Vector操作结果与ArrayList没有任何区别,它是一个比ArrayList古老的实现类。

4、ArrayList类和Vector类的区别

最大的区别在于ArrayList的性能较高,采用异步处理;而Vector采用同步处理,性能较低。

两者都支持Iterator、ListIterator输出,但是Vector多了一种Enumeration输出。

5、LinkedList实现类

使用概率较低,定义:

public class LinkedList<E> extends AbstractSequentialList<E>

implements List<E>,Deque<E>,Cloneable,Serializable

此类继承了AbstractList,所以是List的子类。但此类也是Queue接口的子类,Queue接口定义了如下方法:

(1)LinkedList当做堆栈使用

压栈:push()方法

弹栈:pop()方法

(2)LinkedList当做队列使用

入队:addFirst()

出队:removeLast()

6、Set接口

Set接口也是Collection的子接口,与List接口最大的不同在于Set里面的内容不允许重复。

Set接口没有对Collection接口进行扩充,基本和Collection保持一致。

特点

  • List中的get(int index)方法。
  • add、remove方法,没有set方法,
  • (1)调用iterator()得到迭代器、(2)toArray()转为数组对象访问。
  1. 接口中两个常用子类:Hash、Tree

7、HashSet实现类

HashSet属于散列存放的类集(哈希表)内部实现是HashMap,里面的内容是无序存放的。

将HashSet循环输出,需要将其转换为数组,使用Set中定义的toArray方法可以实现。

使用泛型方法转换为数组:<T> T[] toArray(T[] a)

特点:

(1)HashSet中是不能有任何重复元素的。

(2)HashSet输出元素是无序(即与存储顺序无关)。

8、TreeSet实现类

(1)特点

(1)内部实现是一个有序的二叉树存储 TreeMap;

(2)遍历输出的时候,会按照内部数据顺序进行排序输出。

(2)定义

Public class TreeSet<E> extends AbstractSet<E>

implements NavigableSet<E>,Cloneable,Serializable

(3)排序的说明

Set<Person> all=new TreeSet<Person>();

all.add(new Person("张三",10));

all.add(new Person("李四",11));

(1)TreeSet存储自定义对象时候,如果会抛出异常 java.lang.ClassCastException

想要进行正常使用,需要在Person类中实现Comparable接口(因为TreeSet需要保证数据存入的有序和不重复性)

(2)即使实现了Comparable比较了年龄,如果存入同样的年龄,那么数据也不会存入,因为年龄相同,所以程序任务存入数据和以前的数据是同一个数据。

all.add(new Person("王五",11));

public class Person implements Comparable<Person>{
private String name;
private int age;
@Override
public int compareTo(Person per){
//比较 this 和传入 per
//如果返回负数  this小 / 零 一样大 / 正数  this 大
if(this.age>per.age){
return 1;
}else if(this.age<per.age){
return -1;
}else{
return 0;
}
}
......省略
}

此时,如果添加的Person对象中有年龄相同的对象也只会出现一个,如果还想要比较姓名,则需要修改compareTo方法的实现:

将 return 0修改为:return this.name.compareTo(per.name);

其他扩展:

(1)java中字符排序按照ASCII码进行排序,常见:A->65,a->97。

查看编码对应字符,通过强制转换可以查看:

System.out.println((char)65);

System.out.println((char)1);

(2)Comparator:

ArrayList<Book> list=new ArrayList<>();
list.add(new Book("三国志","A0001"));
list.add(new Book("吕氏春秋","A1001"));
list.add(new Book("国风","M0001"));
Collections.sort(list, new Comparator<Book>() {
    @Override
    public int compare(Book o1, Book o2) {
        return o1.getNumber().compareTo(o2.getNumber());
    }
});

9、关于重复元素的说明

如果要判断两个元素是否相等必须使用Object的equals()方法。

从正规的来讲,要判断两个对象是否相等有两种办法:

(1)判断两个对象编码是否一致,这个方法需要通过hashCode完成,

(2)还需要进一步验证对象的每个属性是否相等,通过equals来完成

public boolean equals(Object obj){

if(this==obj){

return true;

}

if(! obj instanceOf Person){

return false;

}

Person per=(Person)obj;

if(per.name.equals(this.name)&&per.age==this.age){

return true;

}else{

return false;

}

}

public int hashCode(){

return this.name.hashCode() * this.age;

}

小结:

(1)关于treeSet的排序实现,如果是集合中的对象是自定义的或者说其他系统定义的类没有实现Comparable接口,则不能实现TreeSet的排序,会报类型转换(转向Comparable接口)错误。

换句话说添加到TreeSet集合中的对象的类型必须实现Comparable接口。

(2)不过TreeSet的集合因为借用了Comparable接口,同时可以去除重复值,而HashSet虽然是Set接口子类,但是对于没有复写Object中hashCode和equals方法的对象,加入HashSet中也是不能去除重复值的。

三、集合输出Iterator接口

集合的输出本身也是有多种形式的:

Iterator 迭代输出(90%)

ListIterator (5%)

foreach (4%)

Enumeration(1%)

1、Iterator

基本原理:不断判断是否有下个元素,有的话,则直接输出。

定义:public interface Iterator<E>

想要使用此接口必须使用Collection接口,在Collection中规定了一个iterator()方法可以用于为Iterator接口实例化操作。

此接口中规定三个方法:

(1)boolean hasNext()

是否有一个元素

(2)E next()

指针向下移动,并且取出指向的内容

(3)void remove()

删除当前内容

使用注意:必须先移动,即先调用next()方法,然后才能remove(),否则没有指向直接remove会报错。

通过Collection接口为其实例化之后,一定要记住,Iterator中的操作指针在第一条元素上,当调用next()时,获取当前指针指向的值并向下移动,使用hashNext()可以检查序列中是否还有元素。

示例:

Collection<String> all=new ArrayList<String>();

all.add("A");

all.add("B");

all.add("C");

all.add("D");

all.add("E");

Iterator<String> iter=all.iterator();

while(iter.hashNext()){

String str=iter.next();

}

Iterator只能向后单向输出,如果想要进行双向输出必须使用其子类:ListIterator

2、双向输出ListIterator接口

ListIterator是可以进行双向输出的迭代接口,定义:

public interface ListIterator<E> extends Iterator<E>

此接口中定义了如下操作方法:

如果想要使用ListIterator,必须使用List对其进行实例化。

如果想要进行由后向前的输出,先要进行由前向后的输出。

此接口使用较少

注意:调用previous()前提,必须先向下走,才能往上走。因为初始的时候,指针没有指向元素,上面已经没有元素了。

3、介绍废弃的接口Enumeration

public interface Enumeration<E>

Vector<String> v = new Vector<String>();

v.add("A");

v.add("B");

v.add("C");

Enumeration<String> enu = v.elements();

while (enu.hasMoreElements()) {

System.out.println(enu.nextElement());

}

4、foreach输出集合(增强for)

格式:for(数据类型 变量名:集合或数组){}

Collection<String> all= new ArrayList<String>();

all.add("A");

all.add("B");

all.add("C");

all.add("D");

all.add("E");

for (String str : all){

System.out.println(str) ;

}

四、Map集合

1、Map接口

Map:是Mapping映射的意思,不是地图

特点

(1)map,存储键值对key->value;

(2)map的key键不可以重复。

collection中每次操作的都是一个对象,如果要操作一对对象就必须使用Map,Map中所有的对象都按照key->value形式保存,也称二元偶对象。

定义:public interface Map<K,V>

此接口与Collection没有什么关系,是第二大类的集合操作接口。

常见方法如下:

Map本身是一个接口,所以一般会用到几个子类:HashMap、TreeMap、HashTable

2、Map接口与Set接口关系

大多数Set集合的内部都使用了Map集合进行存储,因为Map的特性key不能重复,而Set的特性数据不能重复,所以Set内部采用Map存储,存储的时候第一个键是不同且不重复的,第二个值是相同的new Object()。

HashSet、TreeSet、LinkedHashSet (单值存储)

HashMap、TreeMap、LinkedHashMap (键值对存储)

3、HashMap实现类

HashMap是Map的子类,定义:

public class HashMap<K,V> extends AbstractMap<K,V>

implements Map<K,V>,Cloneable,Serializable

此类继承了AbstractMap,同时可被克隆和序列化

HashMap本身时无序存放的。

位运算:<<

1 << 4 =16 (1左移4位等于16)

二进制数:0001 变为了 10000

左移、右移即位移运算速度非常快。

位运算:&

相当于取余运算。

内部源码实现原理:

当一个对象作为HashMap的键进行存储时,不要改变对象中的属性值,会出错:找不到哈希值。

4、哈希算法的存储实现原理

(1)存储方式:对象数组+链表

(2)哈希表扩容

桶的初始数量是:16,散列因子是:0.75

当桶的使用量超过0.75时,会进行2倍的扩容。

哈希表扩容之后存储数据位置可能就变了,一开始的下标为取余length,扩容后的下标为取余length*2,下标结果可能发生变化。

(3)哈希表的重新散列

HashMap的初始大小和散列因子,决定了其性能

散列:重建哈希表结构

初始大小:合理的初始大小很重要。

散列因子:越大,空间利用率越高,查询效率越低;

越小,空间利用率越低,查询效率越高;

5、Hashtable类和HashMap类的区别

jdk1.0推出的,与HashMap操作类似。

Hashtable不能向集合中插入null值。

Hashtable与HashMap的区别:

HashMap:异步处理,性能较高;Hashtable:同步处理,性能较低;

而且Hashtable不允许设置null,会出现空指向异常;HashMap允许设置为null。

(1) HashMap 线程不安全(效率高),并行操作

(2)Hashtable 线程安全(效率低), 排队,等待

(3)ConcurrentHashMap

采用分段锁机制,保证线程安全,效率又比较高。

6、TreeMap类

TreeMap操作时,按照key进行排序。key中的类可以是任意对象,但是要求对象所在的类必须实现Comparable接口

TreeMap,有序存储,采用二叉树存储。

7、Map集合的输出讲解

Map接口本身不能直接使用Iterator输出的。Map接口存放的每一个内容都是一对值,而使用Iterator接口输出的时候,每一次都是取出一个完整的对象。如果此时非要使用Iterator输出,可以如下操作:

(1)使用Map中的entrySet()方法将Map接口的全部内容变成Set集合。

(2)可以使用Set接口中的iterator()方法为Iterator接口进行实例化。

(3)之后使用Iterator接口进行迭代输出,每一次都可以输出一个Map.Entry实例

(4)通过Map.Entry进行key和value分离。

Map.Entry本身是一个接口,定义在Map内部,是Map的内部接口。

此内部接口使用static定义,所以此接口成为外部接口。

实际上每一个存放在Map中的key和value值都是将其转变成为了Map.Entry并且将Map.Entry保存在Map集合中。

在Map.Entry中常用的方法:

(1)K getKey()

得到Key

(2)V getValue()

得到value

示例:使用Iterator输出Map接口

Map<String,String> map=HashMap<String,String>();

map.add("ZS","张三");

map.add("LS","李四");

map.add("WW","王五");

map.add("ZL","赵六");

map.add("SQ","孙七");

Set<Map.Entry<String,String>> set=map.entrySet();

Iterator<Map.Entry<String,String>> iter=set.iterator();

while(iter.hashNext()){

Map.Entry<String,String> me=iter.next();

System.out.println(me.getKey()+"-->"+me.getValue());

}

五、常见集合工具类Collections

Collections是集合的操作类,与Collection没有任何关系,是一个单独存在的类,定义:

public class Collections extends Object

1、分析hashCode、equals和内存泄漏

(1)equals():

比较两个对象地址值是否相等。

equals()方法在Object中的定义:

public boolean equals(Object obj){

return (this==obj);

}

但是:String、Math、Double、Interger这些封装类在使用equals()时已经覆盖了Object类的equals()方法,不再是地址的比较而是内容的比较。

(2)hashCode():

方法在Object中定义如下:

public native int hashCode();

说明是一个本地方法,它的实现是根据本地机器相关的。当然也可以在自己的类中复写hashCode(),例如:String、Interger、Double、

(3)java.lang.Object中对hashCode的约定:

1、在一个应用程序执行期间,如果一个对象的equals方法做比较的信息没有被修改的话,则对该对象调用多次hashCode方法,它始终如一的返回同一个整数。

2、如果两个对象根据equals(Object o)方法是相等的,则调用两个对象中的任意一个对象的hashCode方法必须产生相同的整数对象结果。

3、如果两个对象根据equals(Object o)方法是不相等的,则调用两个对象中的任意一个对象的hashCode方法不要求产生不同的整数结果,但如果能不同,则有可能提高散列表的性能。

(4)在java集合中,判断两个对象是否相等:

1、判断两个对象的hashCode是否相等:

如果不相等,认为两个对象不相等,完毕;

如果相等,进入2

(这一点是为了提高存储效率的要求,理论上可以没有,但如果没有,实际使用效率会大大降低,所以要求必须)

2、判断两个对象用equals运算是否相等

如果不相等,认为两个对象也不相等

如果相等,认为两个对象相等

(5)内存溢出:

当一个对象被存进HashSet集合中,就不能修改这个对象中那些参与计算hash值的那些字段了,否则对象被修改的hash值与最初存进HashSet中的hash值就不同。

在这种情况下,即使在contains方法中使用该对象的当前引用作为参数去HashSet中检索对象,也将返回找不到对象结果,这也会导致无法从HashSet中删除当前对象,从而造成内存溢出。

六、总结

1、类集就是一个动态的对象数组,可以向集合中加入任意多的内容。

2、List接口中是允许有重复元素的,Set接口中不允许有重复元素。

3、所有重复的元素依靠hashCode和equals进行区分。

4、List接口常用子类:ArrayList、Vector

5、Set接口常用子类:HashSet、TreeSet

6、TreeSet可以排序,一个类的对象依靠Comparable接口排序。

7、Map接口中允许存放一对内容:key->value

8、Map接口的子类:HashMap、TreeMap、Hashtable

9、Map使用Iterator输出的详细步骤

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP