1.java基础面试题

目录

20210126

1.java有哪些特性

2.什么是JVM?

3.Java跨平台原理

4.什么是GC,为什么要有GC

5.Javase组成

6.JDK与JRE

7.Scanner的nextLine()方法

8.switch使用注意事项

9.简述java基本数据类型及内存空间

10.&与&&区别

11.异常是否抛出,应该站在哪个角度思考?

12.finally两个考点:必然执行和return时机

20210128

13.说说你对封装的理解

14.面向过程到面向对象思想层面的转变

15.类与对象关系

16.创建对象内存分析(栈、堆、方法区、PC寄存器、本地方法栈)

16.1.【栈】

16.1.1.Java栈存取数据特点?

16.1.2.栈存储速度快原因?

16.1.3.为什么大部分数据存储到堆内存而不是栈?

16.1.4.栈存储数据的类型

16.2.【堆】

16.2.1.堆存储数据的类型

16.2.2.堆内存与栈内存不同

16.3.【方法区】

16.3.1.方法区可以存放哪些内容?

16.4.【PC寄存器、本地方法栈】

17.static用法

18.权限修饰符使用范围

19.说说单例设计模式

20.抽象类几个问题

21.抽象类和普通类区别

22.面向接口编程优点

23.接口包含哪些?什么时候将类定义成接口?

24.抽象类与接口区别

25.多态的两种体现

20210204

26.数据存储常用结构有哪些?

27.使用栈结构的集合对元素的存取有什么特点?

28.使用队列结构的集合对元素存取有什么特点?

29.使用数组的集合对元素存储的特点?

30.使用单链表的集合对元素存储特点?

31.红黑树结构的集合存储元素特点

32.类集设置的目的、java类集结构图

33.Collection接口常用方法

34.List接口常用方法

35.ArrayList通过无参构造器构造集合初始长度/ArrayList在存储时进行扩容的算法/原理

36.ArrayList与Vector区别(vector实现原理)

37.LinkedList 实现原理及使用

38.Iterator原理和使用

39.Set接口

40.TreeSet与HashSet区别

41.Map、TreeMap、HashMap特点

42.HashMap和Hashtable区别

43.哈希表如何存储元素

44.HashMap源码分析

45.HashMap、Hashtable、ConcurrentHashMap区别

46.散列表散列操作

47.哈希值错乱问题

20210126

1.java有哪些特性

  1. 面向对象
  2. 跨平台
  3. 一种健壮的语言,吸收了 C/C++语言的优点
  4. 有较高的安全性。(自动回收垃圾,强制类型检查,取消指针)

2.什么是JVM?

  1. JVM 可以理解成一个可运行 Java 字节码的虚拟计算机系统.
  2. 它有一个解释器组件,可以实现 Java 字节码和计算机操作系统之间的通信.
  3. 对于不同的运行平台,有不同的JVM
  4. JVM 屏蔽了底层运行平台的差别,实现了“一次编译,随处运行”。

 

3.Java跨平台原理

  • java代码经编译器编译成字节码文件,字节码文件被jvm运行,jvm是万能翻译机,将字节码文件翻译给不同操作系统去执行。

4.什么是GC,为什么要有GC

  • 在 C/C++等语言中,由程序员负责回收无用内存。
  • Java 语言消除了程序员回收无用内存空间的责任: JVM 提供一种系统线程跟踪存储空间的分配情况。并在 JVM 的空闲时,检查并释放那些可以被释放的存储空间。
  • 垃圾回收器在 Java 程序运行过程中自动启用,程序员无法精确控制和干预
  • 有效的防止内存泄漏和使用内存。

5.Javase组成

6.JDK与JRE

  • JDK(Java Development Kits)-- Java 开发工具集
  • JRE(Java Runtime Environment)Java 运行时环境
  • JDK主要包含:工具程序+API+JRE+JVM
    • Java API (应用程序编程接口)、
    • Java 编译器(javac.exe)、Java 运行时解释器(java.exe)、Java 文档化化工具(javadoc.exe)及其它工具及资源
    • JRE JVM
  • JRE 的三项主要功能:
    • 加载代码:由类加载器(class loader)完成; ​
    • 校验代码:由字节码校验器(byte code verifier)完成; ​
    • 执行代码:由运行时解释器(runtime interpreter)完成。

