什么是集合?
存储多个对象的单一对象(java容器)。
为什么不用数组?
集合与数组都是对数据进行存储操作的结构,简称java容器。
此时的存储主要是内存方面的,不涉及到持久话存储(.txt,.jpg,数据库)。
数组存储的缺点:
1、一但初始化,长度确定。
2、数组创建后,元素类型确定。
3、数组提供的方法有限。
4、数组不能够存储无序,不可重复的数据。
集合框架
集合框架有两个父接口:Collection和Map 和 Collections工具类。
一、父接口Collection
单列集合,用来存储一个一个的对象。
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals方法。
方法 | 简介 |
---|---|
add(object obj) | 将元素obj添加到集合当中 |
size() | 获得添加元素的个数 |
addAll(Collection collection1) | 将集合collection1,添加到原有集合中 |
isEmpty() | 判断当前集合是否为空 |
clear() | 清空集合元素 |
boolean remove(object obj) | 通过元素的equals方法找到要删除的元素,并且只会删除找到的第一个元素 |
boolean removeAll(Collection coll) | 取当前集合的差集 |
boolean retainAll(Collection coll) | 把交集的结果存储到当前集合中,不影响coll |
boolean equals(Object obj) | 判断集合元素是否相等 |
Object[] toArray() | 转成对象数组 |
hashcode() | 获取集合对象的哈希值 |
contains(Object obj) | 判断集合对象中是否包含obj,判断时会调用obj对象所在类的equals方法 |
containsAll(Collection coll) | 判断coll中元素是否都存在与当前集合中 |
集合—>数组
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(789);
Object[] arr = coll.toArray();
数组—>集合
List<String> list = Arrays.asList(new String[] {"aa","bb","cc"});
//不能进行增删操作:因为转成的集合对象是Arrays里内部类的ArrayList,不是集合框架中的ArrayList。
//如果想要进行增删操作,需要新建一个集合对象。
List<String> list1 = new ArrayList<String>(list);
Collection集合对象遍历
Collection接口继承了java.lang.Iterable接口,该接口中有一个Iterator()方法。
迭代器Iterator
1、iterator只适用于遍历,它本身不具备承载对象的能力。如果需要创建迭代器对象,必须要先有一个被迭代的集合。
Collection coll = new ArrayList();
coll.add(123);
coll.add(234);
coll.add("AA");
Iterator it = coll.iterator();
while(it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
2、集合对象每次调用iterator都会得到一个全新的迭代器对象。默认的游标在集合第一个元素之前。
3、在迭代过程中,迭代器可以通过remove()方法删除集合中的元素,此remove并非是集合框架中的remove()方法。
4、如果在未调用next()方法之前或再之后调用两次remove(),调用remove方法,会报IllegalStateException
Collection coll = new ArrayList();
coll.add(123);
coll.add(234);
coll.add("AA");
Iterator it = coll.iterator();
//hasNext():判断是否还有下一个元素
while(it.hasNext()) {
//next():1、指针下移 2、将下移后集合位置上的元素返回
Object obj = it.next();
if(obj.equals("AA")) {
it.remove();
}
}
Iterator it1 = coll.iterator();
while(it1.hasNext()) {
System.out.println(it1.next());
}
foreach遍历
java5.0提供了foreach(增强for循环),可以对集合和数组进行遍历。
增强for循环底层仍然调用了iterator
for(遍历的元素类型 遍历元素的自定义名称:遍历结构名称){
System.out.println(遍历元素的自定义名称);
}
常规for循环也可以实现集合的遍历
二、Collection的子接口1:List
List接口概述
1、鉴于java数组存储数据的局限性,通常使用List代替数组。
2、List集合中的元素有序,且可重复,集合中每个元素都有其对应的顺序索引–>常称为动态数组。
3、List的常用实现类有ArrayList,linkedList,Vector。_
ArrayList
List接口的主要实现类,线程不安全的,效率高。底层使用的是数组:Objec[] elementData存储。
LinkedList
对于频繁的删除,插入操作,LinkedList要比ArrayList效率要高,因为它的底层是双向链表存储,内部没有声明数组。线程也是不安全的。
Vector
作为List接口的古老实现类,它的线程是安全的,效率低。底层使用的是数组:Objec[] elementData存储。
List接口的常用方法
List除了继承了Collection方法外,List还添加了一些对于索引操作的方法。
方法 | 简介 |
---|---|
void add(int index,Object ele) | 在index处插入ele元素 |
boolean addAll(int index,Collection eles) | 在index处开始将eles集合中所有元素插入 |
Object get(int index) | 获取索引为index的元素 |
int indexof(Object obj) | 返回obj在集合中首次出现的位置,不存在返回-1 |
int lastIndexOf(Object obj) | 返回obj在集合中末次出现的位置,不存在返回-1 |
Object remove(int index) | 删除下标为index的集合元素,并返回此元素 |
Object set(int index,Object ele) | 设置index位置的元素为ele |
List subList(int fromIndex,int toIndex) | 返回fromIndex到toIndex位置的子集合 |
List的遍历
1、迭代器Iterator
ArrayList list = new ArrayList();
list.add(123);//自动装箱
list.add(456);
list.add("AA");
Iterator it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
2、增强for循环
for(Object obj:list) {
System.out.println(obj);
}
3、普通for循环
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i));
}
区分remove(int index)和remove(Object obj)
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1);//自动装箱
list.add(2);
list.add(3);
updataList(list);
System.out.println(list);
}
private static void updataList(ArrayList list) {
list.remove(2);//这里的2指的是index
list.remove(new Integer(2));//这里的2指的是对象
}
三、Collection子接口2:Set
使用Set集合,必须重写元素所在类的hashcode()和equals()方法。
Set接口概述
1、Set接口是Collection的子接口,并没有提供额外的方法。
2、Set接口存储无序,且不可重复的数据。
3、Set接口判断数据是否重复不使用 “==”,而是equals方法。
4、Set的常用实现类:HashSet,LinkedHashSet,TreeSet。
HashSet
HashSet作为Set的主要实现类,线程不安全,可以存储NULL值。底层:数组+链表。
LinkedHashSet
做遍历操作时,可以按照添加的顺序遍历。
TreeSet
可以按照添加对象的指定属性进行排序。不能够添加所属不同类的对象。
底层采用红黑树结构,有序,查询速度比List快。
不可重复性与无序性描述
1、无序性!=随机性
所谓无序性是指,存储的数据在底层数组中并非按照索引顺序添加。而是根据数据的哈希值进行添加。
public static void main(String[] args) {
Set set = new HashSet();
set.add(123);
set.add(456);
set.add("AA");
set.add("BB");
System.out.println(set);
}
以上代码第一次运行结果:[AA, BB, 456, 123]
第二次运行:[AA, BB, 456, 123]
2、不可重复性
保证添加的元素按照equals判断时,不能返回true。即:相同的元素只能添加一个。
Set的存储过程
以HashSet为例:
向HashSet集合添加元素a,首先调用a所在类的hashcode()方法,计算元素a的hash值。
此哈希值接着通过某种算法计算出HashSet底层数组的存放位置(即,索引位置),判断
数组此位置上是否有元素:
如果此位置上没有其他元素,则a元素添加成功。
如果此位置上其他元素b(或以链表形式存在多个元素),则比较元素a与元素b的哈希值:
如果hash值不相同,添加成功。
如果hash值相同,进而需要调用a所在类的equals()方法:
equals()返回true,元素a添加失败。
equals()返回false,元素添加成功。
在添加元素a时,若此位置上有其他元素b或多个元素,那么在这个索引位置的元素以链表结构存储:
jdk7:元素a放到数组中,指向原来元素。如图1-2。
jdk8:原来的元素在数组中,指向元素a。如图1-3。
口诀:七上八下
存储流程图
图片1-2
图片1-3
LinkedHashSet无序性论证
在添加元素时,每个数据还维护了两个引用,记录此数据前一个数据位置和 后一个数据位置。实际存储的还是无序的。
对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
TreeSet排序
自然排序(Comparable)
在自然排序中,比较两个对象是否相同的标准为:CompareTo()返回0,不再是equals()。
例子1:
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(13);
set.add(-56);
set.add(5);
set.add(56);
System.out.println(set);
}
结果为[-56, 5, 13, 56] 即,从小到大排序。
例子2:
按照姓名排序,元素所在类需要实现comparable接口,重写compareTo()方法。
TreeSet set = new TreeSet();
set.add(new User("jack",14));
set.add(new User("kite",14));
set.add(new User("jom",14));
set.add(new User("tonny",14));
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
结果: User [name=jack, age=14]
User [name=jom, age=14]
User [name=kite, age=14]
User [name=tonny, age=14]
元素所在类compareTo():
public int compareTo(Object o) {
if(o instanceof User) {
User user = (User)o;
return this.name.compareTo(user.name);
}else {
throw new RuntimeException("输入类型不匹配");
}
}
定制排序(Comparator)
需要创建comparator对象。
在自然排序中,比较两个对象是否相同的标准为:Compare()返回0,不再是equals()。
public static void main(String[] args) {
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);//不加com,按照自然排序
set.add(new User("jack",14));
set.add(new User("kite",52));
set.add(new User("jom",19));
set.add(new User("tonny",1));
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
四、Map接口
双列数据,存储key-value对数据。存储无序的双列数据。
key所在的类,需要重写hashcode()和equals()方法。
value所在的类,需要重写equals()方法。
Map实现类的对比
HashMap:作为map的主要实现类;线程不安全,效率高,存储null的key和value,底层:数组+链表(jdk7及之前),数组+链表+红黑树(jdk8)。
LinkedHashMap:在遍历map元素时,可以按照添加顺序进行遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个元素和后一个元素。
对于频繁的遍历操作,效率高于HashMap。
TreeMap:按照添加的key-value对进行排序(按照key排序),实现排序遍历,考虑key的定制排序或自然排序。底层是红黑树。
HashTable:作为古老的实现类,线程安全,效率低,不能存储null的key和value。
Properties:HashTable的子类,常用来处理配置文件,key和value都是String类型。
Map结构
key:无序的,不可重复的,使用Set存储所有key。
value:无序的,可重复的,使用Collection存储所有value。
一个键值对:key-value构成了一个Entry对象,Entry:无序的,不可重复,使用Set存储。
Map接口的常用方法
增删改 | 简介 |
---|---|
Object put(Object key,Object value) | 将指定key-value添加/修改 当前mao对象中 |
void putAll(Map m) | 将m中的key-value对存放到当前map中 |
Object remove(Object key) | 移除指定key的key-value对,并返回value |
void clear() | 清空当前map中的所有数据 |
查询 | 简介 |
---|---|
Object get(Object key) | 获取指定key对应的value |
boolean containsKey(Object key) | 是否包含指定的key |
boolean containsValue(Object value) | 是否包含指定的value |
int size() | 返回map中key-value对的个数 |
boolean isEmpty() | 判断当前map是否为空 |
boolean equals(Object obj) | 判断当前map和参数对象obj是否相等 |
元视图操作 | 简介 |
---|---|
Set keySet() | 返回所有key构成的Set集合 |
Collection values() | 返回所有value构成的Collection集合 |
Set entrySet() | 返回所有key-value对构成的Set集合 |
HashMap的底层实现原理
jdk7:
HashMap map = new HashMap();
在实例化后,底层创建了一个长度为16的一维数组Entry[] table.
....
map.put(key,value);
....
首先,调用key所在类的hashcode()计算key的hash值,此hash值通过某种算法计算以后,得到在Entry数组中存放的位置。
如果此位置上的数据为空,此时的key-value添加成功。
如果此位置上的数据不为空,(意味着此位置上存在一个或者多个数据(以链表形式存在)),比较key和已经存在的一个或多个数据的hash值:
如果key的hash值与已经存在数据的hash值都不相同,此时key-value添加成功。
如果key的hash值与已经存在某个数据的hash值相同,调用key所在类的equals():
如果equals()返回false:此时key-value添加成功。
如果equals()返回true:将value替换原来的数据。
在不断添加的过程中,会涉及到扩容问题,默认的扩容方式为:扩容为原来容量的2倍,并将原来的数据复制过来。
jdk8相较于jdk7在底层实现方面的不同:
1、new HashMap():底层没有创建一个长度为16的数组。
2、jdk8底层数组是:Node[],而非Entry[]。
3、首次调用put()方法时,底层创建长度为16的数组。
4、
jdk7底层结构只 有:数组+链表。jdk8底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数 >8 且当前数组长度 >64 时,此时此索引位置上的所有数据改为使用红黑树存储。
HashMap遍历
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("a", 15);
map.put(12, 32);
map.put("s", "aa");
map.put("S", "kk");
//遍历所有key集:keySet()
Set set = map.keySet();
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//遍历所有value集:values()
Collection values = map.values();
for(Object obj:values) {
System.out.println(obj);
}
//遍历所有key-value(Entry)
Set EntrySet = map.entrySet();
Iterator it1 = EntrySet.iterator();
while(it1.hasNext()) {
Object obj = it1.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;//Map.Entry是Map的内部接口
System.out.println(entry.getKey()+","+entry.getValue());
}
}
TreeMap排序
向TreeMap中添加的key-value,要求key必须有同一个类创建。
用key进行排序,自然排序与定制排序。详见TreeSet。