Collecation[I]
- List[I] - 有序可重复
- ArrayList[C] - 动态数组
- LinkedList[C] - 链表
- Vector[C]
- Set[I] - 无序不可重复
- HashSet[C]
- SortedSet[I]
- TreeSet[C]
Map[I]
- HashMap[C] - key-value的形式存储数据的,针对key是无序不可重复
- HashTable[C]
- Properteis[C] - 属性文件在内存中的映射的对象
特点
优点:因为数组是一个有序的序列,所以它可以通过下标直接取值——查询效率高
缺点:增删效率低
栈列 - 先进后出
队列 - 先进先出
两个接口都是继承自Collection,是常用来存放数据项的集合
区别:
特点
add方法——添加数据
底层数据结构
特点
扩容机制
第一次若是桶数组为空则进行扩容,然后根据hash值计算数组下标,如果该下标对应的元素是空,则将元素直接插入到数组中。如果该下标对应的元素不为空,则判断key值是否相同,如果key值相同,则覆盖value,如果key值不同则需要判断当前是什么结构,如果是红黑树,则直接插入到红黑树。如果是链表,则需要遍历链表,遍历列表时如果发现相同的key则覆盖,如果没有则插入到链表尾部,再判断链表长度是否大于8,如果大于8要将链表转换为红黑树。
他先通过哈希值计算当前key的哈希值, 根据哈希值和桶数组的长度计算出当前key的下标, 判断下标是否有元素, 没有元素, 直接插入, 如果有元素, 则①判断key和哈希值是否相同, 如果相同, 将这个节点的value进行覆盖操作, 如果不相同, ②在判断是否为红黑树结构, 是就插入到红黑树里面. ③以上不满足只能为链表结构了, 如果为链表结构, 则循环遍历, 边判断key和哈希值是否有冲突, 边计算链表的个数是否达到树化的标准, 如果没有达到, 则往链表的尾节点插入数据.
以上条件判断完, 数据放入到数组或者链表或者红黑树里面,计数器+1,然后判断+1的长度, 是否达到了负载因子(0.75)的阈值, 怎么计算阈值(比如: 初始化桶数组16个长度 * 0.75 = 12, 当+1的值大于12时, 就要告诉桶数组要扩容了), 扩容多少(他是按2^n次方扩容的, 初始化长度16, 扩一次乘以2 = 32, 所以这次扩容之后的长度为32).
以上还有一个重要的操作, 链表树化, 树化是否为直接树化为红黑树, 答案是否定的: 他先进行判断
①桶数组是否为空或者
②桶数组的长度是否小于最小的扩容个数(64)
如果满足以上条件的其中一个, 就代表桶数组要扩容, 而并不会直接把链表进行树化.
红黑树拓展:
一开始红黑树是由二叉查找树(二叉平衡树)延伸过来的, 什么是二叉查找树, 左子树节点的的值小于或等于根节点的值, 右子树节点的值大于或等于根节点的值, 所以每次查找某个数据的时候, 二分查找的算法, 从根节点开始找, 查找的数据大于根节点的往右子树找, 小于的往左子树找, 查找的最大次数为树的高度, 但是会有个问题, 当插入的数据导致树的节点大部分为右节点或者左节点的时候, 会导致查找的效率变低, 时间复杂度变为线性的, 所以为了解决这种情况, 诞生出了自平衡的二叉查找树(红黑树).
规则:
① 每一个节点要么都是红色, 要么都是黑色.
② 根节点为黑色
③ 叶子节点为黑色节点(存放的都是NIL空节点)
④ 每个红色节点有两个黑色子节点
⑤ 从给定节点到其后代子节点的每一条路径的都包含相同数量的黑色节点, 并且没有一条路径会是别的路径长度的两倍.
当有打破以上五个规则的其中一个的时候, 自平衡的红黑树会使用两种方式( 变色或者旋转 ), 从新维护这棵树. 变色的话就是将节点换成黑色或者红色, 会影响到其他节点的变色. 旋转的话, 分为左旋: 逆时针转动, 作为父节点向左移动,变为左孩纸, 右节点变为父节点.
扩容因子
比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作。
他的作用很简单,相当于是一个扩容机制的阈值。当超过了这个阈值,就会触发扩容机制。HashMap源码已经为我们默认指定了负载因子是0.75。
负载因子过大,虽然空间利用率上去了,但是时间效率降低了。
负载因子太小,虽然时间效率提升了,但是空间利用率降低了。
基本上为什么是0.75的答案也就出来了,这是时间和空间的权衡。
负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。
一、HashSet
HashSet内部的数据结构是哈希表,是线程不安全的。
HashSet当中,保证集合中元素是唯一的方法。
通过对象的hashCode和equals方法来完成对象唯一性的判断。
假如,对象的hashCode值是一样的,那么就要用equals方法进行比较。
假如,结果是true,那么就要视作相同元素,不存。
假如,结果是false,那么就视为不同元素,存储。
注意了,假如,元素要存储到HashCode当中,那么就一定要覆盖hashCode方法以及equals方法。
二、TreeSet
TreeSet能够对Set集合当中的元素进行排序,是线程不安全的。
TreeSet当中,判断元素唯一性的方法是依据比较方法的返回结果是否为0,假如是0,那么是相同的元素,不存,假如不是0,那么就是不同的元素,存储。
TreeSet对元素进行排序的方式:
1、元素自身具备比较功能,也就是自然排序,需要实现Comparable接口,并覆盖其compareTo方法。
2、元素自身不具备比较功能,那么就要实现Comparator接口,并覆盖其compare方法。
除此之外,还要注意了,LinkedHashSet是一种有序的Set集合。
也就是其元素的存入和输出的顺序是相同的。
简单了解一下
Set[I] - SortedSet[I] - TreeSet[C] - 底层是TreeMap[C] - 使map集合的key根据定制的规则来进行排序.
Set - 无序不可重复的.
TreeSet - 不可重复的,但是可以根据定制的排序规则来进行排序.
Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。
Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。
//第一种方式 - 将map集合中所有的key全部取出来放入到一个Set集合中.
//set集合 - 无序不可重复,map集合的key也是无序不可重复.
Set<Integer> sets = maps.keySet();
//遍历set集合
Iterator<Integer> iter = sets.iterator();
while(iter.hasNext()){
Integer key = iter.next();
String value = maps.get(key);
System.out.println(key+":"+value);
}
//第二种方式 - 将map集合中的每对key-value封装到了一个内置的Entry对象中
//然后将每个entry对象放入到Set集合中
Set<Map.Entry<Integer,String>> entries = maps.entrySet();
Iterator<Map.Entry<Integer,String>> iter2 = entries.iterator();
while(iter2.hasNext()){
Map.Entry<Integer,String> e = iter2.next();
Integer key = e.getKey();
String value = e.getValue();
System.out.println(key+"->"+value);
}
测试第一种
public static void main(String[] args) {
Map<Integer,String> maps = new HashMap<>();
maps.put(1,"java");
maps.put(2,"python");
maps.put(3,"php");
Set<Integer> set = map.keySet();
Iterator<Integer> iter = set.iterator();
while(iter.hasNext()){
int key = iter.next();
System.out.println(key + " " + map.get(key));
}
}
PS:
//获取maps集合中的所有的value
Collection<String> values = maps.values();
System.out.println(values);
第一种
//根据下标进行删除
public static int[] delByIndex(int[] arr,int index){
//校验
if(null == arr || arr.length==0 || index<0 || index>arr.length-1)
return arr;
//1. 定义新的数组
int[] temp = new int[arr.length-1]; //因为根据指定下标删除 删除的个数为1
//2. 定义下标计数器
int pos = 0;
for (int i = 0; i < arr.length; i++) { //遍历arr
if (i != index){
temp[pos++] = arr[i];
}
}
return temp;
}
/**
* 调用delByIndex
* {1,2,3,4}
*
* 遍历到arr i从0开始 arr[i]
* 和i后面的所有的值进行比较arr[j]
* 如果arr[i] == arr[j] - delByIndex(arr,j);
*/
public static int[] delDoubleElement2(int[] arr){
if(null == arr || arr.length==0)
return arr;
for (int i = 0; i < arr.length; i++) {
for (int j = i+1; j <arr.length ; j++) {
if (arr[i] == arr[j]){
//删除j下标对应的数据
// arr = {1,1,2,3,3,1,4,2,1};
arr = delByIndex(arr,j);
j--; //防止下标左移漏掉
}
}
}
return arr;
}
第二种
/**
* 删除指定元素!!!
*
* 数组的长度一旦确定了,就不能改变 - 删除-"假的"
* * @param arr 原数组
* * @param target 需要删除的元素
* * @return 新的数组,一定不包含target值
*/
public static int[] delByTarget(int[] arr,int target){
//确定一个新的数组的关键, - 数组的元素类型 / 数组的长度
int count = 0; //计数器
for (int i = 0; i < arr.length; i++) { //确定target的个数
if (arr[i] == target){
count++;
}
}
if (count == 0){ //判断一下target是否存在
return new int[-1]; //无所谓返回什么 直接抛出错误信息即可
}
int[] arr1 = new int[arr.length-count]; //新数组
int index = 0;
for (int i = 0; i < arr.length; i++) { //遍历原来的数组
if (arr[i] != target){
arr1[index] = arr[i];
index ++;
}
}
return arr1;
}
/**
* 去重!
* 引用和对象
*
* arr -> [1,2,1,2,3,3,4,2]
*
* temp -> [0,0,0,0,0,0,0,0]
*
* 永远将arr[0]依次放入到temp[pos++] = arr[0]
* temp -> [1,2,3,4,0,0,0,0]
*
* 立即到arr中将所有的arr[0]这个数值全部删除
* int[] trr = delByTarget(arr,arr[0]);//
* arr = trr;
* //arr -> []
*
* //arr的长度为0
*
* 核心的原则:不断改变arr的地址
*
* arr = []
* @param arr
* @return
*/
public static int[] delDoubleElement(int[] arr){
//判断数组的有效性
//java.lang.NullPointerException 空指针异常 - null.东西[/属性/方法 - 成员]
if(null == arr || arr.length==0)
return arr;
//定义一个新的数组,长度和原来的数组一样
int[] temp = new int[arr.length];
//定义一个下标计数器
int pos=0;
do{
//永远将arr[0]依次放入到temp[pos++] = arr[0]
temp[pos++] = arr[0];
// 立即到arr中将所有的arr[0]这个数值全部删除
arr = delByTarget(arr,arr[0]); // 核心,包含的知识点,本质
//循环退出
if(arr.length==0)
break;
}while(true);
temp = Arrays.copyOf(temp,pos);
return temp;
}
第三种
public static int[] delDoubleElement3(int[] arr){
if(null == arr || arr.length==0)
return arr;
boolean[] flag = new boolean[arr.length]; //全是 false
Arrays.fill(flag,true);
for (int i = 0; i < flag.length; i++) { // 【1,2,1,3,5,6,8,9】
for (int j = i+1; j <flag.length ; j++) {
if (arr[j]==arr[i]){
flag[j]=false;
}
}
}
int count = 0;
for (int i = 0; i < flag.length; i++) {
if (flag[i]){
count++;
}
}
int[] temp = new int[count];
int pos = 0; //计数器
for (int i = 0; i < arr.length; i++) {
if (flag[i]){
temp[pos++] = arr[i];
}
}
return temp;
}