一、集合
1.1 Collection的子接口:List
1.1.1 List接口的常用API
首先,List接口是Collection的子接口,会继承Collection的所有方法。
其次,List接口又扩展了一些新的方法,这些方法基本上都与元素的“下标/索引”有关系。
1、增(新增)
- add(下标,一个元素)
- addAll(下标,另一个集合)
2、删(新增)
- remove(下标)
3、改(新增)
- set(下标,新元素)
- replaceAll(UnaryOperator接口的实现类对象):需要编写匿名内部类实现UnaryOperator接口,重写apply抽象方法
- sort(null):用Comparable接口的compareTo方法比较大小
- sort(Comparator接口的实现类对象):用定制比较器Comparator的compare方法比较大小
public void test(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
/*
replaceAll方法的形参是UnaryOperator<T>类型的接口,
它又继承了Function<T, R>接口,它的抽象方法 R apply(T t);
在UnaryOperator<T>接口中的 抽象方法 T apply(T t)
现在是在集合的replaceAll方法这个上下文中,那么它是要完成把旧元素替换为新元素。
参数代表旧元素,返回值代表新元素,类型相同,可能值不同了
需求:把上述单词的首字母改为大写
*/
UnaryOperator<String> u = new UnaryOperator<String>() {
@Override
public String apply(String s) {//s代表list集合的旧元素,apply方法的返回值代表list集合的新元素
//把s中的单词首字母改为大写
//思路:截取首字母,把首字母改为大写,再与剩下的字母拼接起来
/*String first = s.substring(0,1);
first = first.toUpperCase();
String after = s.substring(1);
return first + after;*/
return s.substring(0,1).toUpperCase().concat(s.substring(1));
}
};
list.replaceAll(u);
System.out.println(list);//[Hello, World, Java]
}
4、查(新增)
- E get(下标):返回[下标]对应的元素
- List subList(起始下标,终止下标):返回子集
- int indexOf(元素):返回元素首次出现的下标
- int lastIndexOf(元素):返回元素最后一次的下标
1.1.2 List接口的基础/常用实现类
1、ArrayList和Vector
- ArrayList:俗称动态数组,因为它底层是用数组结构来存储一组元素。这个内部数组会自动扩容。
- Vector:旧版的动态数组。
问:ArrayList与Vector的区别?
相同点:动态数组
不同点:
(1)数组的初始化长度:ArrayList从JDK7开始,初始化为 长度为0的空数组,Vector初始化为长度为10的数组。
- ArrayList在第1次添加元素时,会创建长度为10的数组。
(2)扩容机制/规则:ArrayList是1.5倍扩容,Vector是2倍扩容
- 1.5倍是谨慎的扩容,空间的利用率高,浪费率低。缺点:扩容频率增加。
- 2倍是大胆扩容,空间的利用率低,浪费高。优点:扩容频率低。
如果对元素的个数有大致的预判的话,那么可以直接使用**ArrayList**(int initialCapacity) 和 Vector(int initialCapacity)直接给定初始化容量
(3)Vector是古老的动态数组,线程是安全的。 ArrayList比Vector新,线程不安全的。
联想:StringBuffer(旧,线程安全的)和StringBuilder(新,线程不安全的)
2、LinkedList
LinkedList:双向链表
问:动态数组与双向链表有什么区别?
(1)数据结构不同:动态数组的物理结构是数组,双向链表的物理是链表
(2)动态数组的元素是连续存储,当我们创建ArrayList或Vector时,需要在堆中申请一整块连续的内存空间,来存储它的元素。
双向链表,不要求元素是连续存储的,可以见缝插针的存元素,前后元素只要知道对方的地址即可。双向链表不仅仅需要存储元素,还得存储前后元素的地址,这就增加了负担。
补充:早期的时候,GC的算法效率或设计也好没有现在优秀,所以当数组变大以后要找一整块连续存储空间,压力比较大。
现在JVM的GC算法已经很优秀,而且内存的容量比之前大,所以,现在ArrayList的使用频率远远高于LinkedList。
(3)动态数组涉及到扩容。非末尾位置插入和删除元素需要移动元素。双向链表不需要扩容,不需要移动元素,但是每一个元素需要“结点”。
早期的时候,JVM内存拷贝技术一般,所以数组显得非常慢。而现在JVM内存拷贝技术很优秀了,数组的扩容和移动元素的拷贝效率非常高,而链表结点对象的创建和维护的时空消耗反而凸显出来了,使得现在LinkedList看起来更慢。
(4)根据下标的查询,动态数组的效率极高,时间复杂度是O(1),即可以直接根据数组首地址+下标,算出来元素的存储位置。
根据下标的查询,双向链表的查询只能从头或从尾遍历,时间复杂度是O(n)。
(5)不是根据下标查询,都得从头遍历,效率是一样的。
3、Stack
Stack是栈。它是Vector的子类。但是它在Vector的基础上,增加了几个特殊的方法,体现栈的数据结构特点,即先进后出(FILO,First In Last Out)或后进先出(LIFO,Last In First Out)。
stack 新增的方法:
- push():压入栈
- pop():弹出栈
- peek():查看栈顶元素,不拿走
- search(元素):查看这个元素从栈顶开始数是第几个元素,这里不是下标。
问:Stack和LinkedList都有push,pop,peek等方法,它们有什么区别?
- Stack成为顺序栈,底层是数组结构。
- LinkedList成为链式栈,底层是双向链表。
1.2 Collection的子接口:Queue和Deque
Queue称为队列,队列的特点是先进先出(FIFO,First In First Out)。它的实现类也有很多,基础阶段说一个LinkedList。
Queue有一个子接口Deque,它是双端队列(double ended queue)。基础阶段说一个LinkedList。
Queue接口的方法大致如下:添加往队尾添加,出来从队头出来
左边的方法如果添加,删除,查询失败,会抛出异常。右边的方法如果添加,删除,查询失败,会返回特殊值。
Deque接口的方法大致如下:可以从队头、队尾添加,也可以从队头、队尾出来
LinkedList 类:
- LinkedList 实现了 List、Deque 和 Queue 接口。
- 因此,LinkedList 可以用作列表(List)、双端队列(Deque)和队列(Queue)
1.3 Collection集合的关系图
1.4 Map集合
Map是双列集合,它用于存储键值对(key,value),键值对又被称为映射关系。
1.4.1 Map接口的API
所有Map集合以Map接口为根接口。Map的实现类有很多,其中使用频率最高的是HashMap。
1、增
- put(key ,value ):添加一对键值对
- putAll(另一个Map):将另一个Map中的键值对添加到当前Map中
2、删
- remove(key):只要key对应就删除一对键值对
- remove(key, value):要求key,value都对应再删除一对键值对
- clear():清空map集合
3、改
- replace(key,新value)
- replace(key,旧value,新value)
- replaceAll(BiFunction接口的实现类),需要用匿名内部类实现BiFunction接口接口,重写apply方法,新Value类型 apply(key,旧value)
4、查
V get(key):根据key查询valueV getOrDefault(key):根据key查询value,如果该key对应的value不存在,用默认值返回boolean containsKey(key):判断某个key有没有boolean containsValue(value):判断某个value有没有boolean isEmpty():判断是否为空int size():返回键值对的数量
5、遍历
Set<K> keySet():遍历所有keyCollection<V> values():遍历所有valueSet<Map.Entry<K,V> entrySet():遍历所有键值对
//方式一:遍历所有的key
//Set集合的元素不可以重复,因为key不能重复
Set<String> keys = map.keySet();//通过keySet()方法,获取map中所有的key
for (String key : keys) {
System.out.println(key);
}
for (String key : keys) {
System.out.println(key +"->" + map.get(key));
}
//方式二:遍历所有的value
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
//方式三:遍历所有的键值对
Set<Map.Entry<String, String>> entries = map.entrySet();
//最外层是Set表示,所有的(key,value)的映射关系不会重复
//内层是 Map.Entry,它是所有(key,value)键值对的公共父接口类型
//因为Entry接口是Map接口的内部接口,所以名字上写的是Map.Entry
for (Map.Entry<String, String> entry : entries) {//一个entry代表一个键值对
//System.out.println(entry);
System.out.println("key:" + entry.getKey() +"\tvalue = " +entry.getValue());
}
1.4.2 Map集合的特点
- Map的key不可重复。如果两对(key,value)添加到同一个map,key重复的话,新的value会覆盖旧的value。
- Map的value可以重复。
Map的key不可修改,value可以修改。开发中通常会用Integer,String类的对象作为key,因为它们不可变。当然,如果使用得当,可以是任意类型的对象作为key。
1.4.3 Map的常见实现类
Map的常见实现类:
HashMap<K,V>:哈希表Hashtable<K,V>:哈希表LinkedHashMap<K,V>:链式哈希表,LinkedHashMap是HashMap子类。TreeMap<K,V>:红黑树Properties:它是Hashtable的子类,它的key和value默认用String类型。而且添加键值对建议用setProperty方法,获取value建议用getProperty方法。通常用于封装 xx.properties配置文件的信息。
Properties p = new Properties();
//因为jdbc.properties文件是在src下,与咱们写的类是在一起的,所以用类加载器帮我们加载这个文件
p.load(ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"));//加载
System.out.println(p);
String user = p.getProperty("user");
System.out.println("user = " + user);
问:HashMap和Hashtable有什么区别?
- Hashtable是古老的哈希表,线程安全的。不允许key和value为null。底层数据结构是:数组+单链表。
- HashMap是比Hashtable更新的哈希表,线程不安全的。允许key和value为null。底层数据结构:Java之后 数组+单链表+红黑树。
问:HashMap与LinkedHashMap有什么区别?
- HashMap存储键值对遍历没有规律。
- LinkedHashMap存储键值对按照添加的顺序遍历。有一个双向链表来记录所有键值对的添加顺序。
问:HashMap与TreeMap有什么区别?
- HashMap存储键值对遍历没有规律。
- TreeMap存储键值对遍历按照key的大小顺序排列。依赖于Comparable 或 Comparator接口。
1.4.4 Map集合的关系图
1.4.5 Set和Map有什么关系
所有Set的内部结构都是一个Map,存储到Set中的元素都是内部Map的key,它们的value都是一个Object类型的常量对象PRESENT。
1.4.6 如果value是多个对象怎么办?
如果value是多个对象,就再用集合装起来就可以。
1.4.7 Collection、Map、Set、List、Queue有什么区别?
Collection系列的集合是单列集合,存储一组对象。Set和List、Queue都是它的子接口
Map系列的集合是双列集合,存储一组键值对。Map与Collection是独立的,并列的两个接口,没有继承关系。
Set系列的集合不能重复,List系列的集合可以重复,Set系列的集合是无序(不能通过下标进行操作),List系列的集合是有序的(可以通过下标进行操作)。Queue是体现先进先出的集合特点。
1.5 集合工具类Collections
Collections是工具类,它里面提供了很多静态方法,服务于各种集合。
Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制(线程安全)等方法:
-
public static boolean addAll(Collection<? super T> c,T... elements):将所有指定元素添加到指定 collection 中。
-
public static int binarySearch(List<? extends Comparable<? super T>> list,T key):在List集合中查找某个元素的下标,List的元素必须支持可比较大小,即支持自然排序。而且List集合也事先必须是有序的,否则结果不确定。
-
public static int binarySearch(List<? extends T> list,T key,Comparator<? super T> c):在List集合中查找某个元素的下标,List的元素使用定制比较器c的compare方法比较大小。而且List集合也事先必须是有序的,否则结果不确定。
-
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序
-
public static T max(Collection<? extends T> coll,Comparator<? super T> comp)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者
-
public static void reverse(List<?> list)反转指定列表List中元素的顺序。
-
public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌
-
public static <T extends Comparable<? super T>> void sort(List list)根据元素的自然顺序对指定 List 集合元素按升序排序
-
public static void sort(List list,Comparator<? super T> c)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
-
public static void swap(List<?> list,int i,int j)将指定 list 集合中的 i 处元素和 j 处元素进行交换
-
public static int frequency(Collection<?> c,Object o)返回指定集合中指定元素的出现次数
-
public static void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中
-
public static boolean replaceAll(List list,T oldVal,T newVal):使用新值替换 List 对象的所有旧值
-
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
-
Collections类中提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。
二、源码分析
2.1 动态数组
ArrayList和Vector是动态数组。