java-题库

  • 一、hashMap的底层实现
  • 1.1、hashMap概述
  • 1.2、JDK1.7-扩容源码
  • 1.3、JDK1.7链表迁移过程
  • 1.4、JDK1.8下的扩容机制
  • 二、java面向对象的三大特征
  • 三、JVM相关
  • 四、集合相关
  • 五、JDK1.8新特性
  • 5.1、接口内可以添加非抽象的方法实现
  • 5.2、Lambda表达式
  • 5.3、函数式接口
  • 5.4、Stream API
  • 5.6、引入了ForkJoin框架
  • 5.7、引入了新的日期API LocalDate | LocalTime | LocalDateTime
  • 六、抽象类和接口的区别
  • 七、==和eques区别


一、hashMap的底层实现

1.1、hashMap概述

  • HashMap基于Map接口实现,键值对存储,允许使用null 建和null值。
  • HashMap无序的,HashMap线程不安全。

1.2、JDK1.7-扩容源码

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { 
    //......
    // 扩容方法
    void resize(int newCapacity) {
        // 1、创建临时变量,将HashMap数组数据赋值给新数组作临时存储
        Entry[] oldTable = table;
        // 2、判断老数组长度是否超过了允许的最大长度,最大长度为 1 << 30
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
		// 3、创建新的Entry数组,并扩容
        Entry[] newTable = new Entry[newCapacity];
        // 4、扩容赋值,即将老数组中的数据赋值到新数组中
        // initHashSeedAsNeeded(newCapacity) 得到的是一个hash的随机值(哈希种子),在计算哈希码时会用到这个种子,作用是减少哈希碰撞
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        // 6、扩容后赋值
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    
    // newTable : 表示新数组,即扩容后创建的新数组
    // rehash : 是否需要重新计算哈希值
    void transfer(Entry[] newTable, boolean rehash) {
		int newCapacity = newTable.length;
        // 5、将老map中的数据赋值到新map中(数组和链表复制迁移)
		for (Entry<K,V> e : table) {   
			while(null != e) {
				Entry<K,V> next = e.next;
				if (rehash) {
					e.hash = null == e.key ? 0 :hash(e.key);
				}
                // 计算Entry元素在Entry[]数组中的位置
				int i = indexFor(e.hash, newCapacity);
				
				// 链表头插法赋值过程
				e.next = newTable[i];
				newTable[i] = e;
				e = next;
			}
		}
	}
    
    //......
    
}

1.3、JDK1.7链表迁移过程

  • 主要的代码
e.next = newTable[i];
	newTable[i] = e;
	e = next;
  • 假设HashMap的存储状态如下
  • 第一次处理e
//将e的next置为null
    e.next = newTable[i]  
 	对oldTable进行遍历的过程中,取出元素e,假设先取出图中的元素e,
在执行这行代码时,相当于断开x位置e与e1的链表关系,并与newTable[i]建立链表关系,
此时newTable[i]位置为null

  //将e的赋值给,新数组newTable[i] 
  newTable[i] = e
  此时将oldTable中的e复制到newTable中的i位置,同时链表e指向null

java试题扫描_java试题扫描

  • 第二次处理e1
while(null != e) {
	// 这里已经将e.next存储为一个临时变量,也就是e1和e2形成的链表
	Entry<K,V> next = e.next;
	if (rehash) {
		e.hash = null == e.key ? 0 :hash(e.key);
	}
	......
}
// 此时为 e1,组成的链表
  Entry<K,V> next = e.next;
  
  //再进行一次遍历分析,而这次遍历分析从e1开始 即e1.next
  e.next = newTable[i]//此为e
  
  newTable[i] = e//此e为e1,他的nect 挂的e,
  //即 newTable[i]的第一个元素为e1

java试题扫描_数组_02

  • 第n次处理后
链表顺序:
   开始为e-->e1-->e2
完成扩容后:
   e2-->e1-->e

java试题扫描_System_03

1.4、JDK1.8下的扩容机制

  • 底层实现由之前的 “数组+链表” 改为 “数组+链表+红黑树”。
  • 当链表节点较少时仍然是以链表存在,当链表节点较多时(大于8)且数组的长度大于64,时会转为红黑树
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

	/**
     * put()方法真正逻辑所在
     *
     * @param hash key的hash值
     * @param key key
     * @param value value值
     * @param onlyIfAbsent true表示不更新现有值
     * @param evict 如果为false,则表处于创建模式。HashMap中暂未使用
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // tab 哈希数组,p 该哈希桶的首节点,n hashMap的长度,i 计算出的数组下标
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果数组还没初始化,进行数组初始化。使用懒加载机制,table一开始是没有加载的,等put后才开始加载
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 如果根据key计算出的数组位置没有数据
        if ((p = tab[i = (n - 1) & hash]) == null)
        	// 新建一个Node节点插入到相应数据位置。
            tab[i] = newNode(hash, key, value, null);
        // key的hash值对应的数组下标位置不为空的情况
        else {
            // e 临时节点 ,k 当前节点的key
            Node<K,V> e; K k;
            //第一种,数组hash槽首节点的key与当前节点的相等,将当前节点赋值给临时节点e = p
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // hash槽的数据结构为红黑树
            else if (p instanceof TreeNode)
                // 在红黑树中进行添加,如果该节点已经存在,则返回该节点(不为null),用与判断put操作是否成功,如果添加成功返回null
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 链表节点
            else {
                // 遍历链表
                for (int binCount = 0; ; ++binCount) {
                    // 如果在链表尾部还没有找到当前key值,则在链表尾部新增节点
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 判断是否转红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果链表中有重复的key,e则为当前重复的节点,并结束链表的循环遍历
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 如果key值以前已经存在,则对value进行覆盖,并返回value的旧值。
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 将红黑树的root节点放到链表队首,并将root Node存储在table数组中。
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 走到这说明新增了一个Node节点,对Map的修改次数进行+1
        ++modCount;
        // Map的元素容量+1,如果大于扩容阈值,进行扩容
        if (++size > threshold)
            resize();
        // 模板方法,后置处理器
        afterNodeInsertion(evict);
        return null;
    }

java试题扫描_java_04

二、java面向对象的三大特征

1、封装:属性私有化,提供setter和getter方法
2、继承:继承是指将多个相同的属性和方法提取出来,新建一个父类
3、多态:分为两种:设计时多态、运行时多态

三、JVM相关

四、集合相关

第一代线程安全集合类
	Vector. Hashtable
	是怎么保证线程安排的:使用synchronized修饰方法
	缺点:效率低下

第二代线程非安全集合类
	ArrayList, HashMap
	线程不安全,但是性能好,用来替代Vector、Hashtable
	使用ArrayList、HashMap,需要线程安全怎么办呢?
	使用 Collections.synchronizedList(list); 
	     Collections.synchronizedMap(m);
	     
第三代线程安全集合类
	在大量并发情况下如何提高集合的效率和安全呢?
	java.utll.concurrent.*
	ConcurrentHashMap:
	CopyOnWriteArrayList :
	CopyOnWriteArraySet: 注意不是 CopyOnWriteHashSet*
	底层大都采用Lock锁(1.8的ConcurrentHashMap不使用LocktW ,保证安全的同时,
  性能也很高.

五、JDK1.8新特性

5.1、接口内可以添加非抽象的方法实现

java 8允许我们给接口添加一个非抽象的方法实现,只需要使用ddefault关键字即
可,这个特征又叫做犷展方法,
	代码如下:
	interface Formula {
		double calculatefint a);
		default double sqrt(int a) {return Math.sqrt(a);}
	}
	Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类
只需要实现calculate方法,默认方法sqrl将在子类上可以直接使用.

5.2、Lambda表达式

Lambda 规定接口中只能有一个需要被实现的方法。

5.3、函数式接口

  • 函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
  • 简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解@FunctionalInterface

5.4、Stream API

  • Stream操作的三个步骤:创建stream,中间操作,终止操作
  • 创建stream
// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
    List<String> list = new ArrayList<>();
    Strean<String> stream1 = list.stream();

    // 2.通过Arrays的静态方法stream()获取数组流
    String[] str = new String[10];
    Stream<String> stream2 = Arrays.stream(str);

    // 3.通过Stream类中的静态方法of
    Stream<String> stream3 = Stream.of("aa","bb","cc");

    // 4.创建无限流
    // 迭代
    Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);

    //生成
    Stream.generate(() ->Math.random());
  • 中间操作(过滤、map)
/**
   * 筛选 过滤  去重
   */
  emps.stream()
          .filter(e -> e.getAge() > 10)
          .limit(4)
          .skip(4)
          // 需要流中的元素重写hashCode和equals方法
          .distinct()
          .forEach(System.out::println);


  /**
   *  生成新的流 通过map映射
   */
  emps.stream()
          .map((e) -> e.getAge())
          .forEach(System.out::println);


  /**
   *  自然排序  定制排序
   */
  emps.stream()
          .sorted((e1 ,e2) -> {
              if (e1.getAge().equals(e2.getAge())){
                  return e1.getName().compareTo(e2.getName());
              } else{
                  return e1.getAge().compareTo(e2.getAge());
              }
          })
          .forEach(System.out::println);
  • 终止操作
/**
         *      查找和匹配
         *          allMatch-检查是否匹配所有元素
         *          anyMatch-检查是否至少匹配一个元素
         *          noneMatch-检查是否没有匹配所有元素
         *          findFirst-返回第一个元素
         *          findAny-返回当前流中的任意元素
         *          count-返回流中元素的总个数
         *          max-返回流中最大值
         *          min-返回流中最小值
         */

    /**
     *  检查是否匹配元素
     */
    boolean b1 = emps.stream()
             .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
    System.out.println(b1);

    boolean b2 = emps.stream()
            .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
    System.out.println(b2);

    boolean b3 = emps.stream()
            .noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
    System.out.println(b3);

    Optional<Employee> opt = emps.stream()
            .findFirst();
    System.out.println(opt.get());

    // 并行流
    Optional<Employee> opt2 = emps.parallelStream()
            .findAny();
    System.out.println(opt2.get());

    long count = emps.stream()
            .count();
    System.out.println(count);

    Optional<Employee> max = emps.stream()
            .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
    System.out.println(max.get());

    Optional<Employee> min = emps.stream()
            .min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
    System.out.println(min.get());

5.6、引入了ForkJoin框架

  • 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总
/**
 * 要想使用Fark—Join,类必须继承
 * RecursiveAction(无返回值)
 * Or
 * RecursiveTask(有返回值)
*
*/
public class ForkJoin extends RecursiveTask<Long> {

    /**
     * 要想使用Fark—Join,类必须继承RecursiveAction(无返回值) 或者
     * RecursiveTask(有返回值)
     *
     * @author Wuyouxin
     */
    private static final long serialVersionUID = 23423422L;

    private long start;
    private long end;

    public ForkJoin() {
    }

    public ForkJoin(long start, long end) {
        this.start = start;
        this.end = end;
    }

    // 定义阙值
    private static final long THRESHOLD = 10000L;

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            long sum = 0;
            for (long i = start; i < end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (end - start) / 2;
            ForkJoin left = new ForkJoin(start, middle);
            //拆分子任务,压入线程队列
            left.fork();
            ForkJoin right = new ForkJoin(middle + 1, end);
            right.fork();

            //合并并返回
            return left.join() + right.join();
        }
    }

    /**
     * 实现数的累加
     */
    @Test
    public void test1() {
        //开始时间
        Instant start = Instant.now();

        //这里需要一个线程池的支持
        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask<Long> task = new ForkJoin(0L, 10000000000L);
        // 没有返回值     pool.execute();
        // 有返回值
        long sum = pool.invoke(task);

        //结束时间
        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).getSeconds());
    }

    /**
     * java8 并行流 parallel()
     */
    @Test
    public void test2() {
        //开始时间
        Instant start = Instant.now();

        // 并行流计算    累加求和
        LongStream.rangeClosed(0, 10000000000L).parallel()
                .reduce(0, Long :: sum);

        //结束时间
        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).getSeconds());
    }

    @Test
    public void test3(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        list.stream().forEach(System.out::print);

        list.parallelStream()
            .forEach(System.out::print);
    }

