Java各类技能知识点学习链接大全:一、Java基础

本文深入探讨Java中的关键概念和技术,包括Lombok的优缺点、Java String类的设计、动态代理原理、wait()和notify()的使用、Java IO模型、NIO浅析、ConcurrentModificationException异常分析、ThreadLocal内存泄漏问题、HashMap内部机制、CopyOnWriteArrayList原理等,旨在帮助开发者全面掌握Java核心技术。

以下内容大多是学习链接,他人整理,个人收藏以便复习,同时归纳分享出来(如有不妥,原作者可随时联系本人删除,感谢!)

一、Java基础

1、Java使用Lombok的优缺点:

新来个技术总监,禁止我们使用Lombok!_HollisChuang's Blog-CSDN博客

2、Java String类为什么是final的?

Java String类为什么是final的? - 简书

3、jdk8动态代理源码:

JDK动态代理-超详细源码分析 - 简书

4、动态代理为什么是面向接口的?

动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的$Proxy0.class文件发现,

生成的代理类,必须要继承Proxy类,而且java是单继承,所以只能实现接口了,所以只能面向接口:

public final class $Proxy0 extends Proxy implements Interface {
public $Proxy0(InvocationHandler paramInvocationHandler) { 
super(paramInvocationHandler); 
}

jdk的动态代理及为什么需要接口_zxysshgood的博客-CSDN博客_jdk动态代理为什么必须实现接口

5、为什么wait()和notify()需要搭配synchonized关键字使用

(假设没有应用Synchronized关键字,当消费者线程执行wait操作的同时,生产线程如果先执行notify,生产者线程可能在等待队列中找不到消费者线程。导致消费者线程一直处于wait阻塞状态。那么这个模型就要失败了。所以必须要加Synchronized关键字。 )

阿里巴巴面试题: 为什么wait()和notify()需要搭配synchonized关键字使用_萧萧的专栏-CSDN博客

6、五种IO模式

漫话:如何给女朋友解释什么是Linux的五种IO模型?

7、Java NIO 浅析

Java NIO浅析 - 美团技术团队(美团技术博客)

https://zhuanlan.zhihu.com/p/83597838

几种IO对比:Java面试常考的 BIO,NIO,AIO 总结_小树的博客-CSDN博客_bio nio(附代码demo)

8、Java ConcurrentModificationException异常原因和解决方法

(1)、   for each循环,删除list元素,报错问题  ConcurrentModificationException,  原来for each 循环还是利用了迭代器来遍历集合的

(Map也会出现类似这个ConcurrentModificationException问题)

  final void checkForComodification() {
            if  (modCount  != expectedModCount)
             throw new ConcurrentModificationException();

 如下,deleteMethod2不会报错,但是deleteMethod3会报错:

    private static void deleteMethod2(List<String> list) {
        for(Iterator<String> it=list.iterator();it.hasNext();){
            String str=it.next();
            if("bbb".equals(str)){
                it.remove();
            }
        }
    }

    private static void deleteMethod3(List<String> list) {
        for (String str : list) {
            System.out.println(str);
            if("bbb".equals(str)){
                list.remove(str);
            }
        }
    }

(2)、这个快速失败的意思是无论当前是否有并发的情况或问题,只要发现了修改读取不一致就抛异常;

       如果是ArrayList遍历读取时不加锁,这时其他线程修改了ArrayList(增加或删除),

       会抛出ConcurrentModificationException,这就是failfast机制, 通过fastfail来保证集合的正确性;

     (我们这里只讨论Iterator遍历,如果是普通for循环可能会数组越界)

(3)、 多线程解决方案:  

CopyOnWriteArrayList 调用iterator时生成的是一个新的数组快照,遍历时读取的是快照,所以永远不会报错(即使读取后修改了列表),并且在CopyOnWriteArrayList是没有fastfail机制的,原因就在于Iterator的快照实现以及CopyOnWrite已经不需要通过fastfail来保证集合的正确性

CopyOnWriteArrayList的CopyOnWrite即修改数组集合时,会重新创建一个数组并对新数据进行调整,调整完成后将新的数组赋值给老的数组

java集合中删除元素问题 - 知乎

Java ConcurrentModificationException异常原因和解决方法 - Matrix海子 - 博客园

9、Java中StringBuffer的缓冲区为什么是16?

Java中StringBuffer的缓冲区为什么是16? - 知乎

10、WeakHashMap工作原理

https://www.haoid.cn/post/13124

11、ThreadLocal详解,和使用可能出现的问题:

(1)、Threadlocal为什么会存在内存泄露:

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。

但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。

来自:ThreadLocal的内存泄露?什么原因?如何避免? - 知乎 (zhihu.com)

(2)什么是弱引用:

WeakReference是Java语言规范中为了区别直接的对象引用(程序中通过构造函数声明出来的对象引用)而定义的另外一种引用关系。WeakReference标志性的特点是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象之外所有的对象解除引用后,该对象便可以被GC回收),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。

只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

(3)、为什么要用弱引用:

当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。多了一层保障,但是不能完全保证内存泄露问题;

(4)、ThreadLocal正确的使用方法:

ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。ThreadLocal不能使用基本数据类型,只能使用Object类型。
每次使用完ThreadLocal都调用它的remove()方法清除数据;

(5)、项目组应用场景:

a、spring的bean在默认是单例模式的时候,比如controller请求的时候,要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中;

b、使用ThreadLocal来解决SimpleDateFormat线程安全问题

public class TestThreadLocalSimpleDateFormat {