7.Scanner的nextLine()方法

Scanner的nextLine()方法为什么有时候接收不到值? 为什么有时候控制台会跳过我的输入直接运行下面的代码?

  • 其实Scanner对象是接收到了值的,否则也不会直接跳过输入过程。它接收到的是一个换行符(\n),是前面的next()、nextInt()等等这样的方法遗留下来的。
  • Next()方法是按照空格来切分的,不会接收\n。想避免这种现象有两种方法:
    • 1.使用nextLine()先接收一次上面遗留的\n,不必保留返回值,再使用String str=scanner.nextLine()保留返回值,接收真正有用的数据;
    • 2.在使用nextLine()方法时就不要使用任何其他的next系列方法,需要其他类型都用转型方法+异常处理来解决。
  • nextLine和其他的next方法有冲突,所有输入都使用nextLine,这样不会因为输入产生冲突,还可以接收各种类型数据(parsexxx类型转换)

8.switch使用注意事项

  •  switch有两个参数:
    • ()中必须使用变量,类型必须是String、byte、short、int、char、枚举中的某一种类型,不能是long 类型。
    • case后面跟着的必须是常量;
  • switch仅适用于等值判断场合,发生区间判断的场合仅能使用if-elseif-else方式。
  • switch中的break一般不能省略,省略后会发生case穿透,导致两个case中的代码同时进行,仅适用于确定想让多个case执行相同语句,或多个case的执行代码互相包含时才会选择性省略case,如月份日期的输出、文件更新补丁包的覆盖等等。
  • default语句一般可以省略,适合需要对确定分支之外的所有情况进行判定时使用。
  • 前面case定义的变量在后面的case中会发生同名冲突现象,解决方案:将每个case都封装成各自的方法或者单独加上大括号表示分隔。

9.简述java基本数据类型及内存空间

  • 描述整数的数据类型主要有:byte/short/int/long,分别占用1个/2个/4个/8个字节大小。
  • 描述小数的数据类型主要有:float/double,分别占用4个/8个字节大小。 
  • 描述真假的数据类型有:boolean,占用1个字节大小。
  •  描述字符的数据类型有:char,占用2个字节。

10.&与&&区别

1.&和&&都可以用作逻辑与的运算符,表示逻辑与(and),全真则真,否则假。 

2.&&还具有短路的功能,即如果第一个表达式为 false,则不再计算第二个表达式,例如,对于 if(str!=null &&!str.equals(“”))表达式,当 str 为 null 时,后面的表达式不会执行,所以不会出现 NullPointerException 如果将 &&改为&,则会抛出 NullPointerException 异常。If(x==33&++y>0)y 会增长,If(x==33&&++y>0)不会增长 

3.&还可以用作位运算符,当&操作符两边的表达式不是 boolean 类型时,&表示按位与操作,我们通常使用 0x0f 来与一个整数进行&运算,来获取该整数的最低 4 个 bit 位,例如,0x31&0x0f 的结果为 0x01。

11.异常是否抛出,应该站在哪个角度思考?

  • 如果是因为传参导致异常(非方法体问题),则通过throws抛出。

12.finally两个考点:必然执行和return时机

public class Demo6 {
    public static void main(String[] args) {
        Person p = haha();
        System.out.println(p.age);
    }
    public static Person haha(){
        Person p = new Person();
        try{
            p.age = 18;
            return p;
        }catch(Exception e){
            return null;
        }finally {
            p.age = 28;
        }
    }
    static class Person{
        int age;
    }
}
  • 输出结果28,try中return p返回是备份好的返回值p,但p是引用类型指向内容,而finally中修改了p的内容(堆中),因此备份好的返回值也会改变。备份好的p还没被返回就被修改。如下图:

public class Demo7 {
    public static void main(String[] args) {
        int a = haha();
        System.out.println(a);
    }
    public static int haha(){
        int a = 10;
        try{
            return a;
        }catch(Exception e){

        }finally {
            a = 20;
        }
        return 0;
    }
    static class Person{
        int age;
    }
}
  • 输出结果10;try中return a返回的是备份好的a值,finally中修改的是栈中a的值,因为a不是引用类型,所以备份和修改的不是同一个变量。

