提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、collection集合是什么?
collection是一个大小可变的容器,Collection是单列集合,一个元素只能一个值
属于单列集合
- 类继承关系
注意map并没有继承collection
一、子类
1.List集合
- 添加的元素是有序,可重复,有索引
- List是一个接口,不可以实例化,不能写成如下:
List<Integer> list = new List<Integer>();//错误
1).ArrayList
1. 概念
· 底层结构基于数组,查询快,增删慢;线程不安全,效率高
· ArrayList是能够自动增长容量的数组,添加元素会自动增加size,Array需要预设size
· ArrayList适合于进行大量的随机访问的情况下使用(因为它查找快)
· Vector 和 ArrayList 的默认容量都为 10
2. 为什么ArrayList线程不安全
首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制, 当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。从源码的角度来看,因为Vector的方法前加了synchronized 关键字,也就是同步锁的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的,
一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:
1.在 Items[Size] 的位置存放此元素;
2.增大 Size 的值。
举例:
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时线程A仅仅完成步骤1,CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 ,所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行完成剩余步骤,都增加 Size 的值。 此时ArrayListSize的值为2,但是元素实际上只有一个,这就是“线程不安全”了。
3. 为什么ArrayList效率高及其优缺点
· 效率高的原因就是因为线程不安全,缺点即使优点,避免了synchronized同步锁增加的线程开销
· ArrayList查询快,增删慢;
· Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的,因为数组的连续内存, 会有一部分或者全部数据一起进入到CPU缓存
· 但是要删除数据却是开销很大,因为这需要重排数组中的所有数据, (因为删除数据以后, 需要把后面所有的数据前移),增加同上
4. ArrayList扩容实现步骤
ArrayList底层的实现是Array, 数组扩容实现:
·新增数据空间判断
·新增数据的时候需要判断当前是否有空闲空间存储
·扩容需要申请新的连续空间
·把老的数组复制过去
·新加的内容
·回收老的数组空间
·扩容需要申请新的连续空间
5. ArrayList如何解决线程不安全?
一:使用synchronized关键字;
二:使用Collections.synchronizedList();使用方法如下:
假如你创建的代码如下:List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();
那么为了解决这个线程安全问题你可以这么使用Collections.synchronizedList(),如:
List<Map<String,Object>> data=Collections.synchronizedList(newArrayList<Map<String,Object>>());
其他的都没变,使用的方法也几乎与ArrayList一样,大家可以参考下api文档;
6. 使用数组长度分配空间性能对比
注意: 长度尽量使用2的幂作为长度, 计算机分配空间大都使用次幂去分配, 减少碎片空间
代码:
```
package javatest;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName Jtest
* @Description TODO
* @Author lingxiangxiang
* @Date 4:54 PM
* @Version 1.0
**/
public class Jtest {
public static int length = 1048576; //10的20次幂
public static List<Integer> list1 = new ArrayList<>();
public static List<Integer> list2 = new ArrayList<>(length);
public static void addList(int sign) {
long start = System.currentTimeMillis();
for (int i = 0; i < length; i++) {
if (sign == 0) {
list1.add(sign);
} else {
list2.add(sign);
}
}
long end = System.currentTimeMillis();
System.out.println(sign + " exec time is: " + (end - start));
}
public static void main(String[] args) {
addList(0);
addList(1);
}
}
```
执行结果:
```
0 exec time is: 25
1 exec time is: 17
```
说明:ArrayList在初始化的时候指定长度肯定是要比不指定长度的性能好很多, 这样不用重复的去申请空间, 复制数组, 销毁老的分配空间这些步骤
2).LinkedList
1、LinkList的概念
LinkList是一个底层结构基于的双链表
LinkedList适合在表中进行插入、删除使用性更比ArrayList更好
增删快,查询慢;线程不安全,效率高;
支持双链表,定位首尾速度也快;
链表不需要连续的空间, 大小不确定
2、LinkList的优缺点
查询慢的原因:
链表还需要在去内存中根据上下游标查找,CPU缓存比内存块太多
链表代销可变, 扩展性强, 只能顺着指针的方向查询, 速度较慢
增删快的原因:
链表增删只需要改变链表头内存地址即可,不用像ArrayList迁移数据
线程不安全的原因:同Arraylist上
3、LinkList和ArrayList
LinkList的线程不安全原因、解决线程不安全问题同ArrayList,可自行联想
3).Vector
1、概念
底层结构是数组,查询快,增删慢;
线程安全,效率低;
Vector 是 Java 早期提供的线程安全的有序集合,如果不需要线程安全,不建议使用此集合,毕竟同步是有线程开销的。
Vector 和 ArrayList 的默认容量都为 10
2、Vector和ArrayList
Vector和ArrayList一样都是数组List,很多性能都同ArrayList,可自行联想
4).Vector、ArrayList、LinkedList 使用场景有什么区别?
Vector 和 ArrayList 的内部结构是以数组形式存储的,因此非常适合随机访问,但非尾部的删除或新增性能较差,比如我们在中间插入一个元素,就需要把后续的所有元素都进行移动。
LinkedList 插入和删除元素效率比较高,但随机访问性能会比以上两个动态数组慢。
2.Set集合
添加的元素是无序(按照哈希算法排序),不重复,无索引的,增删改查的性能都很好
Set元素重复元素不能存入add方法返回false
Map的重复健将覆盖旧键,将旧值返回
线程不安全
1).HashSet
1、概念
- 添加的元素是无序(按照哈希算法排序),不重复,唯一,无索引的;
- JDK 1.8 之前 哈希表 = 链表 + 数组 + (哈希算法);
- JDK 1.8 之后 哈希表 = 链表 + 数组 + 红黑树 +(哈希算法)当链表长度超过阈值8时会转为红黑树结构;
2、HashSet 是如何保证数据不可重复的
哈希表保证了无序和唯一性,元素唯一性依赖于两个方法:hashCode()和equals()
HashSet 的底层其实就是 HashMap,只不过 HashSet 实现了 Set 接口并且把数据作为 K 值,而 V 值一直使用一个相同的虚值来保存,我们可以看到源码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;// 调用 HashMap 的 put 方法,PRESENT 是一个至始至终都相同的虚值,一个为null的obkect对象
}
由于 HashMap 的 K 值本身就不允许重复,并且在 HashMap 中如果 K/V 相同时,会用新的 V 覆盖掉旧的 V,然后返回旧的 V,那么在 HashSet 中执行这一句话始终会返回一个 false,导致插入失败,这样就保证了数据的不可重复性。
2).TreeSet
底层数据结构是红黑树,按照大小默认升序排序,不重复,无索引;
排序是让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做元素的自然排序也叫做默认排序
3).LinkedHashSet
底层数据结构是链表和哈希表。(FIFO,有序,唯一)。通过链表保证有序性, 通过哈希表保证唯一性。
4).Set/HashSet集合为什么可以去重?
- HashSet是继承hashMap的,所以根据hashCode算法(哈希算法),他会将对象集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。(这也是快速查找元素的方法)
- Set集合去重主要是调用 add 方法时,使用了 hashCode 方法和 equals 方法:如果在 Set集合 中找不到与该元素 hashCode 值相同的元素,则说明该元素是新元素,会被添加到 Set 集合中;如果两个元素的 hashCode 值相同,并且使用 equals 方法比较后,返回值为 true,那么就说明两个元素相同,新元素不会被添加到 Set 集合中;如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,则两个元素不相同,新元素会被添加到 Set 集合中。
- 应用场景:
使用 Set集合存储自定义类型元素去重 ,需要重写 HashCode 方法和 equals 方法
使用 Set集合存储User ,并且不能有重复数据
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) {
Set<User> hs = new HashSet<>();
//初始化 u1
User u1 = new User(1,"zs");
//初始化 u2
User u2 = new User(1,"zs");
//将 u1和 u2都加入到 HashSet集合中
hs.add(u1);
hs.add(u2);
//遍历输出HashSet集合
for (User h : hs) {
System.out.println(h);
}
}
}
控制台打印:
User{id=1, name='zs'}
User{id=1, name='zs'}
根据Set集合去重的原理我们需要重写HashCode 方法和 equals 方法
重写后:
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//重写 equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id &&
Objects.equals(name, user.name);
}
//重写 hashCode 方法
@Override
public int hashCode() {
return Objects.hash(id, name);
}
public static void main(String[] args) {
HashSet<User> hs = new HashSet<>();
User u1 = new User(1,"zs");
User u2 = new User(1,"zs");
hs.add(u1);
hs.add(u2);
for (User h : hs) {
System.out.println(h);
}
}
}
控制台打印:
User{id=1, name='zs'}
3.List 和 Set 有什么区别?
区别分为以下几个方面:
List 允许有多个 null 值,Set 只允许有一个 null 值;(LIst是可重复录入数据的,set继承hashMap有k值的唯一性);
List 可以保证每个元素的存储顺序,Set 无法保证元素的存储顺序(因为它会按照哈希算法排序)。
二、Collection 和 Collections 有什么区别?
Collection 和 Collections 的区别如下:
Collection 是集合类的上级接口,继承它的主要有 List 和 Set;
Collections 是针对集合类的一个工具类,它提供了一些列的静态方法实现,如 Collections.sort() 排序、Collections.reverse() 逆序等
#排序
sort(List<T> list);
#查找
int binarySearch(List<T> list ,T key);//二分查找
T max(Collection<?> coll);
#反转
reverse(List<?> list);
#随机置换
shuffle(List<?> list);
二、Map是什么
Map是映射接口,用于存放键值对<key,value>,通过key来查找value,key不能为空,唯一且不重复
Map属于双列集合
线程不安全
Set元素重复元素不能存入add方法返回false
Map的重复健将覆盖旧键,将旧值返回
一、AbstractMap
该类实现了Map中的大部分API
1、HashMap
底层是哈希表数据结构,线程是不同步的,可以存入null键,null值(说明HashMap是非线程安全的)。
如何保证唯一性:
要保证键的唯一性,需要覆盖hashCode方法,和equals方法
1.LinkedHashMap
LinkedHashMap相比于HashMap内部多维护了一个双向列表,目的就是保证输入顺序和输出顺序一致,带来的劣势也很明显,性能的消耗大大提升
2、TreeMap
1.TreeMap概念和特点
TreeMap继承与AbstractMap,同时实现了NavigableMap接口,因此,TreeMap中的内容是有序键值对
TreeMap是一个红黑树
2.TreeMap的如何实现键值对的排序
方式一:元素自身具备比较性
和TreeSet一样原理,需要让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做 元素的自然排序也叫做默认排序。
方式二:容器具备比较性
当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重 写compare方法,并将该接口的子类实例对象作为参数传递给TreeMap集合的构造方法
二、SortedMap
SortedMap是一个接口,它继承与Map接口。SortedMap中的内容与Map中的区别在于,它是有序的键值对,里面排序的方法是通过比较器(Comparator)实现的
SortedMap的排序方式有两种:自然排序和指定比较器排序
SortedMap定义的API:
//继承与Map的API不再赘写
package java.util;
public interface SortedMap<K,V> extends Map<K,V> {
Comparator<? super K> comparator(); //返回比较器对象
SortedMap<K,V> subMap(K fromKey, K toKey); //返回指定key范围内的Map
SortedMap<K,V> headMap(K toKey); //返回小于指定key的部分集合
SortedMap<K,V> tailMap(K fromKey); //返回大于等于指定key的部分集合
K firstKey(); //返回第一个元素的key
K lastKey(); //返回最后一个元素的key
}
1、NavigableMap
NavigableMap是一个接口,它继承与SortedMap接口,所以它肯定也是有序的,另外,NavigableMap还有一些导航的方法:如获取“大于或等于某个对象的键值对”等等
NavigableMap除了继承了SortedMap的特性外,还提供了如下功能:
- 提供了操作键值对的方法:lowerEntry、floorEntry、cellingEntry和higherEntry方法分别返回小于、小于等于、大于等于和大于给定键的键所关联的Map.Entry对象。
- 提供了操作键的方法:lowerKey、floorKey、cellingKey和higherKey方法分别返回小于、小于等于、大于等于和大于给定键的键。
- 获取键值对的子集。
NavigableMap继承与SortedMap,它的API:
package java.util;
public interface NavigableMap<K,V> extends SortedMap<K,V> {
Map.Entry<K,V> lowerEntry(K key);
K lowerKey(K key);
Map.Entry<K,V> floorEntry(K key);
K floorKey(K key);
Map.Entry<K,V> ceilingEntry(K key);
K ceilingKey(K key);
Map.Entry<K,V> higherEntry(K key);
K higherKey(K key);
Map.Entry<K,V> lastEntry();
Map.Entry<K,V> pollLastEntry();
NavigableMap<K,V> descendingMap();
NavigableSet<K> navigableKeySet();
NavigableSet<K> descendingKeySet();
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
K toKey, boolean toInclusive);
NavigableMap<K,V> headMap(K toKey, boolean inclusive);
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
SortedMap<K,V> subMap(K fromKey, K toKey);
SortedMap<K,V> headMap(K toKey);
SortedMap<K,V> tailMap(K fromKey);
三、Hashtabe
Hashtabe是线程安全的,它不允许空值
三、set和map如何变得线程安全
使用Guava构建ConcurrentHashSet
https://blog.csdn.net/l153097889/article/details/77891250
总结
感谢:
https://blog.csdn.net/qq_28081081/article/details/80413669
https://www.cnblogs.com/williamjie/p/9099020.html
https://www.cnblogs.com/lingshang/p/10897912.html
https://blog.csdn.net/qq_44309181/article/details/103917065