    public static void main(String[] args) {
        try {
            System.out.println(TestThreadLocalSimpleDateFormat.parse("2019-09-01 18:00:11"));
            System.out.println(TestThreadLocalSimpleDateFormat.format(new Date()));
        } catch (Exception e) {
        }
    }

    private static ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };

    public static Date parse(String dateStr) throws Exception{
        Date date = formatter.get().parse(dateStr);
        formatter.remove();
        return date;
    }

    public static String format(Date date) throws  Exception{
        String strDate = formatter.get().format(date);
        formatter.remove();
        return strDate;
    }

}

c、解决线程安全问题
在Spring的Web项目中,我们通常会将业务分为Controller层,Service层,Dao层, 我们都知道@Autowired注解默认使用单例模式,那么不同请求线程进来之后,由于Dao层使用单例,那么负责数据库连接的Connection也只有一个, 如果每个请求线程都去连接数据库,那么就会造成线程不安全的问题,Spring是如何解决这个问题的呢?

在Spring项目中Dao层中装配的Connection肯定是线程安全的,其解决方案就是采用ThreadLocal方法,当每个请求线程使用Connection的时候, 都会从ThreadLocal获取一次,如果为null,说明没有进行过数据库连接,连接后存入ThreadLocal中;

这样每个事务的上下文都应该是独立拥有数据库的connection连接的,否则在数据提交回滚过程中就会产生冲突。如此一来,每一个请求线程都保存有一份 自己的Connection。于是便解决了线程安全问题;

ThreadLocal在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。

详解:由浅入深,全面解析ThreadLocal_LeslieGuGu的博客-CSDN博客_.net threadlocal

视频:07-ThreadLocal的内部结构_哔哩哔哩_bilibili

12、TreeMap和TreeSet在排序时如何比较元素,Collections工具类中的sort()方法如何比较元素

https://www.cnblogs.com/qf123/p/8483744.html

(如果要实现TreeSet 或者TreeMap的 排序(或者说让一个TreeSet可用),必须让加入的对象具有可排序性,否则就会报错 java.lang.ClassCastException。)

13、图解LinkedHashMap原理

图解LinkedHashMap原理 - 简书

14、序列化和反序列化(美团技术博客)

序列化和反序列化 - 美团技术团队

15、如何实现 Array 和 List 之间的转换?
Array 转 List: Arrays. asList(array) ;

    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

List 转 Array:List 的 toArray() 方法。

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

16、数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList? - 割肉机 - 博客园

17、面经手册 · 第8篇《LinkedList插入速度比ArrayList快?你确定吗?》

面经手册 · 第8篇《LinkedList插入速度比ArrayList快?你确定吗?》 - 小傅哥 - 博客园

18、String,StringBuilder,StringBuffer 实现原理解析

 (StringBuffer内部用synchronized关键字控制线程安全,在toString()方法中讲字符串缓存到toStringCache提高访问性能)

String,StringBuilder,StringBuffer 实现原理解析 - 简书

19、Comparable与Comparator的区别:

两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码,

用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。

用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。

Comparable与Comparator的区别_小帅的专栏-CSDN博客_comparator和comparable的区别

20、Java中Native关键字的作用

https://www.cnblogs.com/KingIceMou/p/7239668.html

使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。

JAVA本地方法适用的情况 
(1)为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API访问

(2)为了访问一个老的系统或者使用一个已有的库,而这个系统或这个库不是用JAVA编写的