package com.java.demo1;

public class Demo8 {
    public static void main(String[] args) {

        haha();
    }
    public static void haha(){
        try{
            int a = 10;
            int b = 0;
            System.out.println(a/b);
        }catch(Exception e){
            //退出JVM
            System.out.println("出现了异常");
            System.exit(0);
        }finally {
            System.out.println("锄禾日当午,汗滴禾下土");
        }
    }
}
  • 程序输出"出现了异常"后就退出程序,没有执行finally,这是唯一不执行finally的情况System.exit(0)正常退出程序。

20210128

13.说说你对封装的理解

  1. 什么是封装:封装就是把类中的属性给上不同的访问权限,不允许外界随意修改。
  2. 为什么封装:保护类中的数据不被破坏
  3. 怎么封装:把类中的属性用不同的访问修饰符修饰,给外界提供可以操作的方法

14.面向过程到面向对象思想层面的转变

  1. 面向过程关注的是执行的过程,面向对象关注的是具备功能的对象
  2. 面向过程到面向对象,是程序员思想上 从执行者到指挥者的转变。

15.类与对象关系

  1. 类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征
  2. 类必须通过对象才可以使用,对象的所有操作都在类中定义。
  3. 类由属性和方法组成:
    1. 属性:就相当于人的一个个的特征 ·
    2. 方法:就相当于人的一个个的行为,例如:说话、吃饭、唱歌、睡觉

16.创建对象内存分析(栈、堆、方法区、PC寄存器、本地方法栈)

16.1.【栈】

16.1.1.Java栈存取数据特点?

  • Java栈的区域很小 , 大概2m左右 , 特点是存取的速度特别快,, 先进后出

16.1.2.栈存储速度快原因?

  • 栈内存, 通过 '栈指针' 来创建空间与释放空间 !
  • 指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 !
  • 这种方式速度特别快 , 仅次于PC寄存器 !

16.1.3.为什么大部分数据存储到堆内存而不是栈?

  • 栈使用栈指针进行数据存储,必须要明确移动的大小与范围 (需要关注开辟多少空间),
  • 明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序 的灵活性 ~ 所以我们把更大部分的数据 存储到了堆内存中

16.1.4.栈存储数据的类型

  • 基本数据类型的数据 以及 引用数据类型的引用!

16.2.【堆】

16.2.1.堆存储数据的类型

引用类型

16.2.2.堆内存与栈内存不同

  • 堆内存优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用 时长 !
  • 堆内存中内存的释放是由GC(垃圾回收器)完成的
  • 垃圾回收器 回收堆内存的规则:当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收 !

16.3.【方法区】

16.3.1.方法区可以存放哪些内容?

存放的是

  • - 类信息
  • - 静态的变量
  • - 常量
  • - 成员方法

方法区中包含了一个特殊的区域 ( 常量池 )(存储的是使用static修饰的成员)

16.4.【PC寄存器、本地方法栈】

  • PC寄存器存放的内容
    • PC寄存器保存的是 当前正在执行的 JVM指令的 地址 !
    • 在Java程序中, 每个线程启动时, 都会创建一个PC寄存器 !
  • 本地方法栈存放的内容
    • 保存本地(native)方法的地址 !

17.static用法

  • static表示“静态”的意思,可以用来修饰成员变量和成员方法(后续还会学习 静态代码块 和 静态内部类)。
  • static的主要作用在于创建独立于具体对象的域变量或者方法
  • 简单理解: 被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访 问。 并且不会因为对象的多次创建 而在内存中建立多份数据
  • 1. 静态成员 在类加载时加载并初始化
  • 2. 无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 )
  • 3. 在访问时: 静态不能访问非静态 , 非静态可以访问静态 !内存中是【先】有静态内容,【后】有非静态内容,静态方法中不能使用this

18.权限修饰符使用范围

19.说说单例设计模式

  • 单例设计模式:保证程序在内存中只有一个对象存在(被程序所共享)
  • 单例设计模式的两种实现方式:
    • 一、懒汉式:随着类的加载在内存中对象为null,当调用 getInstance 方法时才创建对象(延迟加载)
    • 二、饿汉式:随着类的加载直接创建对象(推荐开发中使用)
  • 单例设计模式的实现步骤:
    • 1.保证一个类只有一个实例,实现方式:构造方法私有化
    • 2.必须要自己创建这个实例,实现方式:在本类中维护一个本类对象(私有,静态)
    • 3.必须向整个程序提供这个实例,实现方式:对外提供公共的访问方式(getInstance方法,静态)
