java基础知识(一)

java基础知识(一),第1张

java基础知识(一) List和Set的区别

List:是有序的,按照元素进入顺序存储,可以与多个null元素,元素之间是可以重复的;在访问的时候,可以通过迭代器iterator和get(int index)进行访问。

Set:是无序的 ,元素之间不可以重复,最多只能有一个Null元素,只能通过迭代器Iterator进行访问。

HashCode和equals

HashCode ()的作用是获取哈希值,即散列码,会返回一个int型的整数。散列码对应的是对象在散列表中存储的索引地址;HashCode定义在JDK的Object.java中,任何类中都含有hashcode方法。Hashcode的存储形式是(K-V),能够很快的通过键找到对应的值,那就可以迅速找到对应的对象。

为什么要有HashCode呢

以HashSet如何检查重复为例子来 说明为什么要有hashcode

对象加入 HashSet时,会计算对象的Hashcode来决定对象加入的位置,看该位置是否有值,,如果没有,就说明没有重复的对象出现;如果有,这时会调用equals()方法来检查这两个对象是否真的相同。如果相同,那么HashSet就不会让其加入。如果不相同的话,就会重新散列到其他位置。这样就大大减少了equals的次数。

  • 如果两个对象相等,那么它的hashcode值一定相等;
  • 如果两个对象相等,那么对两个对象调用equals返回结果为true;
  • 两个对象有相同的hashcode值,那么它们也不一定时相等的。
  • 因此,equals方法被覆盖过,则hashcode方法也必须被覆盖;
  • hashcode()的默认行为是对堆上的对象产生独特值,如果没有重写hashcode,则class的两个对象无论如何也不会相等(即使这两个对象具有相同的数据)。
为什么equals方法不相等而HashCode方法返回的值却有可能相同呢?

Hashcode方法实际上是通过一种 算法得到一个对象的hash码,这个hash码是用来确定对象在哈希表中的具体的存储区域的,返回的hash码是Int来行的所以范围 有限,而超过这个范围,实际上会产生溢出,溢出之后的值实际在计算机中存的也是这个范围的。在Java中对象可以有很多很多通过New关键字来产生,而hash码也是通过特定算法得到的,所以很难或者说几乎没有算法在这个范围内在这个情况下不会不产生相同的hash码的,也就是说在上述情况下肯定是会产生哈希碰撞的,因此不同对象可能有相同的hashcode返回值。也有人说Object方法中的hashcode方法是通过内存地址得来的,是唯一的。可是hashcode方法是公有的,也就意味着它是可以被程序员重写的,因此不同环境下实现hashcode的算法可能不同。因此equals方法返回不相等,而hashcode方法返回的值却有可能相同!

ArrayList和linkedList区别

ArrayList基于动态数组,连续内存存储,适合下标访问(随机访问);扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将数组的数据拷贝到新数组,如果不是尾部插入数据还涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能,甚至超过linklist(需要创建大量的node对象)

linkList:基于链表,可以存储在分散的内存中,适合做数据插入及删除 *** 作,不适合做查询:需要逐一遍历linkList必须使用迭代器不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。

HashMap和HashTable的区别是什么?底层实现是什么?

区别:HashMap方法没有synchronized修饰,线程非常安全,HashTable线程安全。

HashMap允许key和value值为null,而HashTable不允许;

底层实现:数组+链表

jdk8开始链表高度到8,数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在

  • 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
  • 如果没有产生hash冲突(下标位置没有元素),则直接创建node存入数组;
  • 如果产生hash冲突,先进性equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度达到64时转变为红黑树,长度低于6则将 红黑树转会链表。
  • key为null,存在下标为0的位置。
如何实现一个IOC容器
  1. 配置文件配置包扫描路径
  2. 递归包扫描获取 .class文件
  3. 反射、确定需要交给IOC管理的类
  4. 对需要注入的类进行依赖注入
  • 配置文件中指定需要扫描的包路径
  • 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解。
  • 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储。
  • 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象;
  • 遍历这个IOC容器,获取到每一个类的实例,判断里面是否有依赖其他的类的实例,然后及逆行递归注入。
什么是字节码?采用字节码的好处是什么?

Java中的编译器和解释器:

Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器,这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口。

编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

每一种平台的解释器是不同的,但是实现的虚拟机是相同的,Java源程序经过经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定机器上运行。这也就解释了java的编译和解释并存的特点。

  • 采用字节码的好处

    java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无需重新编译便可在多种不同的计算机上运行。

Java类加载器有哪些

Java类加载器包括:bootstrap.ClassLoader(引导类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(系统类加载器)。依次前者是后者的父类加载器,自定义类加载器继承ClassLoader。

java中的异常体系

Java中的所有异常都来自顶级父类Throwable,Throwable下有两个子类Exception和Error。

Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。

Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。

RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败 。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

GC如何判断对象可以被回收
  • 引用计数法:每个对象都有一个引用计数属性,新增一个引用的属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。

    问题:引用计数法,可能会出现A引用了B,B又引用了A,这时候就算他们都不再使用了,但因为相互引用计数器等于1永远无法回收。

  • 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的,那么虚拟机就判断是可回收对象。

    GC Roots的对象有:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象;
    • 本地方法栈中JNI(即一般说的Native对象)引用的对象。

    可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会,对象被系统宣告死亡至少要经历两次标记过程;第一次经过可达性分析发现没有与GC Roots相连接 的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

    当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize()方法。若未覆盖,则将其直接回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列。由一低优先级线程执行该队列中对象的finalize()方法。执行finalize()完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收;否则,对象“复活”。

    每个对象只能触发一次finalize()方法。

    由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用。

线程的生命周期,线程有哪些状态
  1. 线程通常有5种状态:创建、就绪、运行、阻塞和死亡

  2. 阻塞的情况又分为三种:

    • 等待阻塞:运行中的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll才能唤醒,wait是object类的方法。
    • 同步阻塞:运行中的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则VM会把该线程放入“锁池”中。
    • 其他阻塞:运行的线程执行sleep或Join方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep状态超时,join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  3. 新建状态(New):新创建一个线程对象;

  4. 就绪状态(Runable):线程创建对象后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待CPU的使用权。

  5. 运行状态(Running):就绪状态的线程获取到了CPU,执行程序代码;

  6. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行,知道线程进入就绪状态,才有机会转到运行状态。

  7. 死亡状态(Dead):线程执行完了或者因为异常退出了run方法,该线程结束生命周期。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5582684.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存