(3)为了加快程序的性能,而将一段时间敏感的代码作为本地方法实现。

21、寻找Java中String.split性能更好的方法

(跟StringUtils.split 对比)

https://segmentfault.com/a/1190000016901608

22、JDK11变化详解,JDK8升级JDK11详细指南

JDK11变化详解,JDK8升级JDK11详细指南 - 简书

23、Java8新特性(一)_interface中的static方法和default方法

Java8新特性(一)_interface中的static方法和default方法 - Kevin_zheng - 博客园

24、jdk8新语法Lambda表达式用处

Lambda 表达式有何用处?如何使用? - 知乎 (Mingqi回答

25、jdk8新特性

JDK1.8 新特性(全)_随波的博客-CSDN博客_jdk1.8的新特性有哪些

26、JDK8新特性:接口的静态方法和默认方法

JDK8新特性:接口的静态方法和默认方法_aty-CSDN博客_接口的静态方法

27、java 8的java.time包(非常值得推荐)

用过java1.8之前原生的日期处理api,你就会知道用起来非常麻烦,而且要注意的地方有点多

     (例如月份是由0开始,而且api使用有的不统一,线程不安全等等...),

     所以在java1.8之前的日期api都不值得去使用,虽然说现在都有强大的日期处理的第三方库,

     但是会有兼容性问题,那么现在jdk8有了新的时间处理类,为何不去尝试一下;

 * 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum
 * java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
 * java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
 * 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般开发人员还不一定能写对。
 

java 8的java.time包(非常值得推荐) - 简书 (jianshu.com)

28、ArrayList中elementData为什么被transient修饰?

ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。 
       为什么不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。 

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

ArrayList中elementData为什么被transient修饰?_ZERO-CSDN博客_arraylist transient

29、为什么 String hashCode 方法选择数字31作为乘子

为什么 String hashCode 方法选择数字31作为乘子?_Java笔记虾-CSDN博客

为什么 String hashCode 方法选择数字31作为乘子_JavaZWT的博客-CSDN博客(同上)

快来,我悄悄的给你说几个HashCode的破事。 - why技术 - 博客园提到,新老版本:

而为什么从 37 变成 31 ,作者在第二版里面解释了,也就是我用下划线标注的部分。31 有个很好的特许,即用位移和减法来代替乘法,可以得到更好的性能:

31*i==(i<<5)-1。现代的虚拟机可以自动完成这种优化。从 37 变成 31,一个简单的数字变化,就能带来性能的提升。

30、Integer

Integer中有一段静态内部类代码块,该部分内容会在Integer类被加载的时候就执行,当Integer被加载时,就新建了-128到127的所有数字并存放在Integer数组cache中。
再回到valueOf代码,可以得出结论。当调用valueOf方法(包括后面会提到的重载的参数类型包含String的valueOf方法)时,如果参数的值在-127到128之间,则直接从缓存中返回一个已经存在的对象。如果参数的值不在这个范围内,则new一个Integer对象返回。
所以,当把一个int变量转成Integer的时候(或者新建一个Integer的时候),建议使用valueOf方法来代替构造函数。或者直接使用Integer i = 100;编译器会转成Integer s = Integer.valueOf(100);    

String转成Integer时候,parseInt方法返回的是基本类型int,其他的方法返回的是Integer;

下面结果,第一个为true,第二个为true,第三个为false:

public class TestInteger {
    public static void main(String[] args) {
        //jdk1.5以后,自动装箱、拆箱功能。基本数据类型与包装数据类型转换
        //下面编译后是   Integer integer0 = Integer.valueOf(88),自动装箱;
        Integer integer0=88;
        //下面编译后是  int a = integer0.intValue()  自动拆箱;
        int a = integer0;
        Integer integer1 = Integer.valueOf(88);
        Integer integer2 = Integer.valueOf(88);
        Integer integer3 = Integer.valueOf(888);
        Integer integer4 = Integer.valueOf(888);
        //两个缓存值比较,true
        System.out.println(integer0==integer2);
        //两个缓存值比较,true
        System.out.println(integer1==integer2);
        //超过了 -128~127 缓存值范围,为false
        System.out.println(integer3==integer4);
    }
}

源码讲解,参考链接地址:java Integer 源码学习 - VinoZhu - 博客园

31、深入理解java异常处理机制

java(3)-深入理解java异常处理机制_黄规速博客:学如逆水行舟,不进则退-CSDN博客_java 异常类

结论:

(1)、finally覆盖catch(开头引子的例子):

      a)如果finally有return会覆盖catch里的throw,同样如果finally里有throw会覆盖catch里的return。

      b) 如果catch里和finally都有return, finally中的return会覆盖catch中的。throw也是如此。

