Java 知识问题(持续更新中)

一、集合问题

        1、问题:ArrayList,Vector, LinkedList 区别

                1)、数据结构不同:ArrayList和Vector采用动态数组(ArrayList默认10扩容0.5,Vector默认扩容1倍),LinkedList采用链表方式

                2)、线程安全:Vector类方法带有synchronized关键字保证线程安全,性能比ArrayList差

                3)、性能比较:LinkedList 增删改快,ArrayList查询快,Vector线程安全较慢

                4)、选择:单线程使用ArrayList和LinkedList,多线程建议使用Collections工具类,vector官方已不建议使用,属于Java中的遗留容器(遗留容器还有Hashtable、Dictionary、BitSet、Stack、Properties)

        2、快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别?【这里用iterator举例,其他还有hashmap等不一一举例说明】

             引用:https://blog.csdn.net/zzy7075/article/details/105843866

             1)、fail-fast:通过抛出ConcurrentModificationException异常来触发的

             2)、fail-safe:没有抛出ConcurrentModificationException异常:

        当多个线程对Collection进行操作时,一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。

        fast-fail解决办法:通过util.concurrent集合包下的相应类去处理。new ArrayList<String>()替换成new CopyOnWriteArrayList<String>()。因为ArrayList继承AbstractList,当调用 next() 和 remove()时,都会执行 checkForComodification()

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

        延伸:ArrayList和CopyOnWriteArrayList的区别

        (1) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。
        (2) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
        (3) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!

        3、hashmap数据结构、工作原理、扩容机制

        1)、数据结构

                采用Entry数组来存储key-value对(数组默认大小为16),每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,依次来解决Hash冲突的问题,因为HashMap是按照Key的hash值来计算Entry在HashMap中存储的位置的,如果hash值相同,而key内容不相等,那么就用链表来解决这种hash冲突。

                

        Entry数组:hashmap主体组成单元,包含一个key-value键值对和next指针

private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;

        protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }

        @SuppressWarnings("unchecked")
        protected Object clone() {
            return new Entry<>(hash, key, value,
                                  (next==null ? null : (Entry<K,V>) next.clone()));
        }

        // Map.Entry Ops

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            if (value == null)
                throw new NullPointerException();

            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
               (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }

        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }

        public String toString() {
            return key.toString()+"="+value.toString();
        }
    }

        线性链表:hash冲突链表

        二叉树:红黑树

        哈希表:根据哈希函数f(key)计算实际存储位置

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

        2)、属性变量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化大小 16 

static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量

static final float DEFAULT_LOAD_FACTOR = 0.75f;//装载因子 默认0.75

static final int TREEIFY_THRESHOLD = 8;//数组容量,链表长度大于8可能转红黑树(元素小于MIN_TREEIFY_CAPACITY 优先扩容)

static final int UNTREEIFY_THRESHOLD = 6;//红黑树节点数小于6,转为链表

static final int MIN_TREEIFY_CAPACITY = 64;//数组容量大于等于64且链表长度大于8就转红黑树

       2.1、如果 链表的长度 大于 8(TREEIFY_THRESHOLD ) 会尝试调用 treeifyBin 方法

