1. 集合
- 遍历集合的方法
- 普通for循环
- foreach 语句
- Iterator迭代器
- 注意:对于List集合,其还有一个专门的
ListIterator
迭代器 ListIterator
- 注意:对于List集合,其还有一个专门的
- 只要能用Iterator遍历的,都能用foreach 遍历
- 集合中存储的都是引用数据类型(对象),当把一个基本数据类型放到集合中时,会被自动装箱成对象
- 基本数据类型存储的是值
- 引用数据类型存储的是地址值
1.1 分类
- List (Collection接口下)
- 特点
- 有序(存和取的顺序一致)
- 有索引
- 可以存储重复
- 可以存储null
- 分类
- ArrayList
- LinkedList
- Vector
- Set(Collection接口下)
- 特点
- 无序(存和取的顺序不一样)
- 无索引
- 不可以存储重复
- HashSet 、LinkedHashSet可以存储null ; TreeSet 不可以存储null
- 分类
- HashSet
- LinedHashSet
- TreeSet
- Map(Map接口下)
- HashMap
- TreeMap
1.2 List集合
1.2.1 List集合的实现类
- ArrayList
- LinkedList
- Vector
1.2.1.1 ArrayList
- 建议有较多查询动作,较少删除、增加时使用
- 如果CURD都多,也用这个
基本说明
- 内部通过数组实现
- 数组的默认长度是10 ,不够的话,扩为原来的1.5 倍
- 线程不安全,效率高
- 重写了toString() 方法
- 实现了
List
接口
继承了
AbstractList
抽象类- 能进行CURD操作
实现了RandomAccess 接口
- 能对其进行随机访问(即通过索引可以访问任意一个元素)
实现了Cloneable接口
- 覆盖了clone( )函数,能被克隆
- 关于clone() 的详解:https://blog.csdn.net/qq_33314107/article/details/80271963
Serializable 接口
它自己实现了writeObject() 和 readObject() 方法
这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
可以被序列化,可以通过序列化进行传输
关于序列化与反序列化的说明:https://baijiahao.baidu.com/s?id=1636492159314232573&wfr=spider&for=pc
- 补充:
- 和Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
特点
- 允许对元素快速随机访问
- 查询快,增加、删除慢
- 原因:
- 查询快
- ArrayList 底层是通过一个Object数组实现的,而数组中元素与元素之间的内存地址是连续的,可以通过下标快速查找到某一个元素,因此查询快。
- 增、删满
- 当往集合中添加一个元素的时候,会先判断 数组长度是否够用,如果不够用,会创建一个新的数组,然后将原来数组中的元素拷贝到新数组中(新创建的数组的长度是原数组长度是1.5 倍)。而删除元素时,会把要删除的元素后面的元素全部向前移动一个位置,这样很浪费时间。
- 关于ArrayList增加机制的说明:
- 查询快
- 原因:
- 可以有重复元素
包含的方法:
- 添加元素
- add()
- 会自动装箱
- addAll()
- add()
- 删除元素
- remove()
- 删除时,不会对传入的参数进行自动装箱,因此想要根据元素内容删除整型数据时时,要手动对整型元素进行装箱
- removeAll()
- clear()
- remove()
- 修改元素
- set()
- 获取元素
- get()
- 其他的一些方法
- contains()
- isEmpty()
- size()
- retainAll()
- 取两个集合的交集
- containAll()
- 判断一个List 集合中是否包含某些元素
- iterator()
ArrayList集合的CURD
- 增
- 应用实例:
private static void addElementToList(List<String> list) { //通过 add 方法向List集合中添加元素 list.add("位置一"); list.add("位置二"); list.add(1, "位置三"); //通过addAll 方法向List集合中添加元素 String[] array = new String[]{"位置4","位置5","位置6"}; //注意,由于addAll() 接收的参数类型是List类型,所以要用Arrays.asList()将数组转换成List类型 list.addAll(Arrays.asList(array)); }
- 删
- 应用实例:
private static void DeleteList(List<String> list) { //删除list集合中的内容 //先将要删除的数据放到一个list 集合中,然后将list集合删除 List<String>list1 = new ArrayList<>(); for (String string : list) { list1.add(string); } if (list1.size()>0) { list1.removeAll(list1); } //通过普通for循环遍历 for (int i = 0; i < list.size(); i++) { list.remove(i); //List会动态变化,所以i也要动态变化 i--; } //用迭代器自带的remove方法 Iterator<String>iterator = list.iterator(); while (iterator.hasNext()) { String string = (String)iterator.next(); if (string.equals("位置二")) { iterator.remove(); } } }
- 改
- 应用实例:
private static void updateList(List<String> list) { //更改List集合中元素的内容 //通过普通for循环进行遍历 for (int i = 0; i < list.size(); i++) { if (list.get(i).equals("位置一")) { list.set(i, "修改这个地方"); } } //通过foreach 语句进行修改 for (String string : list) { if (string.equals("位置一")) { list.set(list.indexOf(string), "修改的地方"); } } //???????????????????????????? //通过Iterator 进行修改 Iterator<String>iterator = list.iterator(); while (iterator.hasNext()) { String string = (String) iterator.next(); if (string.equals("位置一")) { iterator.next(); } } }
- 查
- 应用实例:
private static void traverseList(List<String> list) { //遍历list集合 //普通for 循环遍历 System.out.println("普通for循环遍历"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } //foreach 循环遍历 System.out.println("foreach语句遍历"); for(String string:list) { System.out.println(string); } //迭代器遍历 System.out.println("迭代器遍历"); Iterator<String>iterator = list.iterator(); while (iterator.hasNext()) { String string = (String) iterator.next(); System.out.println(string); } }
1.2.1.2 LinkedList
基本说明
- 底层由链表实现
- 线程不安全,效率高
- 重写了toString() 方法
- 实现了
Queue
接口- 可以进行队列操作
- 实现了
Deque
接口- 可以作为双端队列(栈)使用
- 实现了
List
接口- 可以进行CURD
- 实现了
cloneable
接口- 可以被克隆
- 实现了
serializable
接口- 可以被序列化,能通过序列化进行传输
特点
- 增、删快;查询、修改慢
- 它自己实现了writeObject() 和 readObject() 方法
- 这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
- 在首部和尾部添加、删除元素效率高;在中间添加、删除元素效率低
- 可以随机访问元素
- 虽然可以,但效率低
包含的方法
- 添加元素
- addFirst()
- addLast()
- add()
- addAll()
- 修改元素
- remove()
- removeFirst()
- removeLast()
- 更改节点元素
- set()
- 获取元素
- get()
- getFirst()
- getLast()
- 其他方法
- indexof()
- contains()
- lastIndexof()
- clone()
- toArray()
- writeObject()
- readObject()
1.2.1.3 Vector
基本说明
基本说明
- 出现在JDK1.1中,在JDK1.2 及以后的版本并入List集合中
- 目前已经被ArrayLIst 替代了
- 底层由数组实现
- 线程安全,效率低
- 重写了toString() 方法
- 继承了AbstractList
- 实现了List接口
- 实现了RandmoAccess接口
- 提供了随机访问功能
- 实现了Cloneable接口
- 可以实现克隆功能
- 实现了Serializable接口
- 可以序列化
- 它自己实现了writeObject() 和 readObject() 方法
- 这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
特点
- 底层的数组默认长度为10,当空间不够时,会自动扩容
包含的方法
- 添加元素
- add()
- 删除元素
- removeElement()
- remove()
- 更改元素
- setElementAt()
- 查找元素
- get()
- 其他的方法
- contains()
- indexof()
- 第一次出现的位置
- lastindexof()
- 最后一次出现的位置
- clone()
ArrayList 和 Vector的区别
- Vector是线程安全的,ArrayList 是线程非安全的
- Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2
1.2.2 面试题
- 面试视频:day16
1.2.2.1 去除List 集合中的重复值
- 基本数据类型
- 代码实现
引用数据类型
- 对于引用数据类型的处理,有些特殊
- 原因
- 由于判断条件依赖的是contains() 方法,而对于contains () 方法,它依赖的是实体类的equals 方法,如果引用实体类没有重写equals() 方法,就调用Object类的equals方法,对于Object的equals方法,它比较的是地址,而对于同一个实体类实例化出来的 不同对象来说,它俩的地址肯定不同,这样的话,并不能达到去重的效果,而我想要指定实体类中的某些属性作为去重复的依据。
- 所以,想要实现去重,要做两点
- 和上面那种类型一样,创建一个新的List集合,并判断
- 重写实体类中的equals () 方法
同样道理的还有remove () 方法
- 代码实现
1.2.2.2 用LinkedList 模拟进栈出栈
1.3 Set 集合
1.3.1 Set集合的实现类
- HashSet
- LinkedHashSet
- TreeSet
1.3.1.1 HashSet
- 底层实际上是HashMap
基本说明
- 线程不安全
- 可以存储null
- 不允许存储重复
- 不保证存取一致
- 重写了toString() 方法
- 没有索引
- 继承了AbstractSet
- 实现了Set接口
- 实现了Cloneable接口
- 可以被克隆
- 实现了Serializable接口
- 可以被序列化
- 它自己实现了writeObject() 和 readObject() 方法
- 这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
特点
- 没有索引
- 无序
包含的方法
- 增
- add()
- addAll()
- 删
- remove()
- removeAll()
- 改
- 没有专门的方法
- 查
- HashSet没有专门的获取元素的方法。如果想输出元素,可以通过toArray() 将HashSet转换成 数组,再输出
HashSet 保持元素唯一的机理
- 每当我们往HashSet中存入一个元素,HashSet 都会首先调用 HashCode 方法,根据这个元素的地址,为这个元素随机生成一个值(这个值与某些因素有关,对于基本数据类型,只要数值一样,这个值就一样;对于引用数据类型,地址不同,就不同),当hashCode为两个元素生成的值一样时,就会调用该引用数据类型的equals() 方法,进行比较(要是没有equals()方法,就调用Object的equals() 方法),true就不存,false就存。
- 因此对于存引用型数据类型,为保证其不重复,常常要重写hashcode() 以及equals () 方法。
- 具体写法:
- 对于hsahCode() 方法的重写,核心就是使对于同一类元素,保证其HashCode 的值是同一个常数,具体写法可以不同。
- 对于equals () 方法的重写,就看自己吧什么作为重复的标准。(其中 this. 获取的就是当前元素的属性的值,p 就代表集合中已有的元素)
- 为什么eclipse自己重写的hashCode 方法 选定31 :
- 31 是一个质数
- 31这个数即不大也不小
- 31这个数好算。2的5次方-1,2向左移动5位-1
1.3.1.2 LinkedHashSet
基本说明
- 线程不安全
- HashSet的子类
- 方法基本上都是从HashSet那继承过来的
特点
- 保证元素的唯一性
- 可以保证存取一致
1.3.1.3 TreeSet
基本说明
- 线程不安全
- 用于对添加的元素进行排序,每增加一个对象,都会进行排序,将对象插入到二叉树指定的位置。
- 不可以存储null
- Integer和String 对象都可以进行默认的 TreeSet 排序,但自定义类的对象是不可以的 。
- 自定义类必须实现Comparable 接口,
- 并且覆写compareTo() 函数,才可以用TreeSet进行排序。
- 继承了AbstractSet
- 实现了NavigableSet接口
- 实现了Cloneable 接口
- 实现了Serializable接口
- 它自己实现了writeObject() 和 readObject() 方法
- 这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
TreeSet保证元素唯一且实现排序的原理
- TreeSet 集合存储元素的方式取决于compareTo() 方法的返回值
- 第一个元素作为根存进TreeSet中,然后,对于剩下的元素:
- 返回0 :不存
- 返回正:存在右边
- 返回负:存在左边
- 第一个元素作为根存进TreeSet中,然后,对于剩下的元素:
- 所以我们就可以通过对compareTo() 返回值的适当处理,对引用类型数据进行排序
- 自定义类实现Comparable 接口
- 重写compareTo() 方法
- 实例:
1.4 Map 集合
- HashMap
- TreeMap
- LinkedHashMap
- Properties
- HashTable
1.4.1 Map集合的实现类
1.4.1.1 HashMap
- 想要进行迭代的话
- 方法1
- 先通过keySet() 方法获取 键的集合(Set集合)
- 拿到键的Set集合之后,迭代的方法就多了
- 通过Set集合的iterator() 方法 + Map 的 get() 方法获取值
- 通过foreach + Map 的get() 方法获取值
- 方法2
- 通过Map集合的entrySet() 方法将Map集合封装成 Enty 集合,并将Entry 集合放到Set集合中,并返回这个Set集合
- Entry 集合是Map集合的一个内置集合
- 比起Map集合,Entry 还有 getKey() getValue () 方法
- Entry 集合是Map集合的一个内置集合
- 然后就可以用Set集合的变量方法进行遍历了
- 通过Map集合的entrySet() 方法将Map集合封装成 Enty 集合,并将Entry 集合放到Set集合中,并返回这个Set集合
基本说明
- 线程不安全
- 双列
- 无序
- 因为底层是hash算法
- 不能直接迭代
- 没有iterator() 方法
- 继承了AbstractMap 类
- 实现了Map接口
- 实现了Cloneable接口
- 实现了Serializable接口
- 它自己实现了writeObject() 和 readObject() 方法
- 这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
特点
- 不能直接迭代
- 没有iterator() 方法
包含的方法
- 增
- put()
- 返回集合中被覆盖的元素的值
- put()
- 删
- clear()
- 将集合清空
- remove()
- 根据键删除值,返回值
- clear()
- 查
- get()
- 通过键获取值(返回值)
- values()
- 获取值的集合(返回值是Collection集合)
- keySet()
- 获取键的集合(返回值是Set集合)
- get()
- 其他方法
- containsKey()
- containsValue()
- size()
HashMap如何保证键的唯一(以键是自定义对象为例)
- 线程不安全
- 原理和HashSet一样
- 对于自定义类,要重写hashCode() 和equals() 方法
1.4.1.2 LinkedHashMap
LinkedHashMap是HashMap的子类,使用一个双向链表来维护键值对的顺序(与添加顺序相同),维护的是key的顺序。
存储、查找仍是使用hashCode值来索引,遍历(迭代)才使用链表,就是说遍历时的顺序和添加元素的顺序相同。
因为内部维护了一个链表,所以查找、存储时HashMap性能高于LinkedHashMap,但遍历集合时LinkedHashMap性能较好。
基本说明
- 线程不安全
- 保证键的唯一
- 有序(存取一致)
1.4.1.3 TreeMap
基本说明
- 线程不安全
- 继承了AbstractMap 类
- 实现了NavigableMap 接口
- 实现了Cloneable接口
- 实现了Serializable接口
- 它自己实现了writeObject() 和 readObject() 方法
- 这样,在对齐进行序列化的时候,就调用它自己的writeObject() readObject() 方法,而不是调用 ObjectInputStream ObjectOutputStream 的 writeObject() readObject() 方法
当存入的键是自定义类型时,TreeMap 是怎么保证键唯一的
- 对于存入的键是自定义类型时,处理方法和TreeSet集合一样
- 实现Comparable方法
- 根据需要重写compareTo() 方法
1.4.2 面试题
1.4.2.1 Map.Entry<String , Integer> 和 Entry<String , Integer> 的区别
- Map.Entry 这个Entry 是 调用的是Map中的Entry
- 这时,如果调用方法的话,是父类引用指向子类对象
- Entry 这个Entry是调用的是子类HsahMap 中的Entry
- 这是就是直接调用方法
- 此外,Map.Entry 中的getKey() 、getValue() 是抽象的 ,调用的时候,用的实际上是HashMap 中是 Entry 的 getKey() 、 getValue()
1.4.2.2 统计字符串中每个字符出现的次数
1.4.2.3 HashMap与Hashtable的区别与联系
- 联系
- 底层都是哈希算法
- 都是双列集合
- 区别
- HashMap线程不安全,Hashtable线程安全
- 线程不安全就效率高,反之。
- HashMap可以存储null 键 和 null值;Hashtable不可以存储null键和null值
- HashMap线程不安全,Hashtable线程安全
1.5 Map接口与Collections接口的区别与联系
- Map是双列的,Collection的单列的
- Map的键唯一,Collection的子体系Set的元素是唯一的
- Map与Set 的底层实现是一样的。
- Set底层依赖的是Map集合
- 为什么是Set底层依赖Map,而不是Map底层依赖Set?(为什么是单列集合依赖双列集合,而不是反过来?)
- 因为有Key 有Value,那么如果只想用其中一个,只需把另一个隐藏掉就可以了,但原来只有一个,想再来一个就难。
- Set是把元素存到Key中,并展示出来,将Value中存入new Object() 对象,并隐藏掉。
- 为什么要把Set的元素放到Key中,而不是Value中?
- 因为Set底层调用的是Map,而Map的数据结构针对的是Key,所以。。。。。
- Map与Set 的底层实现是一样的。
- Map集合的数据结构值针对键有效,和值无关;Collection集合的数据结构是针对值的
1.5.1 Collections 包含的常用方法
- Collections包含的都是静态方法,所以想用的话, 直接 类名. 方法名就行
- sort()
- 对集合元素进行排序(默认是升序)
- 想对自定义对象进行排序,要在实体类中实现Comparable方法,并重写compareTo() 方法
- 对集合元素进行排序(默认是升序)
- binarySearch(list , "想查找的 元素")
- 返回值是想查找的元素在集合中的位置
- max()
- 获取最大值
- reverse()
- 反转集合中元素的位置
- shuffle()
- 随机打乱元素的位置
1.6 Properties 集合
- 也属于Map集合(双列集合)下的
基本说明
- 线程安全
- 双列集合
- Hashtable 的子类
- 没有泛型
- 主要用于存储配置文件
- 可保存在流中,或从流中加载
- 属性列表中每个键及其对应值都是一个
字符串
包含的方法
- 获取
- propertyNames()
- 获取所有键的枚举
- 返回值:Enumeration
- nextElement()
- 获取键的值
- load()
- 从指定的文件中加载内容到Properties集合中
- prop.load(new FileInputStream("config.properties")) ;
- 这样,就会自动将文件中的信息按照 key : value的方式存到Properties中去
- 注意:这个文件中存的信息的写法有一定要求:
- 可以是一下两种:
- name=张仁号
- 或
- name:张仁号
- propertyNames()
- 判断
- hasMoreElements()
- 判断集合中是否有元素了
- hasMoreElements()
- 修改
- setProperty(key , value) ;
- 将修改的内容加载到文件中去
- store()
- prop.store(new FileOutputStream("config.properties") , comment)
- 第二个参数 comment 是 对该配置文件的描述,可有可夫为null
- prop.store(new FileOutputStream("config.properties") , comment)
- store()