(2)、catch有return而finally没有:

     当 try 中抛出异常且catch 中有 return 语句,finally 中没有 return 语句, java 先执行 catch 中非 return 语句,再执行 finally 语句,最后执行 catch 中 return 语句。

(3)、try有return语句,后续还有return语句,分为以下三种情况:

    情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。

    情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。

    情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:

        a)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。

        b)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。

32、java序列化:

Java对象的序列化(Serialization)和反序列化详解_明洋的专栏-CSDN博客_java序列化和反序列化

33、Java serialVersionUID 有什么作用?

serialVersionUID 就是控制版本是否兼容的,serialVersionUID 就是控制版本是否兼容的,若我们认为修改的 Object 是向后兼容的,则不修改 serialVersionUID;反之,则提高 serialVersionUID的值,这时候再反序列化就会报错,提升版本不兼容!

Java serialVersionUID 有什么作用? - 简书

34、java Logger日志使用占位符打印

java Logger日志使用占位符打印_感冒石头的博客-CSDN博客_java logger 占位符

浅谈LOG日志的写法_xiangnideshen的专栏-CSDN博客_日志log

优先使用参数,减少字符串拼接,提高性能;

打印日志:
logger.info("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", "1", "2", "3", "4");
执行后:
线程ID: 1, 方法描述: 2, 调用参数: 3, 返回结果: 4

35、SimpleDateFormat线程不安全及解决办法

SimpleDateFormat线程不安全及解决办法_默默前行,勿喜、勿悲、一切随缘!-CSDN博客_simpledateformat线程不安全

36、synchronized方法抛出异常后,会释放锁吗

方法抛出异常后,会释放锁(synchronized)_绅士jiejie的博客-CSDN博客

37、sleep() 和 wait() 的区别:

调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁

sleep() 和 wait() 的区别_yinhuanxu-CSDN博客_sleep和wait

38、ArrayList、LinkedList 源码:

Arraylist:底层是基于动态数组,根据下表随机访问数组元素的效率高,向数组尾部添加元素的效率高;但是,删除数组中的数据以及向数组中间添加数据效率低,因为需要移动数组。例如最坏的情况是删除第一个数组元素,则需要将第2至第n个数组元素各向前移动一位。而之所以称为动态数组,是因为Arraylist在数组元素超过其容量大,Arraylist可以进行扩容(针对JDK1.8  数组扩容后的容量是扩容前的1.5倍),Arraylist源码中最大的数组容量是Integer.MAX_VALUE-8,对于空出的8位,目前解释是 :①存储Headerwords;②避免一些机器内存溢出,减少出错几率,所以少分配③最大还是能支持到Integer.MAX_VALUE(当Integer.MAX_VALUE-8依旧无法满足需求时)。

Linkedlist基于链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。
对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。 
对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

面试必备:ArrayList源码解析(JDK8)_zxt0601的博客-CSDN博客_arraylist源码解析

面试必备:LinkedList源码解析(JDK8)_zxt0601的博客-CSDN博客

39、char型变量中能不能存贮一个中文汉字?为什么?

* char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字, 
 * 所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在 unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。

 *补充 说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。(一个字节8bit)

面试题:问题:Java中,char型变量中能不能存储一个中文汉字,为什么?_绝地反击T的博客-CSDN博客_char型变量中能不能存储一个中文汉字

40、为什么重写equal()时为什么也得重写hashCode()

在对象比较时,重写equals不仅比较对象地址相等,还比较对象内容相等,重写hashcode确保相同对象的索引值相等(也就是键在哈希表中存放的位置)。如果两个相同的对象,只重写hashcode,哈希值也就是key在同一个位置了,但是这里把两个对象都存储了。只重写equals,确实存储会变成一个对象,但是hashcode计算出的索引就有可能是两个,导致一个对象在两个位置存储了。

为何重写equals方法就得重写hashCode方法 - 知乎 (zhihu.com)

Object中的equals方法:

    public boolean equals(Object obj) {
        return (this == obj);
    }

User对象,包含name和age字段,重写后如下

@Override
public boolean equals(Object obj) {  
        if(this == obj) {  
            return true;  
        }  
        if(null == obj) {  
            return false;  
        }  
        if(this.getClass() != obj.getClass()) {  
            return false;  
        }  

        User user = (User) obj;  
        if(this.name.equals(user.name)&&this.age == user.age) {  
            return true;  
        }  
        return false;  
    }  