懒汉式实现如下:
class Single{
    private Single(){}

    private static Single s1 = null;

    public static Single getInstance(){
        if(s1 == null){
            s1 = new Single();
        }
        return s1;
    }
}

饿汉式实现如下:
class Single2{
    private Single2(){}

    private static Single2 s = new Single2();

    public static Single getInstance(){
        return s;
    }

    void print(){
        System.out.println("Hello World!");
    }
}

20.抽象类几个问题

  • 抽象类能被实例化吗?
    • 抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。
  • 抽象类能否使用final声明?
    • 不能,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。
  • 抽象类能否有构造方法?
    • 能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默 认是无参的),之后再调用子类自己的构造方法。

21.抽象类和普通类区别

  • 1、抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。 默认缺省为 public
  • 2、抽象类不可以使用new关键字创建对象, 但是在子类创建对象时, 抽象父类也会被JVM实例化。
  • 3、如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为 abstract类

22.面向接口编程优点

  • 这种思想是接口是定义(规范,约束)与实现(名实分离的原则)的分离
  • 优点:
    • 1、 降低程序的耦合性
    • 2、 易于程序的扩展
    • 3、 有利于程序的维护

23.接口包含哪些?什么时候将类定义成接口?

  • 如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
interface 接口名称{
  全局常量 ;
  抽象方法 ;
}


因为接口本身都是由全局常量和抽象方法组成 , 所以接口中的成员定义可以简写:

1、全局常量编写时, 可以省略public static final 关键字,例如:
public static final String INFO = "内容" ;---> String INFO = "内容" ;

2、抽象方法编写时, 可以省略 public abstract 关键字, 例如:
public abstract void print() ;--->void print() ;

24.抽象类与接口区别

  • 1、抽象类要被子类继承,接口要被类实现
  • 2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
  • 3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  • 4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
  • 5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
  • 6、接口不能有构造方法,但是抽象类可以有

25.多态的两种体现

  • 多态:就是对象的多种表现形式,(多种体现形态)。在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来。
  • 多态可以分为两设计时多态和运行时多态
  • 设计时多态:即重载,是指Java允许方法名相同而参数不同(返回值可以相同也可以不相同)。

  • 运行时多态:即重写,是指Java运行根据调用该方法的类型决定调用哪个方法。

    • 重载: 一个类中方法的多态性体现
    • 重写: 子父类中方法的多态性体现。
  • 多态目的:增加代码的灵活度。

20210204

26.数据存储常用结构有哪些?

  • 栈、队列、数组、链表和红黑树

27.使用栈结构的集合对元素的存取有什么特点?

  • stack是限定仅在表尾进行插入和删除操作的线性表
  • 先进后出
  • 栈的入口、出口的都是栈的顶端位置。

28.使用队列结构的集合对元素存取有什么特点?

  • queue只允许在表的 一端进行插入,而在另一端进行删除元素的线性表。只允许在表的 一端进行插入,而在另一端进行删除元素的线性表。
  • 先进先出
  • 队列的入口、出口各占一侧。

29.使用数组的集合对元素存储的特点?

  • 查找元素快:通过索引,可以快速访问指定位置的元素
  • 增删元素慢:增删都需要创建新数组,并将元素复制到新数组。

30.使用单链表的集合对元素存储特点?

  • linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
  • 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的 指针域。

  • 多个结点之间,通过地址进行连接

  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  • 增删元素快
    • 增加元素:只需要修改连接下个元素的地址即可。

  • 删除元素:只需要修改连接下个元素的地址即可。

31.红黑树结构的集合存储元素特点

  • 二叉树:binary tree ,是每个结点不超过2的有序树
  • 红黑树本身就是一颗二叉查找树
  • 红黑树的约束:
    • 1. 节点可以是红色的或者黑色的
    • 2. 根节点是黑色的
    • 3. 叶子节点(特指空节点)是黑色的
    • 4. 每个红色节点的子节点都是黑色的
    • 5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
  • 红黑树的特点:
    • 二分查找速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍

32.类集设置的目的、java类集结构图

  • 普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。

33.Collection接口常用方法

  • Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。
  • 在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。为了更加清楚的区分,集合中是否允许有重复元素.

34.List接口常用方法

  • 在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。,List 接口拥有比 Collection 接口更多的操作方法。
  •  ArrayList(95%)、Vector(4%)、LinkedList(1%)
  • ArrayList和Vector都是基于数组动态扩容(新老数组复制)
  • 在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。

35.ArrayList通过无参构造器构造集合初始长度/ArrayList在存储时进行扩容的算法/原理

ArrayList通过无参构造器构造集合初始长度

  • 下面是api提供的ArrayList构造器初始容量为10,这其实是扩容后的值,最初始值其实是空数组{}.
ArrayList()

构造一个初始容量为10的空列表。

ArrayList​(int initialCapacity)

构造具有指定初始容量的空列表。

  • 看下ArrayList构造器源码,点开elementData它是存储数据的数组,点开DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个值是一个默认final的空数组,也就是说通过arraylist无参构造器构造的集合就是一个空数组,初始容量就是0.
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
 transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

arraylist扩容算法

  • 创建list就是一个final修饰的空数组,容量0,当通过add方法添加元素时,点开add看下,无论添加成功与否返回的都是true,这是写死的。
ArrayList<Object> list = new ArrayList<>();
list.add(100);
  • 里面执行添加的语句是add(e, elementData, size);传递三个参数,一个是待添加的元素e,一个是存数据的数组elementData,最后一个是有效数组实际长度size.
 public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
 }
  • 点开add(e, elementData, size)看看是如何进行添加元素?如果数组有效长度s等于数组长度length,则数组已满,调用grow()进行扩容并返回扩容后数组,否则直接将元素存入elementData中。
   private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
  • grow()如何进行扩容?将目前数组长度(满长)加1作为数组最小容量传入到grow(int minCapacity)方法中,该方法中通过旧数组elementData和计算的新长度copyof出新数组给到elementData。新的长度通过newCapacity(minCapacity)方法计算出来,传参传的是最小需要的数组长度(原数组+1),
  private Object[] grow() {
        return grow(size + 1);
    }
private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
    }
  • newCapacity(minCapacity)是如何计算新数组长度?先获取旧长度oldCapacity(数组存满时总长度),新长度newCapacity是旧长度+旧长度右移1位(即二进制右移一位即旧长度除以2)即旧长度的1.5倍。
  • 下面做一个判断,若是新长度小于等于旧长度,说明新长度不够(比如未添加元素前初始长度是0,1.5倍还0;再比如添加一组数据,长度不可控时也是会进入判读)。
    • 进一步判断若数组elementData是默认初始空数组{},则返回默认长度DEFAULT_CAPACITY(即10)与最小需要长度minCapacity(旧长度+1)两者最大值作为新长度,API中说的初始容量10就是新建的list第一次添加元素后容量。
    • 另外当minCapacity长度超出int范围就会溢出,内存中二进制超出位数,然后由于符号的改变导致minCapacity变成负数,所以这里抛出一个内存溢出异常。
    • 如果elementData不是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA即默认空数组{}即不是第一次添加元素又或者没有抛异常,则返回最小需要长度minCapacity即需要多少就多少刚好够存储.
  • 若扩容以后新长度newCapacity够用,且新长度比允许的最大长度MAX_ARRAY_SIZE(即int类型最大值-8)小则返回新长度,若新长度比允许最大长度还大,则返回hugeCapacity(minCapacity)
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)? newCapacity: hugeCapacity(minCapacity);
    }
 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 该方法hugeCapacity(minCapacity)传递最小需要长度(旧长度+1),若minCapacity<0则内存溢出,若minCapacity大于允许的最大值MAX_ARRAY_SIZE则返回Int类型最大值Integer.MAX_VALUE,否则返回允许的最大值MAX_ARRAY_SIZE。
 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)? Integer.MAX_VALUE: MAX_ARRAY_SIZE;
    }

总之:

  • arraylist扩容就是根据旧数组和计算出的新长度copyof出新数组返回给elementData,arraylist初始是final修饰空数组长度0,当添加第一个元素时默认创建长度10的数组,之后扩容就是1.5倍进行扩容。

