最终冲刺_04

一、Java集合框架的JCF结构

最终冲刺_04_第1张图片

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]    - 属性文件在内存中的映射的对象

二、ArrayList特点,底层

特点

  1. 有序可重复
  2. 底层数据结构是一个“动态增长” 的数组
  3. 查询效率高,增删效率低
  4. 如果不指定初始化容量,ArrayList默认初始容量为10,之后添加的元素超过容量默认会以1.5倍扩容。
  5. 线程不安全

优点:因为数组是一个有序的序列,所以它可以通过下标直接取值——查询效率高

缺点:增删效率低

三、ArrayList扩容机制

  1. ArrayList集合底层默认的初始容量为10
  2. 当添加元素的时候,集合会进行判断:内部当前元素个数+1,去跟原数组长度相比较,如果大于原数组,触发扩容(1.5倍),小于等于,添加元素
  3. 有一个特殊的场景:存在一次性添加多个元素,比如调用的是 addAll() 方法,一次性添加16个元素,集合进行判断:16跟原数组长度进行比较,16大于10,触发扩容机制(1.5倍—>10*1.5=15),发现15<16,那么就让16是集合扩容之后的容量大小,如果添加的是14个元素,15就是容量大小
  4. 扩容之后,确定数组长度,调用copy方法,产生一个新的数组

四、ArrayList和Vector区别

  • Vector线程安全,ArrayList线程非安全
  • ArrayList扩容机制1.5倍,Vector扩容机制2倍

五、LinkedList特点,底层,如何进行查询

  1. 有序的序列
  2. 底层的数据结构双向链表,jdk6以及之前是双向循环链表
  3. 链表结构的特点:查询效率很低,每次都会从头节点开始遍历
  4. 增删效率高,只会涉及到相邻节点的移动

栈列 - 先进后出

队列 - 先进先出

六、ArrayList和LinkedList区别

  1. ArrayList实现了基于动态数组的数据结构,LinkedList基于链表(双向)的数据结构
  2. ArrayList查询效率高,LinkedList增删效率高
    • (理) 对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针
    • (解) 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据

七、List和Set的区别

两个接口都是继承自Collection,是常用来存放数据项的集合

区别:

  • List有序可重复,Set无序不可重复
  • List可以通过下标来访问,Set不能

八、HashSet特点,底层原理,add方法

特点

  1. HashSet实现了set接口,底层是HashMap
  2. 存储的是对象,数据是无序不可重复复的

add方法——添加数据

  1. 把对象添加到容器之前,会根据对象调用hashCode方法,得到一个哈希值
  2. 通过这个哈希值直接定位元素的物理位置,如果没有被占用,直接存储,如果有数据,就产生了哈希碰撞或者哈希冲突
  3. 但这个时候还不能确定是同一个对象,继续调用对象的equals方法,如果返回true,说明是同一个对象,则拒绝添加

底层数据结构

  1. 散列表
  2. 桶数组 + 链表 + 红黑树

九、HashMap特点,底层原理,扩容机制,put方法过程

特点

  1. 针对key无序不可重复
  2. 数据存储的形式为key-value
  3. 线程不安全
  4. 底层数据结构
    • JDK8之前——桶数组+链表
    • JDK8之后——桶数组+链表+红黑树

扩容机制

第一次若是桶数组为空则进行扩容,然后根据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冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。

十、HashMap和HashSet的区别

  1. HashSet的底层是HashMap
  2. HashMap实现了Map接口,HashSet实现了Set接口
  3. HashMap存储键值对,HashSet存储对象
  4. HashMap使用put方法添加元素,HashSet使用add方法添加元素
  5. HashMap使用键对象来计算hashCode值,而HashSet使用成员对象来计算hashCode值,对于两个对象来说hashCode可能相同,所以用equals方法来判断对象的相等性。
  6. HashMap比较快,因为是使用唯一的键来获取对象,HashSet较HashMap来说比较慢

十一、HashMap和HashTable的区别

  • HashMap线程非安全,HashTable线程安全,内部的方法基本都经过synchronized修饰。
  • HashMap的效率略高于HashTable
  • HashMap允许将null作为一个key或者value,而HashTable不允许。
  • HashMap的默认初始扩容大小为16,之后每次扩容容量为原来的2倍。HashTable的默认初始大小为11,之后每次扩容容量变为原来的2n+1
  • JDK8.x以后HashMap在解决哈希冲突时有了较大变化,当链表大于阈值(默认为8)时将链表转换为红黑树,HashTable没有这样的机制。
  • Hashtable 和 HashMap 采用的 hash/rehash 算法都大概一样,所以性能不会有很大的差异。
  • HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue 和 containsKey。因为 contains 方法容易让人引起误解。

十二、HashSet和TreeSet的区别

一、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 - 不可重复的,但是可以根据定制的排序规则来进行排序.

十三、Collection和Collections

  1. Collection 是一个集合接口。它提供了对集合进行基本操作的接口方法
  2. Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作

十四、比较器接口[java.util.Comparator] 可比较接口[java.lang.Comparable] - 返回-1是降序还是升序.

Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。

Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。

十五、Map两个迭代写法 - 手写

//第一种方式 - 将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;
}

你可能感兴趣的:(面试,哈希算法,java,算法)