@Override
    public int hashCode() {
        int hash = 17;
        hash = hash * 31 + getName().hashCode();
        hash = hash * 31 + getAge();
        return hash;
    }

41、CopyOnWriteArrayList的原理和使用方法

CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。保证同一时间只能有一个线程在添加或者删除元素;

优点:

(1).解决的开发工作中的多线程的并发问题。

缺点:

(1).内存占有问题:很明显,两个数组同时驻扎在内存中,如果实际应用中,数据比较多,而且比较大的情况下,占用内存会比较大,针对这个其实可以用ConcurrentHashMap来代替。

(2).数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器

CopyOnWriteArrayList的原理和使用方法_蹦跶的小码哥-CSDN博客_copyonwritearraylist原理


42、HashMap知识点与系列问题

(1)、源码通读:Java 8系列之重新认识HashMap(美团技术博客)

Java 8系列之重新认识HashMap - 美团技术团队

(2)、HashMap的负载因子为什么是0.75

https://segmentfault.com/a/1190000023308658

(3)、HashMap的hash值,存储下标位置是怎么计算的

jdk1.8:

计算hash值:优化了高位运算的算法,将 hashCode 的高 16 位与 hashCode本身 进行异或运算,主要是为了在 table 的 length 较小的时候,让高位也参与运算,并且不会有太大的开销,使得hash分别更均匀:

(在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或)

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

计算下标值:将计算出来的 hash 值与 (table.length - 1) 进行 & 运算,确定下标;我们本来首先想到的就是把 hash 值对 table 长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是模运算消耗还是比较大的,我们知道计算机比较快的运算为位运算,因此 JDK 团队对取模运算进行了优化,使用上面代码2的位与运算来代替模运算。这个方法非常巧妙,它通过 “(table.length -1) & hash” 来得到该对象的索引位置,这个优化是基于以下公式:x mod 2^n = x & (2^n - 1)。我们知道 HashMap 底层数组的长度总是 2 的 n 次方,并且取模运算为 “h mod table.length”,对应上面的公式,可以得到该运算等同于“hash & (table.length - 1)”。这是 HashMap 在速度上的优化,因为 & 比 % 具有更高的效率。

        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

(4)、为什么在JDK1.7的时候是先进行扩容后进行插入,而在JDK1.8的时候则是先插入后进行扩容的呢?

在JDK1.7中的话,是先进行扩容后进行插入的,就是当你发现你插入的桶是不是为空,如果不为空说明存在值就发生了hash冲突,那么就必须得扩容,但是如果不发生Hash冲突的话,说明当前桶是空的(后面并没有挂有链表),那就等到下一次发生Hash冲突的时候在进行扩容,但是当如果以后都没有发生hash冲突产生,那么就不会进行扩容了,减少了一次无用扩容,也减少了内存的使用

(5)、为什么在JDK1.8中进行对HashMap优化的时候,把链表转化为红黑树的阈值是8,而不是7或者不是20呢

如果选择6和8(如果链表小于等于6树还原转为链表,大于等于8转为树),中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
还有一点重要的就是由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值

JDK1.8引入红黑树大程度优化了HashMap的性能

(6)、哈希表如何解决Hash冲突?

(7)、为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键

(8)、jdk1.7和jdk1.8扩容时有什么区别

jdk1.8

在扩充HashMap的时候,因为容量是以前两倍,不需要像JDK1.7的实现那样重新计算hash,那么n-1的mask范围在高位多1bit,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”;

如果 e 的 hash 值与老表的容量进行位与运算为 0,则说明 e 节点扩容后的索引位置跟老表的索引位置一样;

如果 e 的 hash 值与老表的容量进行位与运算为 1,则说明 e 节点扩容后的索引位置为:老表的索引位置+oldCap;

扩容后,节点重 hash 为什么只可能分布在 “原索引位置” 与 “原索引 + oldCap 位置”;

扩容代码中,使用 e 节点的 hash 值跟 oldCap 进行位与运算,以此决定将节点分布到 “原索引位置” 或者 “原索引 + oldCap 位置” 上;

这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点;

jdk1.7

扩容过程中会将原来的数据,放入到新的数组中,但是会重新计算hash值进行分配,通过key值的hash值和新数组的大小算出在当前数组中的存放位置;