if ((e = p.next) == null) {
	p.next = newNode(hash, key, value, null);
	if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
        
		treeifyBin(tab, hash);
	break;
}

        2.2、如果表的长度小于 64(MIN_TREEIFY_CAPACITY ) 会先扩容

    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

        详细内容:JDK1.8源码中的HashMap_cainiaoblog的博客-CSDN博客

        3)、put方法

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0) //是否初始化
            n = (tab = resize()).length;	//扩容初始化:return (Node<K,V>[])new Node[newCap]
        if ((p = tab[i = (n - 1) & hash]) == null)	//Ebtry[] hash位置为null直接赋值
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

        4)、get方法,通过hash获取对应位置数据,hash冲突通过getTreeNode获取

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(key)) == null ? null : e.value;
    }

    /**
     * Implements Map.get and related methods.
     *
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & (hash = hash(key))]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
       

      final TreeNode<K,V> getTreeNode(int h, Object k) {
           return ((parent != null) ? root() : this).find(h, k, null);
       }

        5)、remove移除

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

    /**
     * Implements Map.remove and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

        hashmap面试问题引用:http://www.52xingchen.cn/detail/49

       

3、List Set Map 存取数据区别

        1)、结构

                List和Set存储单列数据,Map存储键值对数据

                List数据有序且允许重复;Set无序(除了TreeSet)且不允许重复;Map无序,键不允许重复,值允许重复

        2)、实现

               2.1、 List接口

               LinkedList:链表,内存是散列的,增删快,查找慢
               ArrayList:数组,非线程安全,增删慢,查找快;
               Vector:数组,线程安全,增删慢,查找慢;

                2.2、Set接口

                HashSet:根据元素hashcode存放,底层是散列表(Node[]数组 + 单链表 + 红黑树)

                TreeSet:利用红黑树进行排序,底层是二叉树

                LinkedHashSet:根据元素hashcode存放,内部维护了一个双向链表

                2.3、Map接口

                HashMap:根据hashcode存放数据,线程不安全,支持null值和null键

                HashTable:根据hashcode存放数据,线程安全,不支持null值和null建

                LinkedHashMap: 根据元素hashcode存放,内部维护了一个双向链表(hashMap+linkList)

                TreeMap:根据键利用红黑树进行排序,默认是键值的升序排序

        3)、取数据

                3.1、List

                        循环:for foreatch Iterator迭代器

                        获取:get(index)、poll()、peek()

                3.2、Set

                        循环:foreatch Iterator迭代器

                3.3、Map

                        循环:foreatch entrySet() keySet()

                        获取:get(key)

     

         4、Array和ArrayList区别

                4.1、空间

                        Array指定空间大小,不可扩展

                        ArrayList动态空间,0.5倍扩展,newCapacity=oldCapacity+(oldCapacity>>1)

                4.2、存储数据

                        Array存储基础类型和对象类型

                        ArrayList只存储对象类型

                4.5、方法

                        Array没有删除方法

                        ArrayList删除remove方法

        

哈罗

一、针对实际业务场景,对于几百、几十万、几个亿数据量的表,如何选择ES还是数据库
  1. 数据结构与查询需求:
    • 如果数据结构简单,主要是结构化数据,并且需要执行复杂的查询操作,包括联接、分组、排序等,那么MySQL可能是更好的选择。MySQL具有强大的查询性能和丰富的查询功能,适用于处理大量结构化数据。
    • 如果数据结构复杂,包含大量非结构化数据(如文本、日志等),并且需要全文搜索、模糊匹配等功能,那么ES是更适合的选择。ES专为全文搜索而设计,具有强大的全文搜索功能和灵活的数据类型。
  2. 数据实时性:
    • 如果需要实时搜索和分析数据,即数据需要实时更新并能够快速返回搜索结果,那么MySQL可能更适合。MySQL具有较好的实时性,可以快速返回查询结果。
    • 如果数据更新不是实时的,或者不需要立即获得最新的搜索结果,那么ES是更好的选择。ES的倒排索引结构使其在处理大量数据时具有优秀的搜索性能,同时ES还支持近实时搜索。
  3. 扩展性与可维护性:
    • 如果需要处理的数据量很大,并且需要横向扩展以支持更多的查询请求,那么ES是更好的选择。ES是分布式搜索引擎,可以方便地扩展集群规模以处理更大的数据量和并发请求。
    • 如果数据量不大,只需要单个数据库实例即可处理,并且关注的是一致性和事务性,那么MySQL可能更合适。MySQL是关系型数据库管理系统,具有较好的事务一致性和ACID属性。
  4. 分析与监控:
    • 如果需要进行复杂的数据分析,包括统计、聚合、趋势预测等,那么选择ES可能更好。ES提供了丰富的分析功能和监控工具,可以帮助你更好地理解数据和性能。
    • 如果只需要基本的统计分析功能,那么MySQL可能更适合。MySQL提供了基本的统计功能和性能监控工具。
  5. 成本与资源:
    • 如果对成本敏感,并且希望最大化资源利用效率,那么选择ES可能更好。ES是开源的分布式搜索引擎,可以节省成本并充分利用现有的计算资源。
    • 如果已经拥有MySQL许可证并且希望继续使用MySQL作为主要的数据存储和处理工具,那么选择MySQL可能更合适。MySQL是一个成熟的关系型数据库管理系统,需要购买相应的许可证。
综上所述,针对亿级数量和几百条、几十万条数据的表,选择使用ES或MySQL应该根据具体的业务场景、数据结构、查询需求、实时性、扩展性、可维护性、分析与监控以及成本与资源等因素进行权衡和决策
二、当数据表中数据量过大,应该如何优化查询速度
  1. 索引优化:为经常用于查询条件的列创建索引,可以显著提高查询速度。索引可以加快查询的执行速度,并减少数据库服务器的负载。但是需要注意的是,索引也会占用一定的存储空间,并且在插入、更新和删除操作时可能会降低性能。
  2. 分区表:将一个大表分为多个较小的分区,可以更有效地管理数据并提高查询性能。每个分区可以单独存储和检索数据,并且可以使用更高效的数据结构和算法来加速查询。
  3. 缓存查询结果:如果查询结果不经常变化,可以将查询结果缓存起来,以减少每次查询时都需要执行的计算和网络传输。缓存可以使用各种技术实现,例如内存缓存、分布式缓存等。
  4. 优化查询语句:编写高效的查询语句可以减少数据库服务器的负载并提高查询速度。例如,避免使用子查询、减少全表扫描等。
  5. 使用索引扫描:在查询语句中使用索引扫描可以加快查询速度。索引扫描会利用创建的索引来查找满足查询条件的数据行,而不是全表扫描。
  6. 调整数据库配置:根据具体的应用场景和硬件环境,调整数据库服务器的配置可以提高查询性能。例如,增加缓冲区大小、调整连接数等。
  7. 使用分布式数据库:将数据分散到多个数据库服务器上可以提高查询性能和可扩展性。分布式数据库可以通过负载均衡和分片技术来分配数据和查询请求。
三、MySQL和Redis的数据强一致性如何实现? (我说的先更新数据库再删除缓存,面试官说这不能保证强一致,要先删缓存再更新数据库)
四、MySQL中有哪些常见的索引类型? 联合索引的失效场景? Spring事务失效场景?
    4.1、常见的索引类型        
  1. 主键索引(PRIMARY KEY):主键索引是唯一且非空的索引,通常在表中自动创建。
  2. 唯一索引(UNIQUE KEY):唯一索引类似于主键索引,不同之处在于一个表可以有多个唯一索引。
  3. 普通索引(INDEX):普通索引是最基本的索引类型,它没有任何限制。
  4. 全文索引(FULLTEXT):全文索引用于全文搜索,它提供了高级的搜索功能。
    4.2、联合索引失效场景
  1. 列顺序:如果查询中引用的列顺序与联合索引的顺序不匹配,那么联合索引将不会生效。
  2. 查询条件:如果查询条件中引用了联合索引中未包含的列,那么联合索引将不会生效。
  3. 过滤条件:如果查询中使用了某些过滤条件,如函数或表达式,那么联合索引将不会生效。
  4. NULL值:如果联合索引中包含NULL值,那么查询优化器可能会选择不使用该索引。
    4.3、Spring事务失效场景包括:
  1. 事务传播级别不匹配:如果当前线程的事务传播级别与预期的事务传播级别不匹配,那么事务将不会生效。
  2. 事务隔离级别不匹配:如果当前线程的事务隔离级别与预期的事务隔离级别不匹配,那么事务将不会生效。
  3. 事务超时:如果事务的执行时间超过了设定的超时时间,那么事务将自动回滚。
  4. 事务异常:如果在事务执行过程中发生了未处理的异常,那么事务将自动回滚。
  5. 事务已提交:如果当前线程的事务已经提交,那么无法再次提交该事务。
五、Java中有哪些数据结构?HashMap的扩容机制? 为什么要把闻值设置为0.75? 为什么HashMap的初始容量要设置为16?
        5.1、 常见的数据结构包括
  1. 数组(Array):一种线性数据结构,用于存储相同类型的元素。
  2. 链表(LinkedList):一种双向链表,每个节点包含一个数据域和一个指向下一个节点的指针。
  3. 栈(Stack):一种后进先出(LIFO)的数据结构,支持插入和删除操作。
  4. 队列(Queue):一种先进先出(FIFO)的数据结构,支持插入和删除操作。
  5. 散列表(HashMap):一种基于键值对映射的数据结构,通过哈希函数将键映射到桶中。
  6. 树(Tree):一种层次结构,每个节点可以有多个子节点。
  7. 图(Graph):由节点和边组成的数据结构,表示对象及其之间的关系。
        5.2、 HashMap的扩容机制
    HashMap是一种动态数据结构,当其容量不足以容纳更多的元素时,会进行扩容。扩容是指扩大HashMap的容量,以容纳更多的键值对。
    HashMap的扩容机制是通过创建一个新的数组,其大小是原数组大小的2倍,并将原数组中的元素复制到新数组中。这个比例是0.75,也称为加载因子。设置这个比例是为了在插入元素时,能够保持较低的冲突率,从而提高HashMap的性能。如果设置过高的加载因子,会导致冲突增加,降低性能;如果设置过低的加载因子,会导致空间浪费。
        5.3、 为什么要把加载因子设置为0.75
     这是因为在实践中发现,当加载因子为0.75时,可以获得较好的性能和空间利用率。这个值是通过实验得出的结论,被认为是较优的选择
        5.4、 为什么HashMap的初始容量要设置为16
    因为Java中的int类型是32位的,而16的二进制表示是10000,恰好可以容纳4个16进制的数字。因此,将初始容量设置为16可以充分利用内存空间,避免频繁扩容带来的性能开销。同时,16也是2的4次方,方便计算扩容时的倍数关系。
    索引位置:index = h&(length-1),数组长度保持2的次幂,length-1的低位都为1,会使得获得的数组索引index更加均匀
六、 List和Set的区别? Set去重的原理是什么?
        6.1、区别            
  1. List(列表):List是一个有序集合,元素可以重复,并且可以插入或删除元素。它通常用于维护元素的特定顺序,例如需要按照特定顺序访问元素的场景。
  2. Set(集合):Set是一个无序集合,元素不能重复,并且不能插入或删除元素。它通常用于去除重复元素,例如需要对一组值进行去重操作的场景。
        6.2、Set去重原理
    通过hashcode获取对象哈希码 计算出存储位置,然后使用equeals比较值是否想等,不相等将新对象覆盖已存在对象   
    因此,为了确保Set中的元素唯一性,需要确保对象的hashCode方法和equals方法被正确地实现。如果两个对象相等,它们的hashCode方法应该返回相同的值。
    七、String有什么特点?它的不可变体现在哪里? String有长度限制吗?
         7.1、 特点
  1. 不可变性 :一旦创建了一个字符串,就不能改变它。例如,你不能改变字符串中的特定字符。不过,你可以创建一个新的字符串来代替原有的字符串。
  2. 共享 :多个String对象可以共享相同的字符数组。例如,当你执行 "hello" + "world" 时,会创建一个新的String对象,但两个对象共享同一段字符数组。
  3. 可变长 :字符串的长度是可变的,可以容纳任何长度的字符序列,包括空字符串。
  4. 线程安全 :String类是不可变的,因此它是线程安全的。一旦一个字符串被创建,其内容就不能被修改,这就意味着多个线程可以同时读取或使用同一个字符串,而不会产生任何问题。
  5. 预定义 :Java中已经预定义了一些常用的字符串操作,如获取子串、查找、替换等。
         7.2、 不可变体现在哪里
  1. 字符串的内容不可变。也就是说,一旦一个字符串被创建,其内容就不能被修改。例如,你不能改变一个字符串中的特定字符。你可以创建一个新的字符串来代替原有的字符串,但原有的字符串仍然存在。
  2. 字符串是不可变的,也意味着它是线程安全的。因为其内容不能被修改,所以不存在线程竞争条件。这就意味着多个线程可以同时读取或使用同一个字符串,而不会产生任何问题。
         7.3、长度限制
                         Java中的String长度是没有限制的,只受制于可用内存的大小。因为String是由字符数组组成的,只要你的内存足够大,就可以创建任意长度的字符串。Idea调试会报错:常量字符串过长,修改Java Compiler为Eclipse
    
    八、 包装类在哪个区间会使用缓存?为什么要用缓存机制,直接声明不可以吗?
         8.1、使用缓存区间
             当通过自动装箱机制创建包装类对象时,首先会判断数值是否在-128—-127的范围内,如果满足条件,则会从缓存(常量池)中寻找指定数值,若找到缓存,则不会新建对象,只是指向指定数值对应的包装类对象,否则,新建对象。
public static Integer valueOf(int i) {
    //其中low是最小值,high是最大值
   if (i >= IntegerCache.low && i <=IntegerCache.high)
   {
       //返回的是缓存中的对象
       return IntegerCache.cache[i + (-IntegerCache.low)];
   }
   return new Integer(i);
}
        Integer 、Byte 、Short 、Long 、Character 缓冲的默认值范围都是-128~127
        Float,Double,Boolean 三大包装类并没有缓冲机制     
8.2、为什么使用缓存机制
  1. 提高性能:对于频繁创建和销毁的对象,缓存可以避免重复创建和销毁的开销,从而提高应用程序的性能。
  2. 节省内存:对于一些资源有限的场景,如移动设备或内存受限的环境,使用缓存可以减少内存占用。
  3. 特定功能:某些包装类可能需要缓存其内部状态以提供特定的功能。例如,Integer 类缓存了 -128 到 127 的所有实例,这是因为这个范围内的整数在内存中占用空间小且频繁使用,通过缓存可以避免重复创建和销毁的开销。
九、 继承和实现的区别?为什么Java不支持多继承
继承
    继承是面向对象编程中的一个重要特性,它允许我们基于已有的类创建新类。在继承中,子类继承了父类的所有属性和方法,还可以添加自己特有的属性和方法。此外,子类还可以重写父类中的方法以实现特定的功能。
实现
    实现则是通过接口来实现的。接口定义了一组要实现的方法,而一个类可以通过实现一个接口来承诺实现这些方法。一个类可以实现多个接口。在Java中,接口和抽象类很相似,但它们有一些区别。例如,抽象类可以包含具体的方法,而接口不能。
Java不支持多继承主要是因为多继承可能会导致一些问题:
  1. 复杂性增加 :多继承会增加代码的复杂性。一个类需要理解和遵守多个父类的接口,这可能会导致混乱和错误。
  2. 菱形问题(Diamond Problem) :在多继承中,当一个类继承多个父类时,可能会出现菱形问题(Diamond Problem)。这个问题发生在当两个父类都继承自一个共同的基类,并且子类需要通过继承来同时获得这两个父类的特性时。这种情况下,编译器可能会无法确定应该使用哪个版本的基类方法。
  3. 资源管理 :在Java中,当一个对象不再被引用时,它会被垃圾收集器收集。但是,在多继承的情况下,对象何时被垃圾收集可能会变得复杂,因为可能有多个父类仍然持有对该对象的引用。
  4. 设计问题 :多继承可能会鼓励“过度设计”,即过度复杂化和不清晰的设计。这可能会导致代码难以理解和维护。
尽管Java不支持多继承,但Java提供了接口来解决多继承可能带来的问题。通过使用接口,我们可以实现多重继承的效果,同时避免多继承可能带来的问题。

蔚来

一、MYSQL专题: 脏读、幻读、不可重复读区别及解决方案

1、脏读 dirty read(读到未提交的数据)

    A事务正在修改数据但未提交,此时B事务去读取此条数据,B事务读取的是未提交的数据,A事务回滚。
解决方案:
方法1:事务隔离级别设置为: READ_COMMITTED( Oracle等多数数据库默认事务级别
方法2:读取时加排它锁(select…for update),事务提交才会释放锁,修改时加共享锁(update …lock in share mode)。加排它锁后,不能对该条数据再加锁,能查询但不能更改数据。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁,共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享。
2、不可重复读  Non-Repeatable read(前后多次读取,数据内容不一致)
    A事务中两次查询同一数据的内容不同,B事务间在A事务两次读取之间更改了此条数据。
解决方案:
方法1:事务隔离级别设置为 REPEATABLE_READ( InnoDB默认级别
方法2:读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题。
3、幻读  repeatable read(前后多次读取,数据总量不一致)
    在同一事务中两次相同查询数据的条数不一致,例如第一次查询查到5条数据,第二次查到8条数据,这是因为在两次查询的间隙,另一个事务插入了3条数据。
解决方案:
方法1:事务隔离级别设置为serializable ,那么数据库就变成了单线程访问的数据库,导致性能降低很多。
方法2:Isolation 属性一共支持五种事务设置,具体介绍如下:
DEFAULT: 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .
READ_UNCOMMITTED: 会读到未提交的数据, 出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )。
READ_COMMITTED【提交读】: 不会读到未提交的数据,会出现不可重复读、幻读问题(锁定正在读取的行)
REPEATABLE_READ 【可重复读】:会出幻读(锁定所读取的所有行)
SERIALIZABLE 【串行读】:保证所有的情况不会发生(锁表)。
4、MySQL InnoDB 引擎实现了标准的行级别锁:共享锁( S lock ) 和排他锁 ( X lock )
不同事务可以同时对同一行记录加 S 锁。
如果一个事务对某一行记录加 X 锁,其他事务就不能加 S 锁或者 X 锁,从而导致锁等待。
非事务修改加 X 锁,查询不加锁

二、索引失效场景有哪些

    1、未遵循最左匹配原则
    2、使用 列运算、 函数方法 和类型转换
    3、使用is not null
    4、错误的模糊匹配:like '%xx'
三、MySql Explain执行计划 或 Sql server Profiler 用过吗
事务失效
    1、MySql:explain sql
            
             1)、列说明:            
    • id — 选择标识符,id 越大优先级越高,越先被执行;
    • select_type — 表示查询的类型;
    • table — 输出结果集的表;
    • partitions — 匹配的分区;
    • type — 表示表的查询类型;
    • possible_keys — 表示查询时,可能使用的索引;
    • key — 表示实际使用的索引;
    • key_len — 索引字段的长度;
    • ref—  列与索引的比较;
    • rows — 大概估算的行数;
    • filtered — 按表条件过滤的行百分比;
    • Extra — 执行情况的描述和说明。
             2)、type字段说明:             
  • all — 扫描全表数据;
  • index — 遍历索引;
  • range — 索引范围查找;
  • index_subquery — 在子查询中使用 ref;
  • unique_subquery — 在子查询中使用 eq_ref;
  • ref_or_null — 对 null 进行索引的优化的 ref;
  • fulltext — 使用全文索引;
  • ref — 使用非唯一索引查找数据;
  • eq_ref — 在 join 查询中使用主键或唯一索引关联;
  • const — 将一个主键放置到 where 后面作为条件查询,
  • const、system、NULL指查询优化到常量级别, 甚至不需要查找时间.
  • MySQL 优化器就能把这次查询优化转化为一个常量,如何转化以及何时转化,这个取决于优化器,这个比 eq_ref 效率高一点。
         
四、binlog和redolog区别
    binlog(二进制日志)用于记录逻辑层操作,用于数据复制和恢复
      redolog(重做日志)用于记录物理层面操作,确保事务的持久性和崩溃恢复
五、Redis基本数据类型
    常用5中数据类型:String字符串类型,List列表类型,Hash哈希表类型,Set集合类型,Sorted Set有序集合类型
六、有序集合底层数据结构实现
    有序集合是由ziplist(压缩列表)或skiplist(跳跃表)组成的
    1、压缩列表ziplist本质上就是一个字节数组,是Redis为了节约内存而设计的线性数据结构 ,可以包含多个元素,每个元素可以是一个字节数组或一个整数
    2、跳跃表skiplist是yi'zhon有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点
    当数据来较少满足下面两个条件使用压缩列表ziplist,否则有序集合使用跳跃表skiplist结构存储
    1)、有序集合保存的元素个数小于128个
    2)、有序集合保存的所有元素成员的长度都必须小于64字节
七、跳表插入数据过程
    跳跃表(skiplist)是一种随机化的数据结构,是一种可以与平衡树媲美的层次化链表结构(查找、删除、添加可以在对数期望时间下完成)
    
    为了支持zset随机的插入和删除,redis考虑性能和实现采用跳跃表
        1)、性能考虑:高并发情况下,树形结构需要执行一些类似于rebalance这样可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部
            
        为什么随机层数,而不是固定层数,例如上图新增或者删除时,需要额外资源遵循规则重新计算处理
        2)、实现考虑:在复杂度与红黑树相同情况下,跳跃表实现更简单,看起来更直观
    添加流程:
    
    随机层数源码:随机层数有50%概率晋级,50%概率分到第1层,25%概率分到第2层,12.5%改了分到第3层,以此类推 2-63 的概率被分配到最顶层。 跳跃表默认允许最大的层数是 32 ZSKIPLIST_MAXLEVEL 
        
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
八、线程池有哪些参数
    1、corePoolSize:初始化核心线程数
      2、maximumPoolSize:最大线程数
      3、keepAlivedTime:空闲线程存活时间,如果大于初始化线程空闲时间此时间,会被销毁
      4、unit:存活时间单位
      5、workQueue:线程池任务队列
      6、threadFactory:线程创建工厂
      7、 RejectedExecutionHandler:线程池拒绝策略
九、线程池拒绝策略有哪些
    1、 AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;
      2、CallerRunsPolicy:把任务交给添加此任务的(main)线程来执行;
      3、DiscardPolicy:忽略此任务,忽略最新的一个任务;
      4、DiscardOldestPolicy:忽略最早的任务,最先加入队列的任务。
十、你最常用的线程池拒绝策略是哪种,为什么?
    最常用自定义拒绝策略,可以实现日志和告警业务代码,方便及时响应和异常任务再处理
十一、三个线程交替打印ABC
   
/*
* CyclicBarrier
* */
public static void cyclicMethod(){
    String printStr = "ABC";
    CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    Runnable tesk = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < printStr.length(); i++) {
                synchronized (this) {
                    sharedCounter = sharedCounter > 2 ? 0 : sharedCounter;
                    System.out.println(printStr.toCharArray()[sharedCounter++]);
                }
            }
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(tesk).start();
    new Thread(tesk).start();
    new Thread(tesk).start();
}