5.7、引入了新的日期API LocalDate | LocalTime | LocalDateTime

  • LocalDateTime
@Test
    public void test(){
        // 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差
        LocalDateTime date = LocalDateTime.now();
        //2018-07-15T14:22:39.759
        System.out.println(date);

        System.out.println(date.getYear());
        System.out.println(date.getMonthValue());
        System.out.println(date.getDayOfMonth());
        System.out.println(date.getHour());
        System.out.println(date.getMinute());
        System.out.println(date.getSecond());
        System.out.println(date.getNano());

        // 手动创建一个LocalDateTime实例
        LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
        System.out.println(date2);
        // 进行加操作,得到新的日期实例
        LocalDateTime date3 = date2.plusDays(12);
        System.out.println(date3);
        // 进行减操作,得到新的日期实例
        LocalDateTime date4 = date3.minusYears(2);
        System.out.println(date4);
    }
  • LocalDate
//获取当前日期,只含年月日 固定格式 yyyy-MM-dd    2018-05-04
        LocalDate today = LocalDate.now();

        // 根据年月日取日期,5月就是5,
        LocalDate oldDate = LocalDate.of(2018, 5, 1);

        // 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
        LocalDate yesteday = LocalDate.parse("2018-05-03");

        // 如果不是闰年 传入29号也会报错
        LocalDate.parse("2018-02-28");

六、抽象类和接口的区别

  • 描述特征用接口
  • 描述概念用抽象类

七、==和eques区别

==号在比较基本数据类型时比较的是值,
	而用==号比较两个对象时比较的是两个对象的地址值。
  • equals()方法存在于Object类中,因为Object类是所有类的直接或间接父类,也就是说所有的类中的equals()方法都继承自Object类,而通过源码我们发现,Object类中equals()方法底层依赖的是==号,那么,在所有没有重写equals()方法的类中,调用equals()方法其实和使用==号的效果一样,也是比较的地址值,然而,Java提供的所有类中,绝大多数类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值.