数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这个操作是极其消耗性能的。所以如果我们已经预知HashMap中元素的个数,那么预设初始容量能够有效的提高HashMap的性能。

扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

(9)、HashMap为什么容量是2的次方

HashMap的容量为什么是2的n次幂,和这个(n - 1) & hash的计算方法有着千丝万缕的关系,符号&是按位与的计算,这是位运算,计算机能直接运算,特别高效,按位与&的计算方法是,只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞;

HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,降低查询效率!

HashMap初始容量为什么是2的n次幂及扩容为什么是2倍的形式_Apeopl的博客-CSDN博客_hashmap扩容为啥是2倍

(10)、在多线程添加HashMap的时候,会出现什么问题

jdk1.7  HashMap,多线程添加数据,扩容时候的头插法,出现死循环问题 疫苗:Java HashMap的死循环 | 酷 壳 - CoolShell

jdk1.8  HashMap虽然不会出现死循环,但是也会出现线程安全问题,相同hash数据在同一个点插入丢失。

视频讲解:JDK7的HashMap头插法循环的问题,这么难理解吗?_哔哩哔哩_bilibili

(11)、为什么HashMap要自己实现writeObject和readObject方法?

HashMap中,由于Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的,对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的

因为所有的对象,都继承自Object类。Object类中的hashCode()方法如下,是native的,在不同平台的实现不同:

public native int hashCode();

String类中重写了hashCode方法:

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

Integer类中重载了hashCode方法:

    public static int hashCode(int value) {
        return value;
    }


所以为了避免这个问题,HashMap采用了下面的方式来解决

将可能会造成数据不一致的元素使用transient关键字修饰,从而避免JDK中默认序列化方法对该对象的序列化操作。不序列化的包括:Entry[] table,size,modCount。
自己实现writeObject方法,从而保证序列化和反序列化结果的一致性。那么,HashMap又是通过什么手段来保证序列化和反序列化数据的一致性的呢?
首先,HashMap序列化的时候不会将保存数据的数组序列化,而是将元素个数以及每个元素的Key和Value都进行序列化。
在反序列化的时候,重新计算Key和Value的位置,重新填充一个数组:

    // Called only from writeObject, to ensure compatible ordering.
    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        Node<K,V>[] tab;
        if (size > 0 && (tab = table) != null) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    s.writeObject(e.key);
                    s.writeObject(e.value);
                }
            }
        }
    }

为什么HashMap要自己实现writeObject和readObject方法? - 知乎

(12)、HashMap线程安全的替代方案:ConcurrentHashMap

(13)、HashMap的空间利用率问题:

(14)、参考链接:

(1)美团面试题:Hashmap的结构,1.7和1.8有哪些区别,史上最深入的分析_王伟的博客-CSDN博客_hashmap1.7和1.8的区别

史上最详细的 JDK 1.8 HashMap 源码解析_程序员囧辉-CSDN博客_hashmap详解

43、LinkedHashMap 源码详细分析(JDK1.8)以及简单的LRU算法实现

LinkedHashMap 源码详细分析(JDK1.8)_慕课手记

44、几种Map类型数据结构对比:

下面针对各个实现类的特点做一些说明:

(1) HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

(2) Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

(3) LinkedHashMap:LinkedHashMap 在HashMap上面结构的基础上,增加了一条双向链表,使得结构可以保持键值对的插入顺序,初始情况下,让 LinkedHashMap 的 head 和 tail 引用同时指向新节点,链表就算建立起来了。随后不断有新节点插入,通过将新节点接在 tail 引用指向节点的后面,即可实现链表的更新。默认情况下,LinkedHashMap 是按插入顺序维护链表。不过我们可以在初始化 LinkedHashMap,指定 accessOrder 参数为 true,即可让它按访问顺序维护链表。访问顺序的原理上并不复杂,当我们调用get/getOrDefault/replace等方法时,只需要将这些方法访问的节点移动到链表的尾部即可。

(4) TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap,TreeMap 的底层就是一颗红黑树。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。

对于上述四种Map类型的类,要求映射中的key是不可变对象。不可变对象是该对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map对象很可能就定位不到映射的位置了。

45、解决java读取大文件内存溢出问题:

不要采用 new File(path)的方式,一次性把大文件加载在内存中

解决方案(1):使用java.util.Scanner类扫描文件

FileInputStream inputStream = null;

Scanner sc = null;

