笔记根据教学视频总结视频链接
数组与集合的优缺点
数组的优点 | 数组的缺点 |
---|---|
定义简单,访问迅速 | 初始化后长度不可变 |
定义好后,类型单一 | |
提供的方法有限,对插入,删除等操作不便,效率不高 | |
没有获取数组实际元素的方法 | |
要求连续的存储空间,对于较多元素,需要开辟较大连续空间 |
集合的优点 | |
---|---|
动态保存多个对象,长度可变 | |
可保存多个类型元素 | |
提供操作对象元素的方法,add,remove,set,get等 |
Collection
这里只列出常用的集合
Collection 特点
-
collection实现子类存放元素都是object对象,会自动装箱
-
如果不用泛型,默认返回object对象
List list = new ArrayList();//由于接口无法实例化,这里使用ArrayList类实现接口 list.add(1);//自动装箱,存放的是Integer对象 list.add(new (Integer(1))) list.add("tom"); list.add(true);
Collection常用方法
-
add : 添加单个元素
-
remove:删除单个元素
-
contains:查找单个元素是否存在
-
size:获取元素个数
-
isEmpty:判断是否为空
-
clear:清空
-
addAll:添加多个元素(先将元素存入集合中,再使用该方法添加集合)
-
containsAll:查找多个元素是否存在(也是通过集合操作)
-
removeAll:删除多个元素(通过集合操作)
Collection迭代遍历
Iterator对象迭代器
Iterator对象迭代器在Iterable中,所有集合都实现了该方法;主要使用两个方法:
- hasNext:判断下一个元素是否存在
- next:取出下一个元素
在使用next前必须先调用hasNext判断下一个元素是否存在。若不调用,且下一条数据为空时,会抛出异常。
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()){
//返回元素对象,
Object obj = iterator.next();
System.out.println(obj);
}
增强for循环
for (Object obj:list){
System.out.println(obj);
}
-
底层还是Iterator迭代器
-
可以理解为简化版的迭代器
普通for循环
for (int i=0;i<list.size();i++){
Object obj = list.get(i);
System.out.println(obj);
}
List
List 也是接口,这里使用ArrayList类实现接口
特点
-
List 集合元素有序,可重复
List<Object> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(1); System.out.println(list); 输出:[1, 2, 3, 4, 1]
-
List 每个元素有对应索引,支持索引操作,索引从0开始
List<Object> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(1); System.out.println(list.get(3)); 输出:4
List 常用方法
-
add(object obj):加入一个元素
-
add(int index,Object obj):在下标为index位置插入元素obj
List<Object> list = new ArrayList<>(); list.add(0); list.add(1); list.add(2); list.add(3); list.add(1,100); System.out.println(list); 输出:[0, 100, 1, 2, 3]
-
addAll(Collection eles):添加多个元素(先将元素存入集合中,再使用该方法添加集合)
-
addAll(int index,Collection eles):在index位置插入集合eles中的元素
-
get(int index):获取index位置上的元素
-
indexOf(object obj):返回obj在集合中首次出现的位置
-
lastIndexOf(object obj):返回obj在集合中最后一次出现的位置
-
remove(int index):移除index位置上的元素,并返回该元素
-
set(int index,object obj):替换index位置尚德元素为obj,索引必须存在
-
subList(int fromIndex,int toIndex):返回子串,左闭右开[fromIndex,toIndex)
ArrayList
特点
-
可放多个null
List<Object> list = new ArrayList<>(); list.add(null); list.add(null); list.add(2); System.out.println(list); 输出:[null, null, 2]
-
底层由数组实现
-
ArrayList基本等同于Vector,但ArrayList是线程不安全,没有用synchronized修饰(执行效率高,因为不用考虑线程安全问题),
-
在多线性下不建议使用ArrayList
ArrayList扩容机制(重点)
-
ArrayList 中存放对象使用Object类型的数组
transient Object[] elementData; //transient修饰表示该属性不会被序列化
-
创建ArrayList 对象时,如果使用无参构造器,则初始化数组大小为0,第一次添加元素,数组扩容为10,以后每次扩容为之前的1.5倍
List list = new ArrayList();//初始化大小为0
-
如果直接使用指定大小的有参构造器,则初始化数组大小为指定大小,以后每次扩容为之前的1.5倍
List list = new ArrayList(5);//初始化大小为指定的5
无参构造 源码
-
初始化数组,创建elementData数组
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//数组为空,即大小为0
-
执行add
//类型装箱操作,不同类型对应不同包装类 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
-
先判断数组容量是否够用
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
-
如果容量不够,调用grow进行扩容
private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
private void grow(int minCapacity) { int oldCapacity = elementData.length; //第一次oldCapacity=0 int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍扩容 if (newCapacity - minCapacity < 0) //如果第一次扩容newCapacity=0 newCapacity = minCapacity; //扩容结果为minCapacity=10 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); //数组扩容为newCapacity大小,copyOf会保留原先数据 }
Vector
特点
-
可放多个null
-
底层也是数组
-
Vector是线程同步,即线程安全(效率不高),Vector操作方法带有 synchronized关键字
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
扩容机制
- 如果使用无参构造器,则初始化数组大小为10,以后每次扩容为之前的2倍,可在构造函数中改变增量大小
- 如果直接使用指定大小的有参构造器,则初始化数组大小为指定大小,以后每次扩容为之前的2倍,可在构造函数中改变增量大小
LinkedList
特点
- 底层实现双向链表和双端队列
- 线程不安全,没有实现线程同步
- 增删不需要数组扩容,效率高
LinkedList底层操作机制
-
LinkedList底层维护一个双向链表
-
维护两个节点,first和last,分别指向首节点和尾结点
-
每个节点(Node对象)里又维护prev、next、item三个属性
-
prev直向前一个节点,next指向后一个节点,最终实现双向链表
LinkedList方法
-
add(object obj):尾插法,插入对象
-
add(int index, object obj): 在下标为index的位置插入obj元素
List list = new LinkedList(); list.add(0); list.add(1); list.add(2); list.add(3); list.add(3,10); System.out.println(list); 输出:[0, 1, 2, 10, 3]
-
set(int index, object obj):将下标为index的位置元素修改为obj元素
List list = new LinkedList(); list.add(0); list.add(1); list.add(2); list.add(3); list.set(2,"obj"); System.out.println(list); 输出:[0, 1, obj, 3]
-
remove(int index):删除下标为index的元素
-
remove(object obj):删除对象为obj的元素,若有多个obj,则删除第一个
-
get(int index):获取下标为index的元素
-
。。。
ArrayList和LinkedList比较
底层结构 | 增删效率 | 改查效率 | |
---|---|---|---|
ArrayList | 数组 | 数组扩容,较低 | 较高 |
LinkedList | 双向链表 | 操作节点指针,较高 | 较低 |
如何选择
-
改查多,选 ArrayList
-
增删多,选LinkedList
-
一般来说,80%-90%都是查询,所以大部分选ArrayList
Set
HashSet
特点
-
无序(添加和取出顺序不一致),没有索引
-
相同的一组数据,无论添加顺序怎么变化,输出顺序固定(内部根据hash值排序)
-
不允许重复元素,所以最多包含一个null
Set set = new HashSet(); set.add(1); set.add(3); set.add("jack"); set.add("jack"); //相同元素,不能添加,在常量池中存在jack set.add(new Dog("tom")); set.add(4); set.add(new Dog("tom")); // 不同对象,地址不同,所以可以加入 set.add(null); set.add(2); set.add(new String("lucy")); set.add(new String("lucy")); //不能添加 System.out.println(set); 输出:[null, 1, 2, 3, Dog{name='tom'}, 4, Dog{name='tom'}, lucy, jack]
-
HashSet底层是HashMap实现,而HashMap底层由数组+链表+红黑树实现
public HashSet() { map = new HashMap<>(); }
Set常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
迭代方式
- 可以使用迭代器
- 增强for循环(本质也是迭代器)
Set没有索引,所以不能用普通索引for循环遍历
为何相同元素不能添加
-
HashSet底层是HashMap
-
添加一个元素时,先得到hash值,然后转成索引
-
找到存储数据表table,看这个索引位置是否已存放元素,没有,则直接加入该索引位置上
-
如果该索引位置上有元素,则调用equals比较,如果比较结果相同,则放弃加入,不同,则添加该索引位置的链表后面
-
在jdk8中,如果链表长度等于8,同时数组table的长度等于64,则会转为红黑树
添加元素源码分析
Set set = new HashSet(); set.add("tom"); set.add("jack"); set.add("jack");
-
执行 HashSet()
public HashSet() { map = new HashMap<>(); }
-
执行add()方法
public boolean add(E e) { // e = "tom" return map.put(e, PRESENT)==null; //(static) PRESENT=new Object() }
-
执行put()方法,返回值为空,代表该位置为空,可放对象。返回值不为空,表示该位置已有对象且和该对象值相等,不能放对象
public V put(K key, V value) { //key = "tom" value = PRESENT (PRESENT没实际意义,占位) return putVal(hash(key), key, value, false, true); }
-
执行hash()方法,得到key对应的hash值,不是hashcode值。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //得到hashcode值,再无符号右移16位,防止冲突 }
-
执行putVal()方法,将key的hash值
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //定义辅助变量 //如果当前tab为空,或大小为0,则第一次扩容,tab数组达到16 if ((tab = table) == null || (n = tab.length) == 0) //table是数组长度,HashMap的一个属性 n = (tab = resize()).length; //根据key,得到的hash值,去计算key应该放到table的那个索引位置,并把这个位置对象赋给p //如果p为空,表示该索引位置没存放数据,创建node,存值 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //如果p不为空,表示该索引位置有数据 else { Node<K,V> e; K k; //如果当前索引位置对应的链表的第一个元素与准备添加key的hash值相等 //并且满足当前节点对象和key是同一个对象或比较equals是否相等相等,equals由程序员重写 //则不能加入 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //再判断p是否为一颗红黑树 //如果是红黑树,则调用putTreeVal()方法添加 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //如果该位置已经是一个链表,则使用for循环判断有无重复 else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //添加后,判断链表长度是否到达8,如果到达,则该位置的链表转为红黑树操作 //但treeifyBin方法会判断table数组长度是否到达64,达到,树化,否则,table数组先扩容,对象仍放在链表后面(此时链表长度已超过8) if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //判断是否需要扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
HashSet扩容机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75=12,这里的12不是table长度,而是HashSet里的元素总个数size
- 如果元素个数size到达12时,table数组就会扩容两倍到32,新的临界值是32*0.75=24,以此类推
- 在jdk8中,如果一条链表的元素个数到达YREEIFY_THRESHOLD(默认是8),就会启动转红黑树操作,但是在转红黑树treeifyBin()方法时,会先判断table是否到达64,如果到达64,则转红黑树。没到64,先对table数组扩容,对象仍放在链表后面(此时链表长度已超过8)
LinkedHashSet
LinkedHashSet特点
- LinkedHashSet是HashSet的子类,底层是LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet元素根据hashcode值决定元素的存储位置,同时使用链表维护元素次序,使得元素插入和输出次序一致
- LinkedHashSet不允许插入重复元素
TreeSet
特点
-
底层是TreeMap
-
当使用无参构造器创建时,默认使用字典排序
TreeSet treeSet = new TreeSet(); treeSet.add("jack"); treeSet.add("tom"); treeSet.add("lucy"); treeSet.add("marry"); System.out.println(treeSet); 输出:[jack, lucy, marry, tom]
-
使用Comparator构建器,可改变排序规则
TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { //按字典从小到大排,调换o1,o2顺序可改变排序次序 return ((String)o2).compareTo((String)o1); } }); treeSet.add("jack"); treeSet.add("tom"); treeSet.add("lucy"); treeSet.add("marry"); System.out.println(treeSet); 输出:[tom, marry, lucy, jack]
-
如果按照字符串长度排序,则不可加入相同长度字符串
TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { return ((String)o2).length()-((String)o1).length(); } }); treeSet.add("jack"); treeSet.add("tom"); treeSet.add("lucy"); //相同长度,不能加 treeSet.add("marry"); System.out.println(treeSet); 输出:[marry, jack, tom]
Map
HashMap
HashMap特点(JDK8)
-
Map结构是键值对key-value
-
key和value可以是任何引用类型的数据(object),封装到node节点中
Map<Object, Object> map = new HashMap<>();
-
不能添加重复key(本质是hashcode),当重复添加相同key时,会替换key中value,所以key只能一次为null
-
可以添加重复value值,因此可以添加多个value为null
-
一对k-v是放在HashMap中Node节点中,而node实现了Entry接口
-
Entry是为了遍历方便,它提供getKey()和getValue()两个方法
-
与HashSet一样,不保证映射顺序,因为底层是以hash表的方式存储,HashMap底层 (数组+链表+红黑树)
-
HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥操作
HashMap接口常用方法
- put(object key , object value):添加键值对元素
- remove(object key):移除键值为key的元素,并返回对象
- get(object key):获取键值为key的对象
- size():获取map大小
- clear():清空map
- containsKey(object key):判断键值为key的对象是否存在
- containsValue(object value):判value值为value的对象是否存在
HashMap六大遍历方法
Map map = new HashMap();
map.put("A", "a");
map.put("B", "b");
map.put("C", "c");
map.put("D", "d");
-
先取出所有key,将key保存在set里,再通过key取出对应的value
Set keySet = map.keySet(); //(1)增强for for (Object key : keySet) { System.out.println(map.get(key)); } //(2)迭代器 Iterator iterator = keySet.iterator(); while(iterator.hasNext()){ Object next = iterator.next(); System.out.println(next); }
-
把所有value取出来,放在Collection中
Collection values = map.values(); //(1)增强for for (Object value : values) { System.out.println(value); } //(2)迭代器 Iterator iterator = values.iterator(); while(iterator.hasNext()){ Object next = iterator.next(); System.out.println(next); }
-
通过EntrySet获取k-y
Set entrySet = map.entrySet(); //(1)增强for for (Object entry : entrySet) { //将entry向下转成Map.Entry Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey()); System.out.println(m.getValue()); } //迭代器 Iterator iterator = entrySet.iterator(); while (iterator.hasNext()) { Object entry = iterator.next(); //将entry向下转成Map.Entry Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey()); System.out.println(m.getValue()); }
HashMap的扩容机制和HashSet完全一样
Hashtable
Hashtable特点
-
键和值都不能为空
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); }
-
线程安全
-
基本使用和HashMap相同
-
底层结构:数组
Hashtable底层结构
- 底层是数组,Hashtable内部类 Entry[],初始化大小为11
- 临界值 threshold=11*0.75=8
Hashtable和HashMap对比
线程安全(同步) | 效率 | 允许null键null值 | |
---|---|---|---|
Hashtable | 安全 | 较低 | 不允许 |
HashMap | 不安全 | 高 | 允许 |
Properties
基本介绍
- Properties 类继承Hashtable并实现Map接口,也是使用键值对保存数据,键值对不能为null
- 使用与Hashtable类似
- Properties 还可以用于 xxx.properties 文件中,加载数据到Properties 类对象,并进行读取与修改
- xxx.properties 通常用于配置文件
TreeMap
-
当使用无参构造器创建时,默认使用字典排序
TreeMap treeMap = new TreeMap(); treeMap.add("jack"); treeMap.add("tom"); treeMap.add("lucy"); treeMap.add("marry"); System.out.println(treeMap); 输出:[jack, lucy, marry, tom]
-
使用Comparator构建器,可改变排序规则
TreeMap treeMap = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { //按字典从小到大排,调换o1,o2顺序可改变排序次序 return ((String)o2).compareTo((String)o1); } }); treeMap.add("jack"); treeMap.add("tom"); treeMap.add("lucy"); treeMap.add("marry"); System.out.println(treeMap); 输出:[tom, marry, lucy, jack]
-
如果按照字符串长度排序,则不可加入相同长度字符串
TreeMap treeMap = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { return ((String)o2).length()-((String)o1).length(); } }); treeMap.add("jack"); treeMap.add("tom"); treeMap.add("lucy"); //相同长度,不能加 treeMap.add("marry"); System.out.println(treeMap); 输出:[marry, jack, tom]
如何选择集合
Collections工具类
Collections是一个操作Set、List和Map等集合的工具类
Collections中提供了一系列静态方法对集合元素进行排序、查询和修改等操作
排序操作(均为static方法)
-
reverse(List list):元素反转
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("king"); System.out.println(list); Collections.reverse(list); //元素反转 System.out.println(list); 输出:[tom, jack, king] [king, jack, tom]
-
sort(List list):默认按字典排序
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("king"); System.out.println(list); Collections.sort(list); System.out.println(list); 输出:[tom, jack, king] [jack, king, tom]
-
sort(List list,Comparator com):自定义排序
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("kinging"); System.out.println(list); Collections.sort(list, new Comparator() { @Override public int compare(Object o1, Object o2) { //按长度由大到小排序 return ((String) o2).length() - ((String) o1).length(); } }); System.out.println(list); 输出: [tom, jack, kinging] 排序后:[kinging, jack, tom]
-
swap(List list,int i,int j):对指定两个位置元素进行交换
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("king"); System.out.println(list); Collections.swap(list,0,2); System.out.println(list); 输出:[tom, jack, king] [king, jack, tom]
查找,替换
-
max(Collection c):按照自然排序给出最大值
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("king"); System.out.println(Collections.max(list)); 输出:tom
-
max(Collection c,Comparator com):自定义返回值
List list = new ArrayList(); list.add("tom"); list.add("jack"); list.add("kinging"); Object max = Collections.max(list, new Comparator() { @Override public int compare(Object o1, Object o2) { //自定义,返回长度最大的元素 return ((String) o1).length() - ((String) o2).length(); } }); System.out.println(max); 输出:kinging