1、集合的相关概念
1.1 集合框架
1、Java集合类由三个接口派生而出,Collection和Map和Iterator
2、Hash
指的是哈希码的一种算法、数据结构
1.2 相关区别
1.2.1 ArrayList和LinkedList的区别?
List常用的ArrayList和LinkedList?
1、区别:
ArrayList底层使用的是数组
LinkedList底层使用的是链表
数组:优点,查询数据速度快。缺点,插入删除修改比较慢(数组在内存中是一块连续的内存,如果插入删除需要移动内存)
链表:优点,插入删除元素效率高,缺点,查询需要从头部开始找,效率低
1.2.2 HashMap和HashTable的区别?HashMap和CurrentMap的区别?
1、HashMap和HashTable都可以存储key-value的数据
2、HashMap可以把null作为key或者value的,而HashTable是不可以的
3、HashMap是线程不安全的,效率高。HashTable是线程安全的,效率低。
4、CurrentHashMap既线程安全又效率高
2、根据底层代码实现集合
2.1 ArrayList
class MyLikedList{
private Object[] date;
private int size = 0;
public MyLikedList(){
date = new Object[10];
}
public void add(Object obj){
if (size>=date.length) {
Object[] dateMo = new Object[date.length*2];
System.arraycopy(date,0,dateMo,0,size);
date = dateMo;
}
date[size++] = obj;
}
}
2.1 HashMap核心数据结构详解
底层实现
1、数组+链表+红黑树(链表与数组之间的平衡)
2、new 默认容量:16
3、红黑树的五个性质:
(1)每个节点要么是红的要么是黑的
(2)根节点是黑的
(3)每个叶节点是黑的
(4)如果一个节点是红的,那么他的两个儿子都是黑的
(5)对于任意节点而言,其到叶节点树尾端NIL指针的每条路径都包含相同数目的黑节点
3、集合框架概述
3.1集合
Java集合就像是一种容器,可以动态的把多个对象的引用放入容器中
3.2 数组在存储多个数据方面的特点:
- 一旦初始化之后,长度确定了
- 数组一旦定义好,元素的类型也就确定
- 数组中提供的方法很有限,对于添加、删除、插入操作不便
- 数组存储,有序可重复
- 数组中实际元素的个数没有现成的方法和属性
3.3 Java集合的分类
- collection:单列数据,定义了存取一组对象的方法的集合
list:元素有序、可重复的集合
Set:元素无序、不可重复的集合
- Map接口:双列数据 ----> key value
4、Collection
4.1 Collection中的API
- add()
- size()
- addAll():添加另外一个集合中的所有元素
- isEmpty():判断当前集合中是否有元素
- clear():清空集合元素
- contains()
- equals()
- containsAll()
- remove()
- retainAll():获取两个集合的交集
- hashCode():返回当前对象的哈希值
- toArray():集合转化为数组
- Array.asList(Arrays):数组转化为集合
- List item
- iterator():返回Iterator接口的实例,用于遍历集合元素
List list = Arrays.asList(new String[]{"12ew", "wqe" ,"dsad"});
注意:int类型数组不可以
要将int类型转化为Integer
4.2 集合元素的遍历
4.2.1 Iterator迭代器接口
- 概念:
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素 - 迭代器模式:
提供一种方法访问一个容器对象中的各个元素,而又不暴露该对象的内部细节。迭代器模式,就是为容器而生。 - API
(1) hasNext():判断是否还有下一个元素
(2) next() :指针下移,将下移后集合位置上的元素返回
Collection col = new ArrayList();
col.add("213");
col.add("2ewq13");
col.add("21wwwwqe3");
col.add("21rrrrewq3");
Iterator iterator = col.iterator();
System.out.println(iterator.hasNext());
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
(3)集合对象每次调用**Iterator()**方法都得到一个全新的迭代器 对象
(4)remove():可以在遍历过程中删除集合中的元素(注意:在未调用next()之前,不可以调用remove)
Collection col = new ArrayList();
col.add("213");
col.add("2ewq13");
col.add("21wwwwqe3");
col.add("21rrrrewq3");
Iterator iterator = col.iterator();
System.out.println(iterator.hasNext());
while (iterator.hasNext()) {
Object object = iterator.next();
if ("213".equals(object)) {
iterator.remove();
}
iterator = col.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
4.2.2 froeach迭代集合和数组
实现也是iterator
在其中不可以修改值
4.3 List接口
4.3.1 List接口相关概念
- 鉴于Java数组用来存储数据的局限性,我们使用List代替数组(动态数组)
- 元素有序、可重复
- 相关实现类有:ArrayList、LinkedList、Vector
4.3.2 ArrayList、LinkedList、Vector的异同
- 相同点
都实现了List接口,存储有序的、可重复的数据 - 不同点
(1)Vector:list的古老实现类,JDK 1.0 。线程安全,效率低,底层使用Object[ ]存储
(2)ArrayList:线程不安全,效率高,底层使用Object[ ]存储,查询较快
(3)LinkedList:链表实现,底层使用双向链表存储,对于频繁的插入删除操作,使用此类的效率比ArrayList高(Collections中有一个方法为:synchronizedList返回的为线程安全的List)
4.3.3 ArrayList源码分析
- JDK 1.7中:
(1)初始容量为10的Object[ ]数组
(2)list.add(123);如果此次的添加导致底层的数组容量不够,则扩容为原来的1.5倍,同时将原来的数组中数据复制到新的数组中返回
(3)建议开发中使用带参数的构造器 - JDK 1.8中:
(1)底层初始化为容量为{ },并未直接创建长度
(2)add()时,底层才创建了长度为10的数组
(3)后续添加和扩容与JDK 1.7 无异
4.3.3 LinkedList源码分析
- LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null
- list.add(112);将123封装到Node中,创建了Node对象
- 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;
}
}
4.3.4 Vector源码分析
- 默认创建长度为10的数组
- 默认扩容为原来数组的两倍
4.3.5 List中的相关方法
- void add (int index, Object ele); // 在指定位置插入ele元素
- void addAll (int index, Collection coll);
- Object get(int index);
- int indexOf(Object obj);
- int lastIndexOf(Object obj);
- Object remove(int index );
- Object set(int index, Object ele);
- List subList(int from index, int toIndex);
4.3.6 List中的遍历
- Iterator 迭代器方式
- 增强for循环
- 普通循环
4.4 Set接口
4.4.1 相关概念:
- 相关实现类:HashSet、LinkedHashSet、TreeSet
- set接口存储无序的不可重复的数据
(1)无序性:
不等于随机性,遍历时有所谓的顺序
存储的数据,在底层的数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
(2)不可重复性:
保证添加的元素按equals判断时,不能返回true。即,相同的元素只可以添加一个
4.4.2 HashSet、LinkedHashSet、TreeSet异同
- HashSet:作为Set接口的主要实现类,线程不安全的;可以存储null值
- HashSet ----> LinkedHashSet:作为HashSet子类实现的,遍历其内部数据时,可以按照添加的顺序遍历
- TreeSet:可以按照添加对象的指定属性,进行排序
4.4.3 set中添加元素的过程(以Hashset为例):
- 我们向HashSet中添加元素,调用HashCode方法计算哈希值,此哈希值经过某种算法算出Hashset在底层数组中的存放位置(索引位置),然后判断此位置上是否已经有元素
- 如果此位置上没有其他元素,则直接添加该元素 ---- 情况1
- 如果此位置上有其他元素,则比较两个元素的哈希值
(1)如果不同,则元素a添加成功 ---- 情况2
(2)如果相同,进而需要调用equals比较两个元素是否相等
返回true,则该元素添加失败
如果返回false,则该元素添加成功 ---- 情况3 - 注意:对于添加成功的情况 2 与情况3而言,元素和已经在指定索引位置上的数据以链表的方式存储
(1)JDK 1.7中:该元素放到数组中,指向原来的元素
(2)JDK 1.8中:原来的元素在数组中,指向新元素 - 向set中添加的数据,其所在的类一定要重写hashCode()和euqlas方法,重写的hashCode()和euqlas方法尽可能保持一致性
4.4.4 IDEA中的hashCode()重写:
数字31
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
- 选择系数时要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突越小”,查询起来的效率越高(减少冲突)
- 31只占用5bits,相乘造成的数据溢出的概率较小
- 31 可以 由 i*31 == (i << 5) -1 来表示,现在很多虚拟机里面都有相关的优化(提高算法效率)
- 31是一个素数,素数的作用就是如果我用一个数字来乘这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
4.4.5 HashSet的底层实现
public HashSet() {
map = new HashMap<>();
}
4.4.6 LinkedHashSet的使用
- 添加的时候无序
- 在添加数据时,每个数据还维护了两个引用,记录前一个数据和后一个数据
- 优点:对于频繁的遍历,效率要高于HashSet
4.4.6 TreeSet-自然排序
- 只能添加同一类型的数据
- 按照添加数据类型排序
- 两种排序方式
(1)自然排序(实现Comparable接口):比较两个对象是否相同的标准为conpareTo(),返回0,不再是equals()
public class TreeSetTest {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new User("li", 23));
treeSet.add(new User("lisa", 2213));
treeSet.add(new User("lisa", 233));
treeSet.add(new User("li", 213));
treeSet.add(new User("jie", 23));
System.out.println(treeSet);
}
}
class User implements Comparable{
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o){
if (o instanceof User) {
User user = (User)o;
int compare = this.name.compareTo(user.name);
if(compare !=0) {
return compare;
} else {
return Integer.compare(this.age, user.age);
}
} else {
throw new RuntimeException("exception ------ ");
}
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
(2)定制排序(Comparator):重写compare()方法
public class TreeSetTest {
public static void main(String[] args) {
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User user1 = (User) o1;
User user2 = (User) o2;
int flag = user1.name.compareTo(user2.name);
if(flag !=0) {
return flag;
} else {
return Integer.compare(user1.age, user2.age);
}
} else {
throw new RuntimeException("exception ------ ");
}
}
};
TreeSet treeSet = new TreeSet(comparator);
treeSet.add(new User("li", 23));
treeSet.add(new User("lisa", 2213));
treeSet.add(new User("lisa", 233));
treeSet.add(new User("li", 213));
treeSet.add(new User("jie", 23));
System.out.println(treeSet);
}
}
4.4.7 面试题
- 例1:
public class HashTest {
public static void main(String[] args) {
Person p1 = new Person(11, "李思");
Person p2 = new Person(12,"李大");
HashSet hashSet = new HashSet();
hashSet.add(p1);
hashSet.add(p2);
System.out.println(hashSet);
p1.name = "礼物";
hashSet.remove(p1);
System.out.println(hashSet);
hashSet.add(new Person(11, "李思"));
System.out.println(hashSet);
}
}
class Person{
int num;
String name;
public Person() {
}
public Person(int num, String name) {
this.num = num;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (num != person.num) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = num;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
}
输出结果:
[Person{num=12, name='李大'}, Person{num=11, name='李思'}]
[Person{num=12, name='李大'}, Person{num=11, name='礼物'}]
[Person{num=12, name='李大'}, Person{num=11, name='礼物'}, Person{num=11, name='李思'}]
分析:哈希值不同,返回同一个对象
5、Map
5.1 基本概念
- 双列数据,存储key-value对的数据
- HashMap :作为Map的主要实现类;线程不安全,效率高,可存储null的key和value(注:)
-----> LinkedHashMap:保证在遍历元素时,可以按照添加的顺序实现,对于频繁的遍历操作等使用 - Hashtable:古老的实现类,线程安全,效率低,不能存储null和value的key和value
-----> Properties:常用来处理配置文件,key和value都是String类型 - TreeMap:保证按照添加的key-value进行排序,实现排序遍历,此时考虑KEY的自然排序或者定制排序
- 对于key-value的理解:
(1)value可以重复,key可重复且无序
(2)使用set存储所有的key
(3)key所在的类要重写equals()和hashCode()
5.2 HashMap底层实现
-
HashMap的底层:
数组+链表(JDK 1.7)
数组+链表+红黑树(JDK 1.8) -
JDK 7
(1)HashMap hashMap = new HashMap(); // 底层创建了一个长度是16的一维数组Entry[ ] table
(2)经过hashCode()计算Entry在数组中存放的位置,如果此位置上的数据为空,直接存(超出容量大小时,扩容为两倍)
如果此位置上的数据不为空,(以链表形式存在),比较key1和已经存在的一个或多个数据的哈希值:如果key1的哈希值与已经存在的数据的哈希值都不相同,此时entry添加成功。如果key1的哈希值和已经存在的某个数据的哈希值相同,使用equals方法比较数据。
(3)当超出临界值(且要存放的位置非空)时,扩容为原来容量的二倍 -
JDK 8
(1)new HashMap()没有创建固定长度的数组
(2)底层的数组为Node[ ],非entry[ ]
(3)首次调用put()方法时,底层创建长度为16的数组
(4)当某一个索引位置上的元素以链表的形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储
DEFAULT_LOAD_FACTOR; // HashMap默认加载因子 0.75
threshold; //12 容量*填充因子 16 * 0.75
为什么是 0.15 既兼顾到数组的利用率,又尽可能的让链表的数量少一些
5.3 LinkedHashMap底层的实现原理
- 可以按照添加的顺序遍历
- LinkedHashMap中的内部类:Entry[ ] entry;
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
5.4 Map的使用:
5.4.1 Map中定义的方法:
- put(); // 添加或者修改
- putAll();
- Object remove(Object key);
- void clear();
- boolean containsKey(Object key);
- boolean containsValue(Object value);
- boolean equals(Object obj);
5.4.2 Map的遍历
- Set keySet(); 返回所有的key构成的Set集合
- Collection values();
- Set entrySet = map.entrySet(); 遍历所有的key-value
5.5 TreeMap解析
- 向TreeMap中添加key-value,要求key必须是由同一种类型的数据
- 要按照key进行排序:自然排序、定制排序
5.6 Properties解析
5.6.1 基本概念
- Properties类是Hashtable的子类,该对象用于处理属性文件
- 属性文件中的key-value都是字符串,所以Properties里的key和value都是字符串类型
- 存取数据时,使用setProperties(String key, String value)和getProperty(String key)方法
5.6.2 从配置文件读取数据
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
FileInputStream fies = new FileInputStream("jdbc.properties");
properties.load(fies); // 加载流对应的文件
System.out.println(properties.getProperty("name"));
}
6、Collections工具类
6.1 基本概念
- 是一个操作Set、List、Map等集合的工具类
- 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作。
- 提供了对于集合元素对象设置不可变、对集合对象实现同步控制等方法
6.2 基本方法:
- reverse(List);
- shuffle(List); // 对List集合元素随机排序
- sort(List); // 根据元素的自然排序对指定的List元素按升序排序
- sort(List, Comparator);
- swap(List, int, int); // 交换数据
- Object max/min(Collection);
- Object max/min(Collection, Comparator);
- int frequency(Collection, Object); //返回指定集合中的元素出现的频率
- Collections类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程鬓发访问集合时的安全问题
List list1 = Collections.synchronizedList(list);