Java集合
层次一:选择合适的集合类去实现数据的保存,调用其内部的相关方法。
层次二:不同的集合类底层的数据结构为何?如何实现数据的操作的:增删改查等。
文章目录
前言
集合与数组存储的概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
数组的存储特点
缺点:
1、数组一旦定义好了,长度就确定了,不易扩展。
2、对于添加、删除、插入等操作,非常不便,同时效率不高。
3、 有序可重复,对于无序、不可重复 不能满足
优点:
数组一旦定义好了,基本数据存储类型确定。只能操作指定的类型数据。(优点,不易出现多种类型数据,操作复杂)
如 String[] arr; int [] arr; object[] arr;
一、集合框架
主要分为Collection 和 Map
Collection 接口:单列集合,用来存储一个个对象
----- List接口:有序、可重复数据—》动态数组
----- ----- ArrayList、 LinkedList 、 Vector
----- Set接口:存储无序的、不可重复
---------- HashSet、LinkedHashSet、TreeSet
-----Map接口:双列数据,用来存储一对,<key,value>
---------- HashMap、 LinkedHashMap 、 TreeMap、Hashtable、 Properties
1、Collection 接口常用方法
public void test() {
// Coolection--> List --->ArrayList
Collection coll = new ArrayList();
coll.add("AA");
coll.add(123);//自动装箱 int --integer
coll.add(new Date());
System.out.println(coll);
Collection<Integer> coll2 = new ArrayList<Integer>();
coll2.add(12);
coll2.add(34);
// coll.add(coll2);
// System.out.println(coll);//[AA, 123, Sun May 23 20:52:24 CST 2021, [12, 34]]
coll.addAll(coll2);
System.out.println(coll); //[AA, 123, Sun May 23 20:56:49 CST 2021, 12, 34]
coll.remove(12);
// 每次都会调用equals方法
System.out.println(coll);//[AA, 123, Sun May 23 20:56:49 CST 2021, 34]
coll.add(new Person("ma", 123));
// 这里需要重写toString 才能展示
System.out.println(coll);
// 这里需要重新equals方法,才能找到,否则无法移除Person,调用了coll.size()
coll.remove(new Person("ma", 123));
System.out.println(coll);
// retainAll(Collection coll1) 返回交集
//7.hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//8.集合 --->数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
//拓展:数组 --->集合:调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
//9.iterator():返回Iterator接口的实例,用于遍历集合元素。放在IteratorTest.java中测试
}
2、Collection 遍历方法
Iterator 是设计模式的一种;
最初的iterator 指向空,先判断terator.hasNext(),再取出iterator.next()以保证并不会出现空指针。
@Test
public void test2(){
// Iterator
Collection coll = new ArrayList();
coll.add(122);
coll.add("sasa");
coll.add("2133");
coll.add(new Person("as", 123));
Iterator iterator = coll.iterator();
while( iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("###################");
// foreach
for (Object o: coll)
{
System.out.println(o);
}
}
3、Collection 子接口List
List常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环
实现List常用类(源码分析)
- ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
- LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
面试题
ArrayList、LinkedList、Vector者的异同?
同: 三个类都是实现了List接口,存储数据的特点相同:存储序的、可重复的数据
不同: 见上
4、Collection 子接口Set
Set中常用方法基本都在Collection中,没有特地定制方法。
- 无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。
HashSet
Set中添加元素过程:
当我们添加元素A时,首先通过元素A所在类hashcode()方法计算A的hash值所在位置,然后通过某种算法(如对16取余数等)计算哈希值对应Set底层数组所在位置。
1、 该位置上不存在元素,则添加成功
2、该位置上有元素B(或以链表形式存在的多个元素,则比较元素A与元素B的hash值:
如果hash值不相同,则元素a添加成功。
3、如果hash值相同,进而需要调用元素A所在类的equals()方法:
equals()返回true,元素A添加失败
equals()返回false,则元素A添加成功.
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构
HashSet/LinkedHashSet
向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
- 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
TreeSet
1.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
2.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
public void test3(){
// 自然的排序、定制排序
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return p1.getName().compareTo(p2.getName());
String
}
return 0;
}
};
TreeSet set = new TreeSet(com);
set.add(new Person("马云",32));
set.add(new Person("马话题",13));
set.add(new Person("张三",34));
set.add(new Person("以实",12));
set.add(new Person("中国",100));
// 在person类中重写的方法
// @Override
// public int compareTo(Object o) {
// if(o instanceof Person){
// Person p = (Person) o;
// return p.name.compareTo(this.name);
// }
// return 0;
// }
// }
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
5、Map
Map–双列数据,通过key-value,来进行存储。
- HashMap,作为Map的主要实现类,线程不安全,但效率高;可存储null的key-value。
LinkedHashMap,继承HashMap类:保证在遍历map元素的同时,可以依照添加的顺序实现遍历。 原因:在原本HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁遍历操作,此类执行效率高HashMap。
- TreeMap
保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序—底层使用红黑树。 - Hashtable
较老的实现类,线程安全,效率低;不能存储nulll的key和value
Properties继承Hashtable:常用来处理配置文件。key和value都是String类型
总结:HashMap底层:数组+链表(7之前) 数组+ 链表+红黑树 (8)
Map的存储结构的理解
Map的key是无序的、不可重复的,使用Set存储key–>key所在的类要重写equals、hashcode方法。
Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals()。
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所以entry
HashMap 实现原理
HashMap map= new HashMap()
jdk7
在实例化后,HashMap底层创建了长度为16的Entry[] table.
在针对有数据的map后,进行map.put(key1,value1);
首先调用key1所在类别的hashcode(),计算key1的哈希值,此哈希值经过某种算法计算以后,获得在Entry数组中存放位置,分为以下情况:
- 如果存放位置数据为空,则key1-value1添加成功
- 如果数据不为空,则意味着需要存储多个数据,链表形式,
1、计算key1的哈希值与已存在数据的哈希值进行比较,不同可添加成功;
2、如果哈希值与某个数据相同,则继续比较,通过所在类的euqals方法比较,返回false–添加成功;返回true则替换当前相同值。
扩容问题:
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
jdk8
- 首先在实例化后没有立刻创建长度为16的数组,在进行添加数据时,才开始创建。
- jdk8 底层数组Node[] ,不是Entry[].
- 首次调用put方法时,底层创建长度为16的数组,
- jdk8底层结构增加了红黑树,为数组+链表+红黑树
- 形成链表时,七上八下的结构
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75 threshold:扩容的临界值,=容量*填充因子:16 *
0.75 => 12 TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8 MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
LinkedHashMap 实现原理
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,继承了HashMap中的Node.
TreeMap
TreeMap区别在于,添加的key必须由同一个类创建的对象,因为要通过key进行排序:自然排序、定制排序。
Properties读取配置文件
Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Collections 工具类
操作集合的工具类,Map和Collection
常用方法
reverse(List):反转 List 中元素的顺序 shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序 sort(List,Comparator):根据指定的
Comparator 产生的顺序对 List 集合元素进行排序 swap(List,int, int):将指定 list 集合中的 i处元素和 j 处元素进行交换 Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 Object
max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素 Object
min(Collection) Object min(Collection,Comparator) int
frequency(Collection,Object):返回指定集合中指定元素的出现次数 void copy(List dest,List
src):将src中的内容复制到dest中 boolean replaceAll(List list,Object
oldVal,Object newVal):使用新值替换 List 对象的所旧值
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)
总结
jdk8:
ArrayList :通过Object[] elmentData数组,调用空参构造器,没有指定长度(jdk7创建直接创建10的数组)
调用add方法后,创建大小10的数组,并通过size计算当前存在元素。元素存储满了,通过1.5倍扩张,并进行复制到创建数组。
LinkedList:实现了Node first和 last,node中
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
调用了HashMap进行搭建,Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
HashMap 使用的Node<k,v>
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}