36.ArrayList与Vector区别(vector实现原理)

相同:

  • 都实现List接口,ArrayList怎么存怎么取数据,Vector也是。都是通过size获取有效长度。
  • 底层都是数组结构。

区别:

  • Arraylist内部原理是默认情况下创建一个长度10的数组,Vector也是,但ArrayList每次扩容1.5倍,Vector可指定扩容长度。
  • Vector与Collection实现不同,Vector是同步的,即是单线程,所以速度慢
  • vector扩容算法和arraylist基本一样,只是在计算新长度时做了判断,若增量<0,则新长度为两倍旧长度,若增量>0则新长度为(旧长度+增量)
Vector<Integer> v = new Vector<>();
v.add(100);
Vector​(int initialCapacity, int capacityIncrement) 构造具有指定初始容量和容量增量的空向量。 
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                          capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)? newCapacity: hugeCapacity(minCapacity);
    }

37.LinkedList 实现原理及使用

  • LinkedList是一个双向链表,查询慢,增删快,
  • LinkedList提供大量首尾操作的方法,通过addFirst(E e),addLast(E e),removeFirst(),removeLast()可模拟队列操作,通过push(E e),pop()模拟模拟堆栈操作

38.Iterator原理和使用

  • 通用的取出集合中所有元素的一个接口,适用于任何集合。Iterator主要用于迭代访问(即遍历)Collection集合元素

原理

  • 当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象
  • 然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
  • Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,接下来通过一个图例来演示Iterator对象迭代元素的过程。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

使用

  • 1.通过Collection集合中的方法iterator()获取迭代器的实现类对象,并使用Iterator接口接收。
  • 2.使用Iterator接口中的方法hasNext判断还有没有下一个元素。
  • 3.使用Iterator接口中的方法next取出集合中的下一个元素。
  • 4.使用Iterator接口中的方法remove移除元素

39.Set接口

  • Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
  • Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法使用循环输出。
  • 能通过循环的方式将 Set 接口中的内容输出,在 Collection 接口中定义了将集合变为对象数组进行输出Object obj[] = all.toArray(); // 将集合变为对象数组或者
  • 在此接口中有两个常用的子类:HashSet、TreeSet

40.TreeSet与HashSet区别

  • HashSet是散列存储无序,TreeSet是二叉树有序存储,这个有序不是存储有序而是根据排序规则进行排序,自定义元素需要实现comparable接口重写compartor方法定义自己排序规则。
  • 关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。
  • 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。 不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。

41.Map、TreeMap、HashMap特点

  • Map集合是一个双列集合,key是不允许重复的(key唯一),value可以重复
  • TreeMap 是Map子类,允许 key 自定义排序,其本身在操作时候将按照 key 进行排序,另外,key 存储自定义类型对象,要求对象所在的类必须实现 Comparable 接口是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类 只需要知道其特点即可。
  • HashMap一个无序的集合,key唯一。

42.HashMap和Hashtable区别

  • Hashtable:底层也是一个哈希表,是一个线程安全(单线程)的集合,因此速度慢。
  • HashMap:   底层是一个哈希表(数组+链表/红黑树),是一个线程不安全(多线程)的集合,因此速度快。

43.哈希表如何存储元素

  • 哈希表由数组+链表/红黑树组成,哈希表初始桶数量是16(即数组长度),散列因子(加载因子)是0.75(可自己设置),当桶的使用量达到75%,则对桶进行扩容2倍(就是数组扩容2倍),此时就是对32取余.
  • 首先通过hashCode方法获取元素哈希值(int值),然后对16取余,余数作为数组索引,然后将元素存储对应位置中。
  • 当出现哈希冲突(即哈希值取余结果相同),java采用链表和红黑树处理这个问题
  • 当哈希桶中数据>8,则链表转换为红黑二叉树。
  • 当哈希桶中数据减少到6,则红黑二叉树转换为链表。
  • 若现在哈希桶中已知数据是7个,当减少数据后,不一定发生红黑树转换为链表,因为哈希桶可能本身就是链表。

44.HashMap源码分析