/*
* CountDownLatch
* */
public static void countDownMethod(){
    String printStr = "ABC";
    CountDownLatch countDownLatch = new CountDownLatch(3);
    Runnable tesk = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < printStr.length(); i++) {
                synchronized (this) {
                    sharedCounter = sharedCounter > 2 ? 0 : sharedCounter;
                    System.out.println(printStr.toCharArray()[sharedCounter++]);
                }
            }
            try {
                countDownLatch.countDown();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(tesk).start();
    new Thread(tesk).start();
    new Thread(tesk).start();
}
十二、力扣括号生成
十三、其他:AOP失效、 hashmap和LinkedHashMap区别、 进程上下文切换
xx公司

一、线程池的几种创建方式

    1、Executors 执行器创建线程

         1)、Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;

        2)、Executors.newCachedThreadPool

        3)、Executors.newSingleThreadExecutor

        4)、Executors.newScheduledThreadPool

        5)、Executors.newSingleThreadScheduledExecutor

        6)、Executors.newWorkStealingPool

    2、ThreadPoolExecutor 线程执行器创建线程(核心线程数,最大线程数,最大线程存活时间,存活时间单位,阻塞队列,线程工厂和拒绝策略可不传)默认策略为 AbortPolicy:拒绝并抛出异常

二、锁升级过程        

            

无锁:初始状态

    一个对象被实例化后,如果还没有被任何线程竞争锁,那么它就为无锁状态(01)。

偏向锁:单线程竞争

    当线程A第一次竞争到锁时,通过CAS操作修改Mark Word中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步。

轻量级锁:多线程竞争,但是任意时刻最多只有一个线程竞争

    如果线程B再去竞争锁,发现偏向线程ID不是自己,那么偏向模式就会立刻不可用。即使两个线程不存在竞争关系(线程A已经释放,线程B再去获取),也会升级为轻量级锁(00)。

重量级锁:同一时刻多线程竞争

    一旦轻量级锁CAS修改失败,说明存在多线程同时竞争锁,轻量级锁就不适用了,必须膨胀为重量级锁(10)。此时Mark Word存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程必须进入阻塞状态。

        

三、i++保证线程安全性

    使用cas(AtomicReference)、锁、synchronized保证i++原子性操作,使用AtomicStampedReference通过时间戳解决CAS ABA问题

四、HashMap和ConcurrentHashMap区别

    1、线程安全

    2、性能:HashMap单线程更好,多线程ConcurrentHashMap使用分段锁技术更好

    3、HashMap允许null值和键,ConcurrentHashMap 不允许null 键,也允许null 值

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值