JavaSE进阶(集合、多线程、反射)

JavaSE进阶(集合、多线程、反射)

集合

说说常见的集合有哪些吧?

答:Map接口和Collection接口是所有集合框架的父接口:

  1. Collection接口的子接口包括:Set接口和List接口
  2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

HashMap与HashTable的区别?

  1. HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
  2. HashMap允许K/V都为null;后者K/V都不允许为null;
  3. HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;

ArrayList和LinkedList的区别?

  1. LinkedList 实现了 List 和 Deque 接口,一般称为双向链表;ArrayList 实现了 List 接口,动态数组;
  2. LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;

面试官:Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?

答:它们的区别是:

  1. Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
  2. Array 大小是固定的,ArrayList 的大小是动态变化的。
  3. ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

HashSet和TreeSet的区别

实现方式

​ HashSet:HashSet是哈希表实现的。

​ TreeSet:TreeSet是二差树实现的。
​ 数据是否有序

​ HashSet:HashSet中的数据是无序的。

​ TreeSet:Treeset中的数据是自动排好序的。

是否可以放入null值

​ HashSet:可以放入null,但只能放入一个null。

​ TreeSet:不允许放入null值。

hashset想要排序,把hashset放入treeset的构造器中;

HashMap的扩容操作是怎么实现的?

通过分析源码我们知道了HashMap通过resize()方法进行扩容或者初始化的操作,下面是对源码进行的一些简单分析:

/**
 * 该函数有2中使用情况:1.初始化哈希表;2.当前数组容量过小,需要扩容
 */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;// 扩容前的数组(当前数组)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;// 扩容前的数组容量(数组长度)
    int oldThr = threshold;// 扩容前数组的阈值
    int newCap, newThr = 0;

    if (oldCap > 0) {
        // 针对情况2:若扩容前的数组容量超过最大值,则不再扩容
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 针对情况2:若没有超过最大值,就扩容为原来的2倍(左移1位)
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }

    // 针对情况1:初始化哈希表(采用指定或者使用默认值的方式)
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }

    // 计算新的resize上限
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        // 把每一个bucket都移动到新的bucket中去
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

什么是哈希?

Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);

什么是哈希冲突?

当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。

为什么HashMap中String、Integer这样的包装类适合作为K?

答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

  1. 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
  2. 内部已重写了equals()hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;

HashSet是如何保证数据不可重复的?

答:HashSet的底层其实就是HashMap,只不过我们HashSet是实现了Set接口并且把数据作为K值,而V值一直使用一个相同的虚值来保存,我们可以看到源码:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
}

由于HashMap的K值本身就不允许重复,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V,那么在HashSet中执行这一句话始终会返回一个false,导致插入失败,这样就保证了数据的不可重复性;

为什么java基本类型不能放入treeSet?

Java集合如Map、Set、List等所有集合只能存放引用类型数据,它们都是存放引用类型数据的容器,不能存放如int、long、float、double等基础类型的数据。

String和Stringbuffer的区别;

.类加载过程:
首先,jvm在执行时,遇到一个新的类时,会到内存中的方法区去找class文件,如果找到就直接拿来用,如果没有找到,
就会到硬盘中去找到这个class文件,将类文件加载到方法区,在类加载时,静态成员变量加载到方法区的静态区域,非静态
成员变量加载到方法区的非静态区域,加载静态成员变量的时候,会根据书写的顺序,将所有的静态成员变量加载到静态方法
区,然后给所有的静态成员变量赋默认值,赋完默认值后,会根据静态成员变量书写的位置,给静态成员变量赋显示值,和执
行静态代码。当执行完所有的静态代码块时,类才算加载完成。

多线程

创建线程的三种方式?

  • 继承thread类

    package thread;
    
    /**
     * @ClassName Demo1
     * 创建线程的方式:继承Thread(已实现Runnable接口)
     * @Author oliver
     * @Date 2020/9/6 12:41
     */
    public class Demo1 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("我是run方法:" + i + "=======================");
            }
        }
    
        public static void main(String[] args) {
            Demo1 demo1 = new Demo1();
            //通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
            //demo1.start();
            //开始执行run方法会直接执行,待执行完才执行其他方法
            //run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
            demo1.run();
            for (int i = 0; i < 1000; i++) {
                System.out.println("我是主方法:" + i + "//");
            }
        }
    }
    //start方法可启动多线程
    //run方法只是thread的一个普通方法调用,还是在主线程里执行,是不会开启多线程的
    
  • 实现runnable接口

    public class Demo3 implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("我是run方法:" + i + "=======================");
            }
        }
    
        public static void main(String[] args) {
            Demo3 demo3 = new Demo3();
            Thread thread = new Thread(demo3);
            thread.start();
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("我是主方法:" + i + "//");
            }
        }
    }
    
  • 实现Callable接口

什么是多线程?

就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

如何启动线程?start和run的区别?

//通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
//demo1.start();
//开始执行run方法会直接执行,待执行完才执行其他方法
//run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

wait和sleep的区别?

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

终止线程的三种方式?

自动终止

在 run() 方法执行完毕后,该线程就终止了。但是在某些特殊的情况下,run() 方法会被一直执行;比如在服务端程序中可能会使用 while(true) { ... } 这样的循环结构来不断的接收来自客户端的请求。此时就可以用修改标志位的方式来结束 run() 方法。

stop()

虽然 stop() 方法确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且该方法已被弃用,最好不要使用它。

interrupt()因特rua普特

现在我们知道了使用 stop() 方式停止线程是非常不安全的方式,那么我们应该使用什么方法来停止线程呢?答案就是使用 interrupt() 方法来中断线程

需要明确的一点的是:interrupt() 方法并不像在 for 循环语句中使用 break 语句那样干脆,马上就停止循环。调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。

什么是死锁?如何避免?

相互持有对方的资源锁

什么是同步?

如何加锁?

synchronized(sei亏儿来自的)(隐式锁,没有开启和关闭)

Lock(java1.5之后出的,可以手动开启和关闭)

为什么线程池执行要快一些?线程池的原理?

反射

注解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值