1.默认加载因子0.75

  • 构造方法中设置默认的加载因子0.75
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; }
static final float DEFAULT_LOAD_FACTOR = 0.75f;

2.初始桶数量16

  • 1左移4位即1乘以2的4次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

3.put方法存元素具体实现

  • 先计算key的哈希值,然后将键和值作为参数传到putVal方法中存储元素
 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
  • 下面是putVal方法存储数据具体实现

  • 1.首先调用put方法传入键值对,然后通过hash算法得到hash值,也就是下面代码
 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
  • 然后先判断(节点)数组table是否为Null或长度为0,是则调用resize()方法对table扩容。这里tab替代table,n表示扩容后长度。若不是则table没问题可存储数据。
 if ((tab = table) == null || (n = tab.length) == 0)  n = (tab = resize()).length;
  • 既然table没问题,就可以通过hash值取余找到table下标,判断该位置是否已有元素(节点),没有则将元素以节点形式存入table,然后增加修改次数modcount++,然后对临界点进行比较,若达到散列的75%则调用resize扩容,否则结束。
if ((p=tab[i = (n - 1) & hash]) == null) tab[i]=newNode(hash, key, value, null);

若通过hash值找到的位置已经有元素,那么判断传入的key是否存在(重复),存在则新值覆盖旧值,若不存在则判断该table[i]节点是否是treeNode树节点,是则存入红黑二叉树,不是(即链表节点)则遍历链表,若链表长度>=8,则将该节点插入红黑树中,否则插入链表中。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;

        //判断table是否正常
        if ((tab = table) == null || (n = tab.length) == 0)  n = (tab = resize()).length;

        //table可存,通过hash值取余确定的位置为空,则直接添加节点元素
        if ((p=tab[i =(n - 1)& hash]) == null) {tab[i]=newNode(hash, key, value, null);}
        //若确定的位置已经有元素
        else {
            Node<K,V> e; K k;
            //若table中取出的元素p的hash和待插入的hash相同并且key也相同,就出现key重复问题,
            //将p赋值给e,然后就跳过了下面两个else,执行 if (e != null) 语句,e不空即代表
            //table取出的元素p和待插入的元素key重复,所以进行value覆盖;若e为null则key不重复
            if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))){
                e = p;
            }else if (p instanceof TreeNode){
                //若key不重复(即元素不重复),则判断table取出的元素若是树节点则存入红黑树
                //若待存元素和红黑树中元素相同,则返回给e然后去覆盖,否则e还是null.
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            } else {
                //若长度<8,则应存入链表中,先遍历链表,每一次遍历都要验证key是否重复,
                //重复则直接跳出循环去执行if (e != null) 语句进行value覆盖,若不重复
                //直接找到最后一个节点然后将待插节点插入其后。
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //当节点数>=(8-1)则转入二叉树
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            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;
    }

45.HashMap、Hashtable、ConcurrentHashMap区别

  • HashMap线程不安全、但效率高(多人同时做同一件事(存取删),若存在共享数据则出现线程安全问题)
  • Hashtable线程安全、效率低。(多人排队做一件事情)。
  • ConcurrentHashMap采用分段锁机制,保证线程安全,效率较高。即锁住的范围只是一个桶,其他人可以对其它桶操作。Hashtable锁住的是整个集合。

46.散列表散列操作

  • 影响HashMap性能的参数:初始容量、负载(散列)因子。
  • 若初始容量给的小,但存储的数据很多,哈希表就会频繁的散列,即重建内部数据结构即重新建立2倍大小哈希表,将原数据再一一存进去,这样导致最后存储数据次数是原来多倍,造成性能浪费。
  • 加载因子若给的小,则占用大量内存空间但效率高,若过大则越节省空间但查询效率过低。

47.哈希值错乱问题

  • 当一个对象作为哈希表的键存储时,这个对象就不要进行修改,不然重新计算的哈希值与原来哈希值会不一样,就找不到原数据,造成内存泄漏。
  • 只要是以哈希表存储元素,被计算哈希值的字段内容就不要修改,若一定要修改,这个字段就不要放在键的位置,当然这是对map来说,若是对set来说只能存单列,是一定不能修改的。
  • 比如当一个对象被存进 HashSet 集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈 希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为 的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中删除当前对象,从而 造成内存泄露。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值