集合的由来
java是面向对象语言,它对事物的描述都是通过对象来体现的。为了方便对这些对象进行操作,我们就必须把这些对象进行存储,存储这些对象,就不能是一个基本的变量,而应该是一个容器类型的变量。
数组和StringBuffer是容器类型的,StringBuffer的结果是一个字符串,不一定满足我们的要求,所以只能选择数组,这就是“对象数组”。而对象数组又不能适应变化的需求,因为数组的长度是固定的,为了适应变化的需求,java就提供了集合供我们使用。
数组和集合的区别
1 长度区别:
① 数组的长度固定
② 集合长度可变
2 元素的数据类型不同:
① 数组可以存储基本数据类型,也可以存储引用数据类型,存储引用数据类型就是对象数组
② 集合只能存储引用数据类型(集合的出现也就是用来存储对象的)
集合是存储多个元素的,存储多个元素也是有需求的,比如:多个元素中不能有相同的元素、多个元素按照某种规则排序一下。针对不同的需求,集合也有很多类,这些集合类的数据结构也不同。
数据结构:数据的存储方式。
虽然数据结构不同,但是它们有共性的内容(存储、获取、判断等),通过不断地向上提取,就可以得到一个集合的继承体系结构图,这个体系的老大是Collection。
文章图片
集合框架图
文章图片
Collection功能概述
迭代器的使用及注意事项
添加功能 1 boolean add(Object obj)
2 boolean addAll(Collection c)
1 添加一个元素
2 添加一个集合的元素
删除功能 1 void clear()
2 boolean remove(Object o)
3 boolean removeAll(Collection c)
1 移除所有元素
2 移除一个元素
3 移除一个集合的元素。只要有一个元素被移除就返回true
遍历功能 Iterator iterator() 迭代器,集合的专用遍历方式 。可以用while循环,也可以用for循环遍历。
iterator()返回的是Iterator,是一个接口,实际返回的是实现类的对象,此接口方法有三个:
void remove():移除迭代器返回的最后一个元素
boolean hasNext():判断是否有元素,如果仍有元素就迭代,返回true
Object next():获取元素,并移动到下一个位置
判断功能 1 boolean contains(Object obj)
2 boolean containsAll(Collection c)
3 boolean isEmpty()
1 判断集合中是否包含指定元素
注意:contains()底层依赖的是equals()。案例:如果是去除ArrayList集合中的重复自定义对象元素,如果要被contains()包含的类对象中没有重写equals(),则默认调用的是Object的equals(),Object的equals()默认比较的是地址值,所以地址值都是不同的,所以被contains()包含的类对象中要重写equals()
【爪哇|【集合】】2 判断集合中是否包含指定集合元素。只有包含所有的元素才返回true
3 判断集合是否为空
长度功能 int size() 返回元素的个数 交集功能 boolean retainAll(Collection c) 两个集合都有的元素。
返回的元素去哪了?返回的boolean是什么意思?
两个集合存在交集,调用该方法的集合存储的是交集的元素,没有其他的元素,返回true。
两个集合不存在交集,调用该方法的集合存储的是空值,没有任何元素,也返回true。
假设有两个集合A,B。A对B做交集,最终的结果保存在A中,B不变;返回值表示的是:A集合的元素是否发生过改变,A集合原来保存的元素发生过改变则返回true,A集合原来保存的元素没有发生过任何改变,则返回false。
转换功能 Object[] toArray() 把集合转为数组。可以实现对数组的遍历
学生类省略... ...
public class IteratorDemo {
public static void main(String[] args) {
Collection c = new ArrayList();
Student s = new Student("张三", 1);
Student s2 = new Student("李四", 2);
Student s3 = new Student("王五", 3);
c.add(s);
c.add(s2);
c.add(s3);
Iterator it = c.iterator();
//通过集合获取迭代器对象。迭代器是依赖于集合而存在的
while (it.hasNext()) { //先判断
Student student = (Student) it.next();
//在获取元素
System.out.println(student.getName()+", "+student.getAge());
}
}}
上面除了可以使用while去循环遍历之外,也可以使用for循环。
it是局部变量,for循环执行完毕就是垃圾,提高内存利用率。只编写for循环部分,如下:
for (Iterator it = c.iterator();
it.hasNext();
) { //没有条件控制
Student student = (Student) it.next();
System.out.println(student.getName()+", "+student.getAge());
}
注意事项:
/*
* 如果将以下的注释用链式编程去编写,如果集合的元素是奇数个,则会报错java.util.NoSuchElementException
*/Iterator it = c.iterator();
while (it.hasNext()) {
//Student student = (Student) it.next();
//System.out.println(student.getName() + ", " + student.getAge());
System.out.println(((Student) it.next()).getName() + ", " + ((Student) it.next()).getAge());
}
文章图片
从控制台的输出可以看出,张三的年龄为2,实际是1。之所以出现这样的问题是因为Iterator接口的next()是获取元素,并移动到下一个位置。这里第二次调用next(),就移动到了李四的年龄了,再移动就是王五的姓名,最后没有另一个人年龄了,就报错。如果是偶数个不会报错,但是遍历出来的数据是错乱的。所以不能多次使用next(),因为每次访问都是一个对象
迭代器的原理及源码解析
迭代器为什么不定义成一个类,而是一个接口?
假设迭代器定义成一个类,这样就可以通过创建该类的对象,调用该类的方法,来实现集合的遍历,定义成迭代器类,就是一个具体实现,具体实现,这些集合类存储元素的方式就是一样的。BUT! java中提供了这么多的集合类,而这些集合类的数据结构都是不同的,所以存储元素的方式应该是不同的,进而遍历的方式也应该是不一样的,所以就没有定义成一个迭代器类。
而无论是哪种集合,遍历集合都应该具备获取元素以及判断的功能,在获取元素之前先做判断,这样就不容易出错。而每种集合的方式又不太一样,所以这两个常用的判断跟获取元素功能给提取了出来,并不提供具体实现,这种方式就是接口。
那么,具体的实现类在哪呢?答案是:在具体的实现类中,以成员内部类的方式来体现的。源码如下:
//Iterator接口定义了如下两个方法
public interface Iterator {
boolean hasNext();
Object next();
}
public interface Iterable {
Iterator iterator();
//该方法返回的是Iterator接口
}
public interface Collection extends Iterable {
Iterator iterator();
}public interface List extends Collection {
Iterator iterator();
//到这还没有具体实现,因为List还是接口
}public class ArrayList implements List {
public Iterator iterator() { //具体子类实现了
return new Itr();
}private class Itr implements Iterator { //Itr重写了这两个方法
public boolean hasNext() {}
public Object next(){}
}
}
//遍历集合元素
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");
Iterator it = c.iterator();
//new Itr();
while(it.hasNext()) {
String s = (String)it.next();
System.out.println(s);
}
以上遍历集合元素代码解析:
通过c集合对象获取迭代器对象,在多态中通过父类对象访问成员方法的特点中,如果子类重写了父类的方法,输出的结果是子类成员方法重写父类成员方法后的内容。编译看左边,Collection有iterator(),不报错通过,运行看右边,子类ArrayList有iterator(),所以输出的是子类的iterator(),iterator()返回的是new Itr(),Itr是一个具体的实现类,所以,如果返回的是一个接口,实际返回的是实现类的对象。
List集合特点:
有序(存储顺序和取出顺序一致)、可重复
List集合特有功能概述
List 集合特有遍历功能(普通for):size()结合get()
添加功能 void add(int index, Object element) 在指定位置添加元素 删除功能 Object remove(int index) 根据索引删除元素,返回被删除的元素 修改功能 Object set(int index, Object element) 根据索引修改元素,返回被修改的元素 获取(遍历)功能 Object get(int index) 获取指定位置的元素 遍历功能 ListIterator listIterator() 列表迭代器。List集合特有的迭代器
listIterator()返回ListIterator,是一个接口,继承了Iterator。常用的方法有:
boolean hasPrevious():与Iterator接口的hasNext()一样,只不过是逆向判断是否有元素。
Object previous():与Iterator接口的next()一样,只不过是获取上一个元素。
注意:要实现逆向遍历,首先必须实现正向遍历!所以一般无意义,不使用
public class ListDemo {
public static void main(String[] args) {
List l = new ArrayList();
l.add("王麻子");
l.add("张飞");
l.add("武松");
for (int i = 0;
i < l.size();
i++) {
String s = (String) l.get(i);
System.out.println(s);
}
}
}
并发修改异常的产生原因及解决办法
public static void main(String[] args) {
List l = new ArrayList();
l.add("王麻子");
l.add("张飞");
l.add("武松");
Iterator it = l.iterator();
while (it.hasNext()) {
String s = (String) it.next();
if ("张飞".equals(s)) {
l.add("好汉");
}
}
System.out.println(l);
}
文章图片
产生原因:
以上的代码逻辑看似没有问题,如果两个字符串内容相等,则添加新的元素。但是却报错了,之所以报错是因为,迭代器是依赖集合而存在的,当判断到两个字符串内容相等就给集合添加了一个新的元素,之后在用迭代器的hasNext()判断,迭代器却不知道集合里面有新的元素,所以就报错了,这个错叫并发修改异常!
也就是说,迭代器遍历元素的时候,通过集合是不能修改元素的。
解决办法:
集合的创建、往集合添加元素省略
方法1 迭代器遍历元素,迭代器修改元素
/*
* 方法1
* 因为Iterator迭代器没有添加功能,它的子接口ListIterator有
*/
ListIterator it = l.listIterator();
while (it.hasNext()) {
String s = (String) it.next();
if ("张飞".equals(s)) {
it.add("好汉");
}
}
System.out.println(l);
方法2 集合遍历元素,集合修改元素(普通for)
//方法2
for (int i = 0;
i < l.size();
i++) {
String s = (String) l.get(i);
if ("张飞".equals(s)) {
l.add("梁山");
}
}
System.out.println(l);
方法1与方法2的区别是:方法1添加的新元素位于要比较元素的后面,方法2新添加的元素在整个元素的最后面
ArrayList集合类底层数据结构是数组,所以查询快,增删慢;线程不安全,所以效率高
ArrayList集合特有功能概述
Vector集合类底层数据结构是数组,所以查询快,增删慢;线程安全,所以效率低
转换功能 public T[] toArray(T[] a) 把集合转成数组
Vector集合特有功能概述
LinkedList底层数据结构是链表,所以查询慢,增删快;线程不安全,所以效率高
添加功能 void addElement(Object obj) 添加功能,jdk1.0出现的,被add()替代 获取功能 1 Object elementAt(int index)
2 Enumeration elements()
1 根据索引获取,类似于get() 。jdk1.0出现。被get()替代
2 获取多个元素,类似于迭代器。jdk1.0出现。被Iterator iterator()替代
elements()返回Enumeration,是一个接口,实际返回的是实现类的对象,该接口有2个方法:
boolean hasMoreElements():被hasNext()替代
Object nextElement():被next()替代
LinkedList集合特有功能概述
Arrays工具类功能概述
添加功能 1 void addFirst(Object obj)
2 void addLast(Object obj)
1 将指定元素插入到元素的开头位置处。新添加的元素总是在最前面 。
2 将指定元素插入到元素的末尾位置处
获取功能 1 Object getFirst()
2 Object getLast()
1 获取集合中的第一个元素。集合元素不变化
2 获取集合中的最后一个元素。集合元素不变化
删除功能 1 Object removeFirst()
2 Object removeLast()
1 删除集合中的第一个元素,并返回(获取)删除的元素
2 删除集合中的最后一个元素,并返回删除的元素
对asList()的使用,代码如下:
转换功能 public static List asList(T... a) 将数组转换为集合。这里需要注意的是,只能针对对象数组进行转换,对基本数据类型的数组转换不了。 排序功能 public static void sort(Object[] a) 对指定对象数组进行排序
// 例
public class ArraysDemo {public static void main(String[] args) {
String[] arr = {"hello", "world", "java"};
List list = Arrays.asList(arr);
//list.add("javaEE");
//是错误的,虽然转成了集合,但本质还是数组,不能改变集合的大小,会报错:UnsupportedOperationException
for (String s : list) { //遍历集合。除了可以使用迭代器,还可以使用foreach
System.out.println(s);
}
}}
//因为方法是可变参数,所以可以省略定义数组的步骤,直接这样编写:
List list = Arrays.asList("hello", "world", "java");
注意事项:
此时虽然将一个数组转换为了一个集合,但是不能改变集合的大小,其本质还是一个数组。因为参数是以数组的形式保存下来的,数组的长度不可变。
如果给集合添加、删除元素则报错:UnsupportedOperationException
文章图片
Set集合特点
无序(存储顺序和取出顺序不一致)、唯一
HashSet集合类底层数据结构是哈希表;线程不安全,所以效率高。哈希表结构底层依赖HashCode()和equals()。哈希表:是一个元素为链表的数组,综合了数组和链表的优点。
HashSet保证元素唯一的源码解析
为什么List集合是可重复的,而Set集合是唯一的呢?
保证Set集合唯一的是它的add(),源码如下:
interface Collection{
...
}
interface set extends Collection{
...
}
class HashSet implements Set{
private static final Object PRESENT = new Object();
private transient HashMap map;
public HashSet{
map = new HashMap<>();
//可以看出,HashSet底层是HashMap
}public boolean add(E e){
return map.put(e, PRESENT) == null;
//调用了HashMap的put()
}
}class HashMap implements Map{
public V put(K key, V value) {
//看哈希表是否为空,如果为空,就开辟空间
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}//判断对象是否为null
if (key == null)
return putForNullKey(value);
int hash = hash(key);
//算哈希值,调用了hash()。和添加进来元素对象的hashCode()相关//在哈希表中查找hash值
int i = indexFor(hash, table.length);
for (Entry e = table[i];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = https://www.it610.com/article/e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
//走这里其实是没有添加元素
}
}modCount++;
addEntry(hash, key, value, i);
//把元素添加
return null;
}transient int hashSeed = 0;
//hash()
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
//这里调用的是添加进来元素对象的hashCode()// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h>>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
}
通过查看add()源码,可以看出这个方法底层依赖两个方法:hashCode()和equals()。也就是说由这两个方法保证元素唯一性的。
步骤:首先比较哈希值,如果相同,继续走,比较地址值或者比较equals(),如果不同,就直接添加到集合中
按照方法的步骤来说:先看hashCode()值是否相同
相同:继续比较equals()方法。返回true:说明元素重复,就不添加;返回false:说明元素不重复,就添加到集合。
不同:就直接把元素添加到集合。
如果类没有重写hashCode()和equals(),默认使用的Object()。一般来说不会相同。
而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉,只留下一个。
LinkedHashSet集合类
LinkedHashSet集合类继承自HashSet,LinkedHashSet底层数据结构由 哈希表和链表组成。
特点:有序、唯一
链表保证元素有序(存储和取出顺序一致);
哈希表保证元素的唯一性。
TreeSet集合类
TreeSet底层数据结构是二叉树。(也称红黑树,是一种自平衡的二叉树)。TreeSet底层是TreeMap
如何存进去元素的?
第一个元素存储的时候,直接作为根节点存储;从第二个元素开始,每个元素与根节点进行比较:
小:作为左节点
大:作为右节点
相等:不存储
如何取出元素?
分 前序遍历、中序遍历、后序遍历。从根节点开始,按照左中右的原则依次取出元素。
特点:排序、唯一
能够对元素按照某种规则进行排序,具体取决于使用的构造方法,有2种:
1 自然排序 -- 无参构造
真正的比较是依赖于元素的compareTo(),这个方法定义在Comparable接口里面的。所以,要想重写compareTo(),就必须实现Comparable接口,这个接口表示的就是自然排序。
2 比较器排序 -- 带参构造
因为带参构造方法需要传入一个Comparator接口,实际要传入的是此接口的实现类的对象。传入此接口实现类的对象可以外部定义一个类实现此接口,重写compare(),但如果只用一次,一般使用匿名内部类,在匿名内部类重写compare()
TreeSet集合类保证元素排序和唯一的原理:
排序:
1 自然排序(是元素具备比较性)
让元素所属的类实现自然排序接口Comparable
2 比较器排序(是集合具备比较性)
让集合的构造方法接收一个比较器接口Comparator的实现类对象
唯一:
是根据比较的返回是否是0来决定的
Map集合
特点:Map集合的最大特点,就是它可以存储键值对的元素,键和值是映射的关系,将键映射到值,一个键映射一个值。键唯一,但值可以重复。无序。
Map集合和Collection集合的区别:
Map集合存储元素是成对出现的,Map集合的键是唯一的,值是可重复的;Collection集合是单独存储元素的,Collection集合的子接口Set是唯一的,List是可重复的。
Map集合的数据结构只针对键,与值无关;Collection集合数据结构针对的是元素
Map集合功能概述
Map集合的遍历
添加功能 V put(K key,V value) 添加元素 删除功能 1 void clear()
2 V remove(Object Key)
1 移除所有键值对元素
2 根据键删除键值对元素,并把值返回
判断功能 1 boolean containsKey(Object key)
2 boolean containsValue(Object Value)
3 boolean isEmpty()
1 判断集合是否包含指定的键
2 判断集合是否包含指定的值
3 判断集合是否为空
获取(遍历)功能 1 Set > entrySet()
2 SetKeySet()
3 V get(Object key)
4 Collectionvalues()
1 返回键值对对象的集合
Map.Entry是一个接口,此接口有常用的2个方法:
K getKey():返回与此项对应的键
V getValue():返回与此项对应的值
2 获取集合中所有的键的集合
3 根据键获取值
4 获取集合中所有的值的集合
长度功能 int size() 返回集合键值对的对数
方法1:根据键找值
思路:获取集合中所有的键的集合;遍历键的集合,得到每一个键;根据键获取值。
先用KeySet(),然后用foreach循环遍历Set集合,得到每一个键,然后再用get(Object key)。
方法2:根据键值对对象找键和值
思路:获取所有键值对对象的集合;遍历键值对对象的集合,得到每一个键值对对象;根据键值对对象得到键和值
public class MapDemo {public static void main(String[] args) {
Map map = new HashMap();
map.put("鹿晗", "关晓彤");
map.put("雪儿", "范范");
map.put("黄晓明", "angelababy");
Set set = map.entrySet();
for (Entry entry : set) {
String key = entry.getKey();
String value = https://www.it610.com/article/entry.getValue();
System.out.println(key+", "+value);
}
}}
LinkedHashMap集合类
LinkedHashMap集合类继承HashMap。LinkedHashMap底层数据结构由 哈希表和链表组成。
特点:有序、唯一
链表保证元素有序(存储和取出顺序一致);
哈希表保证元素的唯一性。
TreeMap集合类
TreeMap键的数据结构是二叉树。
特点:排序、唯一
HashMap和Hashtable的区别
Hashtable是jdk1.0出现的,所以它的命名不规范,它的底层也是Map集合的哈希表结构,用法几乎是一样的,HashMap的出现就是用来代替Hashtable的,就像ArrayList集合用来代替Vector。
Hashtable:线程安全的,所以效率低。不允许null键null值;
HashMap:线程不安全,所以效率高。允许null键null值。
Collections——集合操作工具类概述
要知道的几个静态方法
排序功能 public static void sort(List list) 默认自然排序 二分查找 public static int binarySearch(List> list, T key) 最大值 public static T max(Collectin> coll) 反转 public static void reverse(List list) 随机置换 public static void shuffle(List list)
推荐阅读
- java基础|JAVA基础学习总结,day15(Map集合)
- 源码阅读|HashMap原理及常见考点
- Java笔记|集合框架Map,HashMap,TreeMap【JAVA基础】
- Java15新特性及代码示例
- 程序人生|外包干了五年,已经快寄了...
- java|java 代码封装_封装 java代码
- 架构资料|消灭 Java 代码的“坏味道”
- JavaWeb实战|IDEA+Java+JSP+Mysql+Tomcat实现Web商品信息管理系统
- JavaWeb实战|IDEA+Java+JSP+Mysql+Tomcat实现Web学生宿舍信息管理系统