Java各类技能知识点学习链接大全:一、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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供典型应用案例,剖析JSP/Servret技术与Struts 2技术在Web开发中的不同 提供完整的应用案例,使读者可以深入体会SSH开发模式的精髓 所有开发工具和框架均使用目前的最新版本,紧跟技术发展的趋势 提供230个实例和4个综合案例,可以作为案头必备的查询手册 一线开发人员全力打造,分享技术盛宴! 重点内容及特色 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》介绍了Web开发中客户端技术的基础知识,包括JavaScript、CSS、AJAX等,这些技术都是Web应用中常用的客户端技术。 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax+》讲解了JSP/S rvlet技术的基础知识,并提供了一个综合案例展示其具体应用,它们是Java Web服务端技术的基石,也是学习Java Web开发所要必须掌握的技术。 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》重点讲解了Struts 2、Speing和HIbernate框架的基础知识和高级技术,如Sruts 2中的*、类型转换、国际化和标签等,HIbe rna{e的会话、0/R映射和事务管理等,Spring中的数据库技术与AOP等。 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》特别介绍了Struts 2对AjAX的支持,还重点剖析了SSH框架的整合开发,并给出了两个综合案例来展示整合SSH框架开发Web应用。 和已经出版的同类图书相比,《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》讲解由浅入深,涵盖更多内容,列举了大量典型实例具有超强的实用性,另外,《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》各篇独立,适合读者全面学习或对部分内容重点学习。 读者对象 有Java基础,想进一步学习SSH框架整合开发的人员 了解SSH整合开发,想进一步提高开发技术的人员 正在使用SSH整合技术开发项目,想查阅资料的人员 大中专院校的学生和老师,以及Java培训班的学员和讲师 需要一本案头必备查询手册的程序员 光盘内容 6小多媒体体视频讲解 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》所涉及的源代码 布衣暖,菜根香,好书滋味长!清华大学出版社长期以来一直秉承为读者多出好书的宗旨,多年来为读者奉献了大量脍炙人口的精品图书。尤其在计算机图书出版领域更是形成了鲜明特色,所出版的各类计算机图书受到了广大读者的好评。本次出版的“原创经典,程序员典藏”系列图书是清华大学出版社的重点精品计算机图书,旨在帮助读者全面学习各类程序设计语言和开发工具,提高开发水平。同也为广大程序员提供良好的技术参考,以便作为案头必备的查询手册。 内容提要 -------------------------------------------------------------------------------- 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》通过对SSH中的各种技术循序渐进地讲解,使读者尽快掌握开发基于SSH的Web程序的方法。《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》内容包括Web客户端技术、JSP/Servlet技术、Struts 2(*、类型转换、输入校验、上传和下载文件、Struts 2的各种标签、对 AJAX的支持等)、Spring(Ioc容器、装配Java Bean、Jdbc和Hibernate模板、事务管理、Spring AOP等)以及 Hibernate(会话、映射、标准查询API、HQL、事务管理、锁等)。除此之外,《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》还提供了两个完整的实例来讲解开发SSH的详细步骤和方法。通过对这两个实例的学习,读者可以对SSH开发模式有更透彻地理解和认识。SSH是目前最流行的Java Web开发技术。 《Java Web开发技术大全:JSP+Servlet+Struts+Hibernate+Spring+Ajax》适合广大从事Java Web开发工作的技术人员、对SSH开发感兴趣的人员以及大专院校学生阅读,尤其是具有一定的Web开发经验的技术人员。 目录 -------------------------------------------------------------------------------- 第1篇 web开发基础篇 第1章 搭建开发环境 1.1 本书使用的软件和框架的版本 1.2 JDK6的下载与安装 1.3 Eclipse3.4 的下载与安装 1.4 MyEclipse6.5 的下载与安装 1.5 Eclipse:IDEforJavaEEDevelopers的下载与安装 1.6 Tomcat6的下载与安装 1.7 在MyEclipse中配置。Tomcat 1.8 在EclipseIDEforJavaEEDevelopers中配置Tomcat 1.9 小结 第2章 JavaWeb应用开发基础 2.1 Web技术的发展 2.2 JavaWeb技术 2.2.1 Java.Welb程序的基本组成 2.2.2 Java,Web程序的目录结构 2.2.3 JavaWeb程序的配置文件 2.3 MVC模式与MvC框架 2.3.1 JSP模型1和JSP模型2 2.3.2 Web应用程序需要的基础服务 2.3.3 MVC模式概述 2.3.4 常用的MvC框架 2.4 小结 第3章 Web开发中的客户端技术 3.1 常用的JavaScriptIDE简介 3.1.1 在MyEclipse中使用JavaScript 3.1.2 在EclipseIDEforJavaEE中使用JavaScript 3.1.3 在NetBeans中使用JavaScript 3.1.4 其他的JavaScriptIDE 3.2.1 avaScdpt语法基础 3.2.1 实例:编写第一个JavaScript程序:Greet 3.2.2 变量 3.2.3 原始类型 3.2.4 类型转换 3.2.5 函数与函数调用 3.2.6 类和对象 3.3 JavaScript高级技术 3.3.1 DOM技术概述 3.3.2 获得HTML元素的3种方法 3.3.3 实例:图像自动切换 3.3.4 正则表达式 3.3.5 实例:表格排序 3.4 CSS基础 3.4.1 CSS的基本语法 3.4.2 在Style属性中定义样式 3.4.3 在HTML中定义样式 3.4.4 在外部文件中定义样式 3.4.5 样式的继承 3.5 AJAX.基础 3.5.1 AJAX概述 3.5.2 实例:使用XMLHttpRequest获得Web资源 3.5.3 实例:使用XMLHttpRequest跨域访问Web资源 3.5.4 实例:AJAX的3种交换数据方法 3.6 小结 第4章 Servlet技术 4.1 Servlet的Helloworld程序 4.1.1 实例:在My Eclipse中编写Helloworld程序 4.1.2 实例:手工编写:Helloworld程序 4.2 Servlet基础 4.2.1 配置数据库连接池 4.2.2 数据库连接池的应用 4.2 -3实例:用doGet方法处理客户端请求 4.2.4 实例:用doPost方法处理客户端请求 4.2.5 实例:用service方法处理客户端请求 4.2.6 实例:初始化(init)和销毁(destroy)Servlet 4.2.7 实例:使用PrintWriter输出响应消息 4.2.8 实例:用ServletOutputStream显示图像 4.2.9 实例:使用RequestDispatcher包含Web资源 4.2.10 实例:使用RequestDispatcher转发Web资源 4.3 HttpServletResponse类的其他功能 4.3.1 产生状态响应码 4.3.2 设置响应消息头 4.3.3 实例:验证响应头设置情况 4.4 使用:HttpServletRequest获得请求消息 4.4.1 获取请求行消息 4.4.2 获取网络连接消息 4.4.3 获取请求头消息 4.5 处理Cookie 4.5.1 什么是Cookie 4.5.2 Cookie类中的方法 4.5.3 实例:用Cookie读写客户端信息 4.5.4 实例:用Cookie读写复杂数据 4.6 处理Session 4.6.1 什么是Session 4.6.2 HttpSession接口中的方法 4.6.3 HttpServletRequest接口中的Session方法 4.6.4 实例:通过Cookie跟踪Session 4.6.5 实例:通过重写uRL跟踪Session 4.7 Web开发的中文问题 4.7.1 Java的编码原理 4.7.2 实例:解决输出中文乱码问题 4.7.3 实例:解决服务端程序读取中文请求消息的乱码问题 4.7.4 实例:用AJAX技术发送和接收中文信息 4.7.5 实例:在请求消息头和响应消息头中转输中文 4.8 小结 第5章 JSP技术 5.1 用MyEclipse编写第一个JSP程序 5.1.1 实例:编写显示服务器当前间的JSP程序 5.1.2 调试JSP程序 5.1.3 改变JSP的访问路径和扩展名 5.1.4 手动发布JSP程序 5.2 JSP的运行原理 5.2.1 Tomcat如何处理JSP页 5.2.2 分析由JSP生成的Servlet代码 5.3 JSP基本语法 5.3.1 JSP表达式 5.3.2 在JSP中嵌入Java代码 5.3.3.JSP声明 5.3.4.JSP表达式语言(EL) 5.3.5 实例:用EL函数替换HTML中的特殊字符 5.3.6 JSP页面中的注释 5.4 JSP指令 5.4.1 JSP指令简介 5.4.2 page页面指令 5.4.3 include加入指令 5.5.JSP的9个内置对象 5.5.1 out输出对象 5.5.2 pageContext封装对象 5.5.3 其他的JSP内置对象 5.6 JSP标签 5.6.1 插入标签 5.6.2 转发标签 5.6.3 传参标签 5.6.4 创建:Bean标签 5.6.5 设置属性值标签 5.6.6 获取属性值标签 5.7 JSP的标准标签库(JSTL) 5.7.1 如何使用JSTL 5.7.2 条件标签 5.7.3 循环标签 5.8 小结 第6章 用Servlet和JSP实现注册登录系统 第2篇 Struts 2篇 第7章 编写Struts 2的第一个程序 第8章 Struts 2进阶 第9章 Struts 2的* 第10章 Struts 2的类型转换 第11章 Struts 2的输入校验 第12章 文件的上传和下载 第13章 国际化 第14章 Struts 2的标签库 第15章 Struts 2对AJAX的支持 第16章 用Struts 2实现注册登录系统 第3篇 Hibernate篇 第17章 Hibernate的Helloworld程序 第18章 配置Hibernate 第19章 Hibernate的会话与O/R映射 第20章 Hibernate的查询与更新技术 第21章 Hibernate的高级技术 第4篇 Spring篇 第22章 Spring的Helloworld程序 第23章 反向控制(Ioc)与装配JavaBean 第24章 Spring中的数据库技术 第25章 Spring的其他高级技术 第5篇 综合实例篇 第26章 Struts 2与Hibernate、Spring的整合 第27章 网络硬盘 第28章 论坛系统

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值