【JAVA重点知识汇总】(包含Java基础、JVM、Java并发)



Java、大数据开发学习要点(持续更新中…)


一、JAVA重点知识汇总

String的不可变性

1. String的不可变性(ps:通过反射可以改变)

  • String类被final修饰,保证类不被继承。
  • String内部char[] value设置为private,并且用final修饰符修饰,保证成员变量初始化后不被修改。
  • 不提供setter方法改变成员变量,即避免外部通过其他接口修改String的值。
  • 通过构造器初始化char[] value时,对传入对象进行深拷贝(deep copy),避免用户在String类以外通过改变这个对象的引用来改变其内部的值。
  • 在getter方法中,不直接返回对象引用,而是返回对象的深拷贝,防止对象外泄。

2. String的不可变性的好处

  • 满足字符串常量池的需要(有助于共享)。如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是 不可变的,才可能使用 String Pool。
  • 线程安全考虑
  • 支持hash映射和缓存。因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。

3. 字符串的一些问题思考

  • new String(“aaa”)、s.intern()、String s = "aaa"的区别

    String s1 = new String("aaa");
    String s2 = new String("aaa");
    System.out.println(s1 == s2);   // false,指向堆内不同引用
    String s3 = s1.intern();
    String s4 = s1.intern();
    System.out.println(s3 == s4);  // true,指向字符串常量池中相同引用
    String s5 = "bbb";
    String s6 = "bbb";
    System.out.println(s5 == s6);  // true,指向字符串常量池中相同引用
    

    只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中而是在堆中非字符串常量池中存储。

  • 字符串拼接

    • "+"拼接:加号拼接字符串jvm底层其实是调用StringBuilder来实现的,但并不是说直接用“+”号拼接就可以达到StringBuilder的效率了,因为每次使用 "+"拼接都会新建一个StringBuilder对象,并且最后toString()方法还会生成一个String对象。在循环拼接次数较大时候,就会生成大量StringBuilder对象,会产生大量内存消耗。
    • concat拼接:申请一个char类型的buf数组,将需要拼接的字符串都放在这个数组里,最后再创建并返回一个新的String对象。
  • String str = new String(“abc”)创建了几个对象?(两个对象)

    一个是编译时期在字符串常量池中的"abc",另一个是运行时堆中(非常量池)的String对象。

StringBuilder & StringBuffer

  在字符串修改/拼接时,String 是不可变的对象, 因此在每次对 String 类型进行改变的时候, 都会生成一个新的 String 对象,然后将指针指向新的 String 对象。不仅效率低下,还会大量浪费内存空间。
  而使用 StringBuffer/StringBuilder 类时,每次都会对 StringBuffer/StringBuilder 对象本身中的char[]进行修改操作,而不产生新的未使用对象。

  • 当字符串修改较少的情况下,建议使用String str = 'hello'来创建字符串
  • 当字符串修改较多的情况下,建议使用StringBuilder,在多线程的场景下建议使用StringBuffer(方法都通过synchronized来修饰保证并发修改的数据安全性)

"=="和equals的区别

  1. "=="的比较
  • 基本数据类型用比较的是两个数据的值是否相等。
  • 引用类型(类、接口、数组)用比较的是它们在内存中的存放地址是否相等(两个变量是否引用同一个对象)。

对象是存放在堆中的,栈中存放的是对象的引用(地址),所以直接对对象引用比较是在比较对象的栈中的值。如果要比较堆中对象的内容是否相同,那么就要重写equals方法了。

  1. equals的比较

    Object的equals(),源码实现中是对象地址的比较。通常,我们需要比较的是对象中的值,所以需要重写equals(),以让其按照我们想要的逻辑进行对象的比较(要注意传入对象类型的判断)。
    ps:基本数据类型的包装类,在赋值、运算的时候会进行自动装箱和拆箱,直接进行==比较就是比较的包装对象的地址值