try {

    inputStream = new FileInputStream(path);

    sc = new Scanner(inputStream, "UTF-8");

    while (sc.hasNextLine()) {

        String line = sc.nextLine();

        // System.out.println(line);

    }

    // note that Scanner suppresses exceptions

    if (sc.ioException() != null) {

        throw sc.ioException();

    }

} finally {

    if (inputStream != null) {

        inputStream.close();

    }

    if (sc != null) {

        sc.close();

    }

}

解决方案(2):Apache Commons IO流,同样也可以使用Commons IO库实现,利用该库提供的自定义LineIterator:

LineIterator it = FileUtils.lineIterator(theFile, "UTF-8");

try {

    while (it.hasNext()) {

        String line = it.nextLine();

        // do something with line

    }

} finally {

    LineIterator.closeQuietly(it);

}

解决方案(3):在java代码中,把大文件拆分成小文件(也得以上面两种方法为前提,行读取)

解决方案(4):使用Linux命令,split -l  按照行分割大文件,拆分成小文件处理:Linux命令之大文件分割_小哥骑单车-CSDN博客_linux拆分文件

解决java读取大文件内存溢出问题、如何在不重复读取与不耗尽内存的情况下处理大文件_yangguoqi的专栏-CSDN博客

高效读取大文件,再也不用担心 OOM 了!_Java极客技术-CSDN博客

46、深入解析String#intern(美团技术博客)

深入解析String#intern - 美团技术团队

47、Java内存模型final域的内存语义:

(1)、final域的重排序规则

对于final域,编译器和处理器要遵守两个重排序规则。

1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

(2)、写final域的重排序规则

写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含下面2个方面。

1)JMM禁止编译器把final域的写重排序到构造函数之外。

2)编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。

JSR-133为什么要增强final的语义

在旧的Java内存模型中,一个最严重的缺陷就是线程可能看到final域的值会改变。比如,一个线程当前看到一个整型final域的值为0(还未初始化之前的默认值),过一段时间之后这个线程再去读这个final域的值时,却发现值变为1(被某个线程初始化之后的值)。最常见的例子就是在旧的Java内存模型中,String的值可能会改变。

为了修补这个漏洞,JSR-133专家组增强了final的语义。通过为final域增加写和读重排序规则,可以为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。

《Java并发编程的艺术》 读书笔记 之 Java内存模型(六)final域的内存语义_小哥骑单车-CSDN博客

48、java反射基本理解:

Java反射机制创建对象_水滴的专栏-CSDN博客_java反射创建对象

49、this关键字与super关键字详解

this关键字与super关键字详解_大同小后生伟的博客-CSDN博客_super关键字

50、Lombok 的 @EqualsAndHashCode(callSuper = false) 的使用

Lombok 的 @EqualsAndHashCode(callSuper = false) 的使用_请叫我大师兄-CSDN博客_equalsandhashcode

51、Java异常处理基础知识笔记

Java异常处理基础知识笔记:异常处理机制、异常继承关系、捕获异常、抛出异常、异常的传播、异常调用栈、自定义异常、第三方日志库

Java异常处理基础知识笔记:异常处理机制、异常继承关系、捕获异常、抛出异常、异常的传播、异常调用栈、自定义异常、第三方日志库 - 古兰精 - 博客园

52、【Java基础-3】吃透Java IO:字节流、字符流、缓冲流

【Java基础-3】吃透Java IO:字节流、字符流、缓冲流_云深不知处-CSDN博客_java缓冲字节流

53、Java中Iterable和Iterator的辨析

Iterable接口中只包含一个方法,就是一个iterator()方法,用来返回一个Iterator类型的对象,或者说返回一个实现了Iterator接口的对象。

Java中Iterable和Iterator的辨析_TangowL-CSDN博客_iterable java

54、反射的优点缺点:

优点:使用场合:在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息。反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类;

缺点:

性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

使用反射会模糊程序内部逻辑:程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

3.反射的使用场合和作用、及其优缺点 - 知乎

55、NoClassDefFoundError 和 ClassNotFoundException 有什么区别

Java 面试题:NoClassDefFoundError 和 ClassNotFoundException 有什么区别?_LMAO的博客-CSDN博客_noclassdeffounderror 什么意思

56、Java synchronized锁升级过程简述

Synchronized 的锁升级流程是这样:无锁 ----> 偏向锁 ----> 轻量级锁----> 锁自旋 ----> 重量级锁

最开始是无锁状态的;

(1)偏向锁:

为什么要引入偏向锁?偏向锁是单线程下的锁优化;

因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

偏向锁的升级

当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

偏向锁的取消:

偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;

如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;

(2)轻量级锁

为什么要引入轻量级锁?

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

轻量级锁什么时候升级为重量级锁?

线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。

但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

*注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

参考:Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级_tongdanping的博客-CSDN博客

Synchronized你以为你真的懂? - 知乎

57、为什么synchronized演变成重量级锁后性能会下降

Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为重量级锁。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。

JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了轻量级锁和偏向锁。

58、TreeMap中如何实现自定义类key值的排序

TreeMap中如何实现自定义类key值的排序_huang_xiao_yu的博客-CSDN博客

59、Java volatile关键字总结

Java volatile关键字最全总结:原理剖析与实例讲解(简单易懂)_夏日清风-CSDN博客_java volatile

60、volatile能保证可见性,为什么不能保证原子性?

简单的说,修改volatile变量分为四步:

1)读取volatile变量到local

2)修改变量值

3)local值写回

4)插入内存屏障,即lock指令,让其他线程可见

这样就很容易看出来,前三步都是不安全的,取值和写回之间,不能保证没有其他线程修改。原子性需要锁来保证。

这也就是为什么,volatile只用来保证变量可见性,但不保证原子性。

首先线程A读取了i的变量的值,这个时候线程切换到了B,线程B同样从主内存中读取i的值,由于线程A没有对i做过任何修改,此时线程B获取到的i仍然是100。线程B工作内存中为i执行了加1的操作,但是没有刷新到主内存中,这个时候又切换到了A线程,A线程直接对工作内存中的100进行加1运输(因为A线程已经读取过i的值了),由于线程B并未写入i的最新值,这个时候A线程的工内存中的100不会失效。 最后,线程A将i=101写入主内存中,线程B也将i=101写入主内存中。 始终需要记住,i++ 的操作是3步骤!这样理解起来就更容易了

volatile为什么不能保证原子性_Blog-CSDN博客_volatile为什么不能保证原子性

61、Java程序比C/C++程序慢的影响因素

1)解释性语言固有开销:java程序在运行时类加载器从类路经中加载相关的类,然后java虚拟机读取该类文件的字节,执行相应操作.而C 编译的时候将程序编译成本地机器码.一般来说java程序执行速度要比C 慢10-30倍.即使采用just-in-time compiling (读取类文件字节后,编译成本地机器码)技术,速度也要比C 慢好多.

2)字节码加载执行开销:java程序要从网络上加载类字节,然后执行,这也是导致java运行速度慢的原因.

3)运行时溢出检测开销:在程序运行过程中,java虚拟机要检测数组是否越界,在C 中则不检测.

4)堆与栈的区别:java中所有的对象都创建在堆中,没有对象被创建在stack中,而C 有的对象和变量是创建在stack中的

5)运行时引用检测开销:java在运行过程中检测对象的引用是否为空,如果引用指向都空指针,且执行某个方法时会抛出空指针异常

6)运行时类型检测开销:java运行时对类型检测,如果类型不正确会抛出ClassCastException异常.

7)GC巨大开销:java的垃圾回收机制较C 由程序员管理内存效率更低.

8)类型转换开销:java中的原始数据类型在每个操作系统平台长度都是相同,而C 这些数据类型长度是随操作系统的不同而不同,所以java在不同操作系统上执行时有个转化过程.

9)String类型开销:在java中String 是UNICODE.当java要操作一个 ASCII string 时,比C 效率上相对要低一些.

10)动态链接开销:java中采用的是动态链接

java 和 C 代码运行效率的比较(整理) - dy9776 - 博客园

62、JAVA 内存泄露详解(原因、例子及解决)

3JAVA 内存泄露详解(原因、例子及解决)_anxpp的博客-CSDN博客_java内存泄漏的原因及解决办法6

63、并发编程中的逃离“996icu”——this引用逃逸 - 云+社区 - 腾讯云

64、深入理解java注解的实现原理(转载)

深入理解java注解的实现原理(转载)_YuZongTao-CSDN博客_java注解的实现原理

并发编程中的逃离“996icu”——this引用逃逸 - 云+社区 - 腾讯云

65、为什么需要base 64编码解码

为什么要使用base64编码,有哪些情景需求? - 知乎

66、深拷和浅拷贝的区别:

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

Java深入理解深拷贝和浅拷贝区别_java深拷贝和浅拷贝的区别_老周聊架构的博客-CSDN博客

深拷贝的几种方法:

Java深拷贝的几种方法_jackson深层拷贝_FserSuN的博客-CSDN博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值