Java基础笔记

HashMap

  • 特点:无序、非线程安全、基于数组和链表实现、允许key、value为null

  • 容量和负载因子

    capacity : 容量,初始大小16。table数组的长度

    load_factor : 负载因子,默认0.75

    当size>= capacity * load_factor 时数组进行扩容,新的table长度为之前的2倍。

  • jdk 1.8之后,链表长度超过8会转为红黑树,优化查找效率。

    Java基础笔记_第1张图片

LinkedHashMap

  • 特点:继承自HashMap、有序,双向链表

  • 读取顺序:accessOrder=false时按插入顺序读取,当accessOrder=true时按访问顺序读取

  • 扩展了HashMap的Entry,增加了两个属性:Entry before, after 指向前后节点。

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    

ConcurrentHashMap

  • 同样是线程安全

  • JDK 1.7实现

    是由 Segment 数组、HashEntry 数组组成,和 HashMap 一样,仍然是数组加链表组成。

    采用分段锁技术,每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment

    Java基础笔记_第2张图片(https://postimg.cc/PLh8L8SF)

  • JDK 1.8实现

    抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

    也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。

    Java基础笔记_第3张图片(https://postimg.cc/svsYQq1J)

HashTable

  • 不能存储null的key和value,底层数组和链表实现
  • 线程安全,使用synchronized来实现线程安全。HashTable只有一把锁,只能允许一个线程进行操作,效率低。如果两个线程同时想get数据,并不能实现,只能一个线程get,其他等待。
  • 效率低,推荐使用ConcurrentHashMap

ArrayList LinkedList

  • ArrayList: 基于数组实现,非线程安全。
    当数组大小不足时增长率为当前长度的50%。
    使用索引在数组中搜索和读取数据是很快的

  • LinkedList: 基于双向链表实现,非线程安全。
    插入,添加,删除操作速度更快。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

ThreadLocal

  • ThreadLocal提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程间的数据隔离~。

  • 原理

    1. 每个Thread维护着一个ThreadLocalMap的引用
    2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
    3. 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
    4. 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
    5. ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value
  • 为什么Entry的key为ThreadLocal的弱引用?

    当threadLocal不使用后,将其置为null,由于threadLocal没有强引用指向,会顺利倍GC回收。

    但是如果这里不是WeakReference,下图虚线是个强引用的话,虽然threadLocal置为了null,但是threadLocal就会因为和entry存在强应用无法被回收,造成内存泄漏,除非线程结束,线程被回收了,threadLocalMap也跟着回收。

  • Java基础笔记_第4张图片

参考 https://blog.csdn.net/levena/article/details/78027136

​ https://blog.csdn.net/qq_42862882/article/details/89820017

Java泛型

  • 语法糖

指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

  • 泛型的目的

Java 泛型就是把一种语法糖,通过泛型使得在编译阶段完成一些类型转换的工作,避免在运行时强制类型转换而出现 ClassCastException,即类型转换异常。

  • 泛型的好处

1.类型安全。使得程序在编译期间就能对类型错误进行捕获
2.消除了代码中的许多强制类型转换(因为使用泛型,编译器在编译时会做类型转换)
3.增加代码可靠性、可读性

  • 通配符
A > B > C > D > E

1.<?>未知类型通配符 :因为类型未知,所以不能写入数据,但是可以用Object接收数据,因为肯定是Object或其子类。

2.<? extends T> 上限通配符

List<? extends C> list = new ArrayList<E>()new ArrayList<C>()new ArrayList<D>();

list不能插入数据,比如如果插入E,list有可能可能是 List<C>类型
但是可以读取数据,因为数据肯定是C或C的子类。

3.<? super T> 下限通配符

List<? super C> list = new ArrayList<A>()new ArrayList<B>()new ArrayList<C>();

list可以插入数据,类型为C或C的子类。
但是不能读取数据,比如要去A类型数据,有可能list是 List<B> 类型   
  • PECS 原则: Producer Extends, Consumer Super

Java深拷贝和浅拷贝

  • 浅拷贝(Shallow Copy):
  1. 对于基本数据类型,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
  2. 对于引用类型,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
  • 深拷贝(deep copy):
    对于深拷贝来说,不仅要复制对象的所有基本数据类型,还要为所有引用类型的成员变量申请存储空间,并复制每个引用类型所引用的对象,直到该对象可达的所有对象。

  • 延迟拷贝:
    延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。 当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。

equals和==的区别?

  • equals 没有被重写的情况下,和==等价,比较的就是两个对象在栈中的引用(地址)

  • hashcode 直接返回对象的内存地址

  • == 如果是基本类型,比较的是值;如果是对象,比较的是对象在栈中的引用,如果要比较对象的在堆中的内容,则需要重写equals方法。

Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同。上面说的对象相同指的是用eqauls方法比较。

JVM运行时内存数据模型

  • 方法区 :属于线程共享区域

    存放已经被虚拟机加载的类信息,如常量,静态变量。 这块区域也被称为永久代

  • 堆 :属于线程共享区域

    堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配。

    这块区域也是垃圾回收器重点管理的区域,由于大多数垃圾回收器都采用分代回收算法,所有堆内存也分为 新生代老年代,可以方便垃圾的准确回收。

  • 虚拟机栈

    虚拟机栈由一个一个的栈帧组成,栈帧是在每一个方法调用时产生的。

    每一个栈帧由局部变量区操作数栈等组成。每创建一个栈帧压栈,当一个方法执行完毕之后则出栈。

  • 本地方法栈

    与虚拟机栈所发挥的作用相似。它们之间的区别不过是虚拟机栈为虚拟机执行java方法,而本地方法栈为虚拟机使用到的Native方法服务。

  • 程序计数器

    记录当前线程所执行的字节码行号,用于获取下一条执行的字节码。

Java基础笔记_第5张图片

JVM的垃圾回收机制

  • 哪种对象需要回收?

    有两种方法判断

    • 引用计数法
    • 可达性分析法
  • 怎么回收(回收算法)?

    [1] 标记-清除算法

    标记清除算法分为两个步骤,标记和清除。 首先将不需要回收的对象标记起来,然后再清除其余可回收对象。

    效率不高,并且容易造成内存不连续(内存碎片问题)。

    [2] 复制算法

    复制算法是将内存划分为两块大小相等的区域,每次使用时都只用其中一块区域,当发生垃圾回收时会将存活的对象全部复制到未使用的区域,然后对之前的区域进行全部回收。

    简单高效,不会造成内存碎片问题,但是浪费内存。

    [3] 标记-整理算法

    复制算法如果在存活对象较多时效率明显会降低,特别是在老年代中并没有多余的内存区域可以提供内存担保。

    所以老年代中使用的时候标记整理算法,它的原理和标记清除算法类似,只是最后一步的清除改为了将存活对象全部移动到一端,然后再将边界之外的内存全部回收。

    [4] 分代回收算法

    现代多数的商用 JVM 的垃圾收集器都是采用的分代回收算法,和之前所提到的算法并没有新的内容。

    只是将 Java 堆分为了新生代和老年代。由于新生代中存活对象较少,所以采用复制算法,简单高效。

    而老年代中对象较多,并且没有可以担保的内存区域,所以一般采用标记清除或者是标记整理算法

你可能感兴趣的:(面试)