Object.hashCode()

  hashCode方法返回一个hash码(int),主要作用是在对对象进行散列时作为key输入,因此需要每个对象的hashCode尽可能不同,这样才能保证散列的存取性能。事实上,Object类提供的默认实现确保每个对象的hash码不同(在对象的内存地址基础上经过特定算法返回一个hashCode)
  hashCode用于配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap 以及 HashTable。散列集合中元素不可重复,Java则依据元素的hashCode来判断两个元素是否重复。当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了(放入对象的 hashcode与集合中任一元素的hashcode不相等);如果这个位置上已经有元素了(hashcode相等),就调用它的equals方法与新元素进行比较,相同的话就不存,不相同使用一定方法来解决hash冲突问题(经典的如链地址法)。

  • 散列中如何判断对象是否相等?
    //哈希值相等 && (同一个对象 || equals为true)
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {... }
    1. 等价的两个对象散列值一定相同
    2. 散列值相同的两个对象不一定等价(hash冲突)

Java的深浅拷贝

  Java的深浅拷贝都属于对象拷贝。在对对象进行拷贝时,如果只对基本数据类型进行拷贝,而对引用数据类型进行了引用传递,则这个对象拷贝是浅拷贝;反之,在对对象的引用数据类型进行拷贝时,是通过创建了一个与原对象相同的新对象则称为深拷贝
  Objoct.clone()默认实现是浅拷贝,那么如何实现深拷贝?

  1. 重写clone()方法,在引用对象克隆时也调用其clone()方法,要求引用对象的clone方法也是深拷贝的。
  2. 将对象序列化,再反序列化得到一个与之相同的新对象(Serializable、解析成JSON等)。

多态与重载重写

											Java三大特性:封装、继承、多态

多态的体现分为编译时多态运行时多态

重载是相对于同一个类来说的,同一个类中可以存在多个同名但是参数列表不同(返回值不参与)的函数,是Java编译时多态的体现。
重写是相对于继承的父类和子类来说的,子类通过拥有同名、同参数列表、同返回类型的函数来重写父类的方法,是Java运行时多态的体现。

方法调用的多态,是在类解析期间将符号引用转换成直接引用的过程中,在编译时期是静态分派的,调用的是引用类型中相同参数列表的对应方法(重载方法);而在运行时期,又会进行动态分派,根据实例对象中来确定调用哪个重写方法。

几个重要的关键字

  1. final修饰变量则初始化后不可修改,修饰类变量则需要直接初始化,修饰实例变量则需要直接初始化或者在构造函数中初始化。修饰方法则方法不能被子类重写修饰类则该类不能被继承。ps:final修饰的变量还有保证变量可见性的作用。
  2. finally用于异常处理,finally结构中的代码无论异常是否发生一定会被执行,一般用于关闭连接资源。
  3. finalize:是Object类中的方法,在可达性计算后对象不可达,则对象会被加入F-Queue中准备调用一次finalize方法(此前未调用过且finalize方法被重写过),来执行对象回收前的必要清理工作或进行自救
  • static修饰字段和方法分别表示是类变量和类方法,存放在元空间中,是线程共享的。static还可以用于静态代码块,仅在类加载的时候执行其中的代码一次,可以用于类的初始化。static还可以用于静态内部类,外部可以不创建外部类的实例就可访问静态内部类中的字段和方法。

HashMap内部原理

											此问题资料太多了,可以自行查阅并总结。
  • 集合遍历删除、添加多个元素的情况下:若不注意往往容易出现问题,出现 ConcurrentModificationException(并发异常,快速失败)。
  • 而线程安全的集合遍历采用的是CopyOnWrite的策略,并发下的数据增删操作和遍历的不是同一个集合,因此是并发安全的(安全失败)。

Java IO和NIO的区别

NIO 与普通 I/O 的区别主要有以下两点:

  1. NIO是非阻塞式IO,IO是阻塞式IO

    操作系统中介绍过,read()系统调用分为两个阶段 等待数据准备将数据从内核拷贝到进程中

    Java IO的各种流是阻塞的。这意味着,当一个线程进行read()或write()系统调用时,该线程两个阶段都被阻塞,直到数据从内核缓冲区中拷贝到进程中;而非阻塞式IO,在发送IO相关系统调用后第一阶段是非阻塞的可以去做自己事情,但需要不断轮询内核IO是否完成。

  2. NIO面向数据块,IO面向数据流

    IO以流的方式处理数据,每次从流中读取一个或者多个字节,数据不进行缓存。而NIO通过数据缓冲区,以数据块的形式进行读取。

  NIO 实现了 IO 多路复用中的 Reactor 模型,即一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,线程就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。由于创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好的性能

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值