Java 集合是一个常用的技术 ,无论是在: 开发使用,还是面试总是高频的提及到~
不适合初学者,适合面试|复习...
存储Object类型
可以通过泛型进行规范!弥补了数组长度固定的缺点更灵活
Java 集合可分为 Collection 和 Map 两种体系
Collection接口: 单列数据,定义了存取一组对象的方法的集合
Map接口:双列数据,保存具有映射关系 key-value
的集合
Collection集合主要有List和Set两大接口 不常用的 Queue接口
List 接口
一个有序,(元素存入集合的顺序和取出的顺序一致) 的集合容器 元素可以重复,可以插入多个null元素,元素都有索引
有三个实现类: ArrayList
LinkedList
Vector
Set 接口
一个无序, (存入和取出顺序有可能不一致) 的集合容器
不可以存储重复元素,只允许存入一个null元素
必须保证元素唯一性
常用实现类: HashSet
LinkedHashSet
TreeSet
JDK不提供此接口的任何直接实现,而是提供更具体的子接口:set
list
都默认继承了Collection 的方法();
boolean .add( object ); //在列表 末尾添元素 和数组一样 起始索引位置从 0 开使,可储存null; 数值类型会自动装箱; void .add( int,object ); //指定索引位置添加元素 但 不可指定 在超过目前 集合长度; void .forEach(System.out::print);//jdk8 新特性;一种循环遍历; void .clear(); //清空集合; int .size(); //返回集合元素个数; boolean .contains( object ); //判断集合在是否存在指定元素; ture/flase; 底层相当于是调 equls();比,String类重写了所以比较的是值; //没重写当然就是比较地址咯; boolean .containAll( Collection ); //判断参数是个集合,判断参数集合自己有没有,都有就true 一个没有就false boolean .equals( Coll... ); //想要返回 true,需要当前集合元素 和 参数集合元素值都相同 地址 ;根据new ArrayList 或其它集合/其顺序也要相同; Object .get( int ); //返回指定索引的元素;( 注意这是Object 类型需要类型转换 ); List .sublist(int1,int2); //返回集合 1-2 位置的子集合; Object .set(int,Object); //设置指定 int 位置的元素; 注意不可以在,集合进行循环遍历的时候移除元素! 因为集合长度发送改变,循环遍历报错! Object .remove( int ); //删除指定下标中的元素; 返回删除的元素; boolean .remove( object/int ); //移除集合中指定元素; true移除成功 false移除失败:不存在要移除元素; //虽然数值类型会装箱,int下标删除了..需要确保下标存在 boolean .removeAll( Collection ); //从集合中移除指定: 一个集合元素; 两个集合A B, A.removeAll(B); //把A B集合中一样的元素移除掉了(交集); 正确的删除需要使用:Iterator对象~ Iterator .iterator(); //返回一个 Iterator对象; 数组集合相互转换 Object[] .toArray(); //可以将集合返回一个 Object[] 数组; 集合 Arrays.aslist( 数组 ); //Arrays类的静态方法: 将数组转换成集合;
Arraylist: 对数组进行封装,实现长度可变的数组(根据增删自动改变元素在集合中位置)
底层其实就是一个,可以动态调节长度的数组
基本使用:
public void jh(){ /** 创建集合对象 */ // Collection coll = new ArrayList(); ArrayList arrayList = new ArrayList(); //List集合存储Object类型元素,支持重复元素! arrayList.add(1); arrayList.add(2); arrayList.add(2); arrayList.add(3); arrayList.add("2"); arrayList.add(null); //可以存储null System.out.println("arrayList集合遍历:"); for (Object o : arrayList) { System.out.println(o); } /** 常用方法 */ //contains(Object obj):判断当前集合中是否包含obj :判断时会调用obj对象所在类的equals(),自定义对象需要重写equals System.out.println("arrayList集合是否存在 1:"+arrayList.contains(1)); //containsAll(Collection coll1) 判断形参coll1中的所有元素是否都存在于当前集合中 ArrayList arrayList2 = new ArrayList(); arrayList2.add(1); arrayList2.add(2); System.out.println("arrayList集合是否存在 arrayList2的所有元素:"+arrayList.containsAll(arrayList2)); //retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合 // arrayList.retainAll(arrayList2); // System.out.println("retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合"); // for (Object o : arrayList) { // System.out.println(o); // } /** 注释上面: retainAll() 让两个集合元素 A.retainAll(B) A集合只保留B 交集共同的数据了~ */ //remove(Object obj):从当前集合中移除obj元素 //removeAll(Collection coll1):差集:从当前集合中移除coll1中所有的元素 arrayList.removeAll(arrayList2); System.out.println("removeAll(Collection coll1):差集:从当前集合中移除coll1中所有的元素"); for (Object o : arrayList) { System.out.println(o); } /** 数组 ———》List集合 */ System.out.println("使用: Arrays. asList(array) 进行转换"); List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"}); System.out.println(list); /** List集合 ———》数组 */ System.out.println("使用: List 自带的 toArray() 方法"); Object[] objects = list.toArray(); for (Object obj : objects) { if(obj instanceof String) // 对象 instanceof 类型 比较对象是否属于改类型~ System.out.print(obj+" "); } }
LinkedList:采用双向链表存储方式
优:
在插入删除时效率更高,同Arraylist一样都实现list接口比Arraylist多了一些方法 缺点就是查找遍历没有Arraylist 快
void .addFist( object ); //在集合 首部 添加元素 (索引0); void .addLast( object ); //在集合 末尾 添加元素; Object .getFist( ); //返回 第一元素; Object .getLast( ); //返回 最后一个元素; Object .removrFist( ); //删除 并 返回第一个元素; Object .removeLast( ); //删除 并 返回最后一个元素;
Vector 读:歪课特
远古代码,jdk1.0就有的
通过 Vector(); 构造器创建对象:
底层就创建了长度为10的数组,扩容方面就是扩展为原数组长度的二倍;
ArrayList和LinkedList的异同
二者都是线程不安全的,相对于线程安全的Vector,执行效率高
ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表的数据结构
对于随机访问get和set: ArrayList优于LinkedList, LinkedList要移动指针
对于新增(特质插入)add和删除remove操作:LinkedList占优,因为ArrayList要移动数据
ArrayList和Vector的异同:
开销就比ArrayList大,访问要慢~
Set接口实现 Collection接口
存储一组无序的
(不等于随机,而是根据数据的哈希值决定的),不可重复
的,集合数据
Set接口中没有额外定义新的方法,使用的都是Collection 中声明过的方法:
Set 接口的实现类:HashSet
LinkedHashSet
TreeSet
HashSet
唯一,无序。基于 HashMap 实现,底层采用 HashMap 保存数据
**它不允许集合中有重复的值, **
将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象
LinkedHashSet: 作为HashSet子类,遍历器内部数据时,可以按照添加的顺序遍历
作为HashSet类的子类,在添加数据同时,每个数据还维护两个引用, 记录了此数据前一个数据和后一个数据
优:频繁的遍历操作,效率高
ThreeSet
向TreeSet添加的数据,要求是相同的类型的
自定义类需要:指定排序规则
自然排序: 定义类继承 Comparable 重写 compareTo(obj);
**定制排序: ** TreeSet在new 时就传入一个 Comparator类对象
五种有效方法:
TreeSet
LinkedHashSet
循环遍历判断,remove()
/** 方案一 */ @org.junit.Test public void distinct() { List<String> lists = Arrays.asList(new String[]{"AA", "BB", "CC","CC"}); HashSet<String> sets = new HashSet<>(); for (String s : lists) { sets.add(s); } System.out.println("去重后的Set集合: "+sets); } @org.junit.Test /** 方案二 */ public void distinct2() { List<String> lists = Arrays.asList(new String[]{"AA", "BB", "CC","CC"}); //方式一 List<String> listNew = new ArrayList<String>(new TreeSet<String>(lists)); System.out.println("TreeSet"+listNew); //方式二 List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(lists)); System.out.println("LinkedHashSet"+listNew); } @org.junit.Test /** 方案四 */ public void distinct4() { List<String> lists = Arrays.asList(new String[]{"AA", "BB", "CC","CC"}); List<String> myList = lists.stream().distinct().collect(Collectors.toList()); System.out.println("JDK8.0新特性:"+myList); }
Iterator 中:迭代器 读:艾特瑞特~
可以通过,Collection类的 .iterator()
方法返回一个 Iterator迭代器对象
boolean hasNext(); //判断是否 存在下一个可访问的元素; Object next(); //返回要访问的下一个元素; void remove(); //移除当前的集合元素可保证从源集合中安全地删除对象; 注意(图:Iterator移除)
迭代器是通过 集合.iterator() 方法获取
表示对该集合的迭代
迭代器存在 游标的概念:
默认游标都在集合的第一个元素之前
,因此:通过Next(); 获取下一个元素!
注意: 在调用it.next()方法之前必须要调用it.hasNext()进行检测
若不调用,且下一条记录无效直接调用 i.next()
会抛出 NoSuchElementException异常。
边遍历边修改 集合 的唯一正确方式是使用 Iterator.remove() 方法,如下:
Iterator<Integer> it = list.iterator(); while(it.hasNext()){ *// do something* it.remove(); }
一种最常见的错误代码如下:
for(Integer i : list){ list.remove(i) }
ConcurrentModificationException 异常
双列集合,存储一对 key-value 数据—》
HashMap:作为Map的主要实现类;线程不安全的,效率高;允许存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历
原因: 在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
对于频繁的遍历操作,此类执行效率高于HashMap
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序 底层使用红黑树
Hashtable:作为古老的实现类
key和value都是String类型
实现类有:
HashMap
LinkedHasMap
TreeMap
Hashtable(jdk1.0)
Properties
Object .put(Object key,Object value); //以 键值对 方式存储 注意(键 唯一 值 可以重复 //相同的键 后面的替代前面的 键值对 元素) Object .get(key); //根据键 返回对应的值对象不存在对应键 返回 null; Object .remove(key); //根据键 删除指定 键值对 对象 int .size(); //返回元素个数 boolean .containsKey( object key ); //判断 是否存在 指定键值对 对象; boolean .isEmpty(); //判断 是否存在 键值对 对象 void .clear(); //清空该集合所有 元素; Set .keySet(); //返回 键的集合 Set类型集合; set是Collection 的实现类, 通过 .iterator(); 方法返回一个Iterator对象,进行遍历! Collection .values(); //返回 值的集合 collection 类型集合;
@Test public void mapTest(){ Map map = new HashMap(); //只能存储 K 唯一, 值可以重复的元素,重复的key 会替代之前的数据! map.put(1, "AA"); map.put(1, "AA"); map.put(1, "BB"); map.put(2, "AA"); map.put(2, "BB"); map.put(3, "CC"); System.out.println("遍历所有的key-value"); System.out.println("方式一: entrySet()"); Set entrySet = map.entrySet(); //获取所有的: entrySet()方法返回一个实现Map.Entry接口的对象集合 Iterator iterator1 = entrySet.iterator(); //set 属于 collection.iterator(); 获取迭代器! while (iterator1.hasNext()){ Object obj = iterator1.next(); Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey() + "---->" + entry.getValue()); } System.out.println(); 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); } System.out.println(); System.out.println("方式三: 通过ForEach循环进行遍历: 此数省略~"); System.out.println(); System.out.println("方式四: 通过Java8 Lambda表达式遍历:"); map.forEach((k, v) -> System.out.println("key: " + k + " value:" + v)); System.out.println(); System.out.println("根据Key 寻找value="+map.get(1)); System.out.println("remove(Object key) 根据key移除元素~"); System.out.println(); System.out.println("boolean containsKey(Object key): 是否包含指定的key"+map.containsKey(1)); System.out.println(); System.out.println("map.clear() 清空map,与map = null操作不同"); System.out.println("map.isEmpty() 判断map是否为空"); }
Map的entrySet()方法返回一个实现Map.Entry接口的对象集合
通过这个集合的迭代器
获得每一个条数据的键或值Map.Entry中的常用方法
集合源码解析
在 JDK 1.7 中 HashMap 是以数组加链表的形式组成的
JDK 1.8 之后新增了红黑树的组成结构,当链表大于 8
并且容量大于 64
时,链表结构会转换成红黑树结构
HashMap 基于 Hash 算法实现的:
当我们往Hashmap 中 put 元素时
首先计算 key
的hash
值,这里调用了 hash
方法
hash
方法实际是让key.hashCode()
与key.hashCode()>>>16
进行, 异或操作 目的是减少碰撞
>>> 无符号右移 3>>>1: 3无符号右移一位等于 3/2=1 (三 除 二的一次幂) 二进制码最高位无论是 0 或1 都0表示,结果为一个正数; & 与运算符 二进制码 0: true 1:false 将两个二级制码逐个位 码进行比较,返回成一个新的二级制码; 就是它的结果; ^ 异运算符 二进制码 0: true 1:false 将两个二级制码逐个位 码进行比较,返回成一个新的二级制码; 就是它的结果; | 或运算符 二进制码 0: true 1:false 将两个二级制码逐个位 码进行比较,返回成一个新的二级制码; 就是它的结果; 计算机的每个对象最终都会转义成二进制: 0101010101001 而: 与 异 或 就是针对这些二进制操作的运算符 & 与运算符 a &= b; 等同于 a = a & b; 有0为0,否则为1 0 & 0 = 0; 1 & 1 = 1; 1 & 0 = 0; | 或运算符 有1为1,否则为0 0 | 0 = 0; 1 | 1 = 1; 1 | 0 = 1; ^ 异运算符 相同为0,不同为1 0 ^ 0 = 0; 1 ^ 1 = 0; 1 ^ 0 = 1; 异 或 与 就是将两边的对象的 二进制每一个值进行比较,返回一个新的对象~
我们都知道HashMap 底层实现是: 数组+链表
JDK8: 数组+链表+红黑树
哈希值
进行取模算法, 得到在一个在数组上的坐标.直接新增
, 如果存在则: ③覆盖该元素!
相同:
都是 K-V 存储结构
HashMap和HashTable都实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆
不同:
多线程环境下可以采用concurrent并发包下的concurrentHashMap
HashTable是线程安全的key为null的键值对永远都放在以table[0]为头结点的链表中
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
内部已重写了equals()
、hashCode()
等方法,遵守了HashMap内部的规范
JDK5.0新增
是程序设计语言的一种风格或范式 , 泛型允许程序员在强类型程序,编写代码时使用一些特定的[类型]
这种参数类型可以用在:类
、接口
和方法中
,分别被称为泛型类、泛型接口、泛型方法
集合中存储的对象 什么都是 Object 类型因此 每次获取 都需要 进行类型判断转换
instanceof
泛型集合 在集合的基础上 指定存储类型
List<String>