• Java基础知识整理


    1.StringBuffer和StringBuilder

     StringBuffer和StringBuilder师出同门,一个自带线程安全,一个不考虑线程安全,如上,他们的师傅:

     通过翻看源码可以看出这俩兄弟针对字符串操作的所有方法基本上都是直接调用父类的方法实现,只不过是同步与不同步的区别。从上图中可以看出他们两兄弟的师傅已经实现了可变的字符串的基本内容,所以这俩兄弟就是一个可变字符串数组(char)的搬运工。

     2. 字符串常量池

      首先必须要知道的是字符串是不可变的,因为它的char数组是私有的且final的,这是常量池加入字符串常量池的必要原因之一,即字符串的不变性。Java为了避免产生大量的String对象,设计了一个字符串常量池。工作原理是这样的,创建一个字符串时,JVM首先为检查字符串常量池中是否有值相等的字符串,如果有,则不再创建,直接返回该字符串的引用地址,若没有,则创建,然后放到字符串常量池中,并返回新创建的字符串的引用地址。

    了解下三个概念:

      字面量:字面量就是一个值,比如变量的值或者常量的值(字母、字符串、数字等等)
      变量:定义完成后,可以修改的数据
      常量:定义完成后,固定且不能改变的数据

    常量值又称为字面常量,它是通过数据直接表示的,这句话什么意思?就是说在定义常量值的时候直接赋值的方式如:String A="a"; 那么"a"就是一个常量值,还有其他数据类型,如int,布尔等。

    常量是区别于变量的一种类型,同时又不同于常量值,它是通过声明一个常量符号来代替常量值,一般以大写定义名字,如final String A="a";,A就是一个常量,但是需要使用final来修饰。常量具有以下特点:

    1. 在定义常量时就需要对该常量进行初始化。

    2. final 关键字不仅可以用来修饰基本数据类型的常量,还可以用来修饰对象的引用或者方法,代表此类或此方法不允许被继承或重写,具有声明保护的意义。

    我们知道在Java中除了基本数据类型之外都是对象,所以字符串也是对象,对象就要涉及到内存分配,且字符串常量是不变的,所以为了减少在JVM中频繁创建字符串常量的内存消耗,就将字符串常量池也加入到了方法区中了。

    ps:方法区存放加载的类信息、常量、静态变量,静态代码块等信息;类信息包括类的版本、字段、方法、接口等,方法区也被称为永久代。

    字符串创建的其中2种方法:

      1. new String(“a”);以这种方法创建的字符串会首先判断字符串常量池中是否有该字符串,如果没有就进行创建,另外因为有new关键字,还会在堆内存中创建该字符串,但是这个字符串返回的地址是堆上的地址,所以它和其他相同内容的字符串的内存地址都是不相等的,注意此时就创建了两个字符串,当调用intern()方法时,如果字符串常量池中有这个字符串就直接返回该字符串的值,如果没有就将该字符串值加入到字符串常量池中,然后再返回。注意JDK1.7之后虽然字符串常量池也转换到了堆中,但是其实字符串常量池是在堆中独立开辟的空间,和堆中的其他字符串不是一个空间。

      2. 通过字面量赋值创建字符串,如:String A="a";,会先在常量池中查找是否存在该字符串,若存在则返回,否则在常量池中生成一个该字符串。

     3. 为什么String + 操作效率低下?

    因为+操作会先创建一个StringBuilder对象然后通过append方法将两个字符串拼接,而每次调用+操作都会创建一个StringBuilder对象,创建内存对象自然消耗巨大。

    将一个基本数据类型转换为String一共有三种方式:

    1.int i = 5;
    2.String i1 = "" + i;
    3.String i2 = String.valueOf(i);
    4.String i3 = Integer.toString(i);
    

    第三行和第四行没有任何区别,因为String.valueOf(i)也是调用Integer.toString(i)来实现的。

    第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。

    https://mp.weixin.qq.com/s?__biz=MzU3NTA5MzQ5Mg==&mid=2247483931&idx=1&sn=332a10dd74d8093ee1aa04d54c710f1e&chksm=fd292d80ca5ea4960af6c6374cf7ce633d62cee072c6d3c9bb3ea3df4586f482144af7f8bea5&token=1439522715&lang=zh_CN#rd

    4. volatile关键字

      首先了解下Java中对重排序的两个要求:as-if-serial语义和happens-before规则

    为什么要重排序?重排序发生在下面几个步骤:

     主要是编译器和处理器为了优化我们的程序性能,但是这些优化步骤需要遵循上面两个要求。

    as-if-serial语义?as-if-serial语义允许对存在控制依赖的操作做重排序,前提是不影响最终的执行结果。

      数据依赖性?如果有两个操作对一个数据进行操作,其中存在至少一个写操作,那么这两个操作就存在数据依赖性,此时在程序员编程思想下构建了这两个操作,如果重排序了这两个操作,则得到的结果绝对不是程序员预想的,这就是数据依赖性。所以as-if-serial语义要求可以重排序但是要保证这种数据依赖性。

    happens-before规则?该规则共有下面六个规则:

      规则1:结果一致性规则:可以随便重排序但是需要保证最终结果一致,这和as-if-serial语义有异曲同工之妙,但有些许区别。

      规则2:强化volatile规则:对一个volatile变量的写操作,happens-before后续对这个变量的读操作,即在一个计算流程中需要保证写操作必须按照程序员原本的意外Happen-Before于读操作之后。

      规则3:传递性规则:这没啥好说的。

      规则4:锁规则:即对一个对象的上锁和去锁操作不允许重排序。

      规则5:线程start()规则:a线程启动b线程,那么保证a线程在启动之前的操作可见于b线程。

      规则6:线程join()规则:a线程joinb线程,那么保证b线程可以看到b线程的所有操作。

    https://www.jianshu.com/p/9464bf340234

      Java中volatile关键字可以保证数据的可见性和禁止重排序。因为JMM定义了线程拥有需要数据的本地副本,而Java采用的并发模型又是共享内存的形式,所以如果同一个对象的数据的可见性得不到保证就会导致线程之间的通信出现错误,换句话说就是线程得到了错误的数据,所以volatile关键字可以保证数据的可见性;禁止重排序,多线程下多个线程对volatile变量的读写操作不允许被重排序,这样就不会读到错误的数据,这是由Happen-before规则定义的。

    https://www.jianshu.com/p/8a58d8335270

    5. i++的线程不安全问题

    为什么说i++是线程不安全的?答案很简单,因为i++并不是一个原子操作,而是分布进行的操作,包括i++,++i,这两个操作都不是原子性操作,解决的办法是加锁或者使用atomicxxx原子类,前者是通过同步线程,后者是通过自旋锁锁住该变量,对于修改操作只允许一个线程操作该变量,但是性能会比不加自旋锁慢20倍。

     https://www.jianshu.com/p/7ca19d0ad176

    6. ArrayList实现原理

    ArrayList的数据结构是数组(数组即是数据类型也是一种数据结构),这个类没有什么特殊的处理,但是在实际使用中还是占有很大的比例,看一下它的简短介绍:

     从上图可以看出,可变数组,实现了List全部接口的List实现类,另一个相似的类是Vector,只是Vector是同步List,而此类是不同步的。

    7. 什么是链表

      链表也是Java中的一种数据结构,区别于数组,链表不要求内存连续,数组的优缺点是CRUD简单,但是在使用前需要确定内存。而链表不需要确定内存,增删改简单查找复杂,链表具有前驱和后驱节点,以此来定位前后节点。

    8. 为什么重写了equals方法也必须重写hashCode方法

    简单点讲重写这两个方法大多是因为Hash表的原因,如hashmap等,因为获取key在hash表中的散列值是需要用到Object的hashCode方法得到这个key本身的散列值,但是散列值它的规则是:

    • 在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
    • 如果2个对象通过equals调用后返回是true,那么这个2个对象的has
    • hCode方法也必须返回同样的int型散列码如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)

    这就导致了一个后果,如果你只是重写了equals方法,两个值相等但是地址不相等的对象会因为在内存中的散列值不同而导致他们被分配到了不同hash槽中,但实际上他们应该也必须在一个hash槽中,这会让基于hash表的集合类出现错误。

    https://www.cnblogs.com/ouym/p/8963219.html

    9. 为何HashMap的数组长度一定是2的次幂

      一句话为了增加一个扰动函数,因为2的次幂-1的长度的低位全部为1,这在hashmap计算hash槽位置时,会将本身的hashCode与自己进行异或,这样key本身的hashCode的高16位和自己的低16位异或,这样key的hashCode会更加均匀的散列在hash表中。就是减少了hash冲突的意思。

    10. 线程的五个状态

    • 新建:就是刚使用new方法,new出来的线程;

    • 就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

    • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

    • 阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

    • 销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

    创建线程的几种方法:

    1.继承Thread类,重写run方法
    2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
    3.通过Callable和FutureTask创建线程

    1 public class threads {
    2     public static void main(String[] args) {
    3         Thread thread = new Thread(new FutureTask<Object>(()-> {System.out.println("线程名:"+Thread.currentThread().getName());return null; }));
    4         thread.start();
    5     }
    6 }

    4.通过线程池创建线程 

    1 public class threads {
    2     public static void main(String[] args) {
    3         ExecutorService executorService = Executors.newFixedThreadPool(1);
    4         executorService.execute(() -> {
    5             System.out.println("线程名:"+Thread.currentThread().getName());
    6         });
    7         executorService.shutdown();
    8     }
    9 }

    另外线程池处理execute可以执行线程外,还有一个submit方法可以执行线程,区别是submit会返回执行结果以及抛出的异常,而execute没有返回结果。

    11. synchronized和Lock

      synchronized得益于JDK版本的增强,此JDK自带的同步方法,已经成为了一个会自动升级的锁,会从无锁状态到偏向锁,到轻量级锁最后到重量级锁,所以很好用。而Lock锁是Java锁,即通过java代码实现的锁,底层是通过队列和自旋来同步线程,牵扯到自旋就意味者要牺牲效率来达到目的,因为自旋本身就是一个耗费资源的动作。

    12. 线程间通信

      线程间通信分为两种形式,一种是共享内存,一种是消息通信。主要有以下几种实现方式:

      1. Volatile关键字:因其线程可见性,所以可作为线程间通信的基石。

      2. 使用Object类的wait,notify,notifyall方法进行线程间通信。实现原理很简单,在一个Object上等待的所以线程都会接收到该对象发起的wait,notify,notifyall方法,从而达到唤醒和阻塞线程的目的,注意,wait方法是放弃锁,而Thread.sleep()方法是休息但不放弃锁。

      3.使用CountDownLatch

      CountDownLatch内部维护一个线程可见int类型的state字段,然后是其重要的三个方法:

    1 //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
    2 public void await() throws InterruptedException { };   
    3 //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
    4 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
    5 //将count值减1
    6 public void countDown() { };  

    调用countDown方法将state-1,其他线程在执行await方法时会判断state字段是否为0,如果不为0就挂起,以此来达到通信的目的。

      4. ReentrantLock 结合 Condition

      Condition的使用方式和Object的notify和wait类似,await方法会使当前线程放弃锁,signal方法会让处于await的线程进行锁的竞争。

      5. LockSupport

      使用十分简单的一个线程间通信的工具类,a线程调用LockSupport.park();方法会使a线程进入等待状态,而唤醒该线程需要b线程调用LockSupport.unpark(a线程的名字);

    13. ThreadLocal

      ThreadLocal就是一个内涵map,但是这个map是每个线程都有自己的,互不干扰,可以通过设置不同的值来进行对线程特定化操作。下面是该类的get方法体:

     1 public T get() {
     2         Thread t = Thread.currentThread();
     3         ThreadLocalMap map = getMap(t);
     4         if (map != null) {
     5             ThreadLocalMap.Entry e = map.getEntry(this);
     6             if (e != null) {
     7                 @SuppressWarnings("unchecked")
     8                 T result = (T)e.value;
     9                 return result;
    10             }
    11         }
    12         return setInitialValue();
    13     }

    14. AbstractQueuedSynchronizer

      AbstractQueuedSynchronizer又叫AQS,AQS是伴随JUC一起来的,JUC提供很多并发编程的实用工具类,包括AQS,有很多地方都会用到这个AQS,比如Lock,CountDownLatch等,算是一个公共抽象类吧。

    https://www.jianshu.com/p/da9d051dcc3d

    15. ThreadLocal的OOM,弱引用和强引用

      ThreadLocal为什么会出现OOM?ThreadLocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.

      强引用?当一个内存中的对象被一个变量指向,那么这个内存对象就存在强引用,GC就无法将其销毁,而弱引用则相反,当一个内存中的对象无论有没有被引用,只要其没有在其它地方被调用,那么下次GC就会将其回收。弱引用类WeakReference。

    https://www.cnblogs.com/zjj1996/p/9140385.html

    16. 深拷贝,浅拷贝

      浅拷贝就是地址的复制,地址内得内容的变动影响所有浅拷贝的对象;深拷贝就是对象的重新复制,一个对象内的内容变动不会影响其他深拷贝的对象内容。

    17. JSP的九大隐含对象,四大域,转发和重定向,HTTP1.1,拦截器和过滤器

    request对象; response对象;session对象:本次请求的一个会话,存储在服务器上;out对象:JspWriter 的一个对象。调用out.println()方法可以直接将字符串打印到浏览器上; page对象:当前页面本身; application对象:用户数据的共享,可以设置和读取属性;

     exception对象:对异常信息的封装;pageContext对象:pageContext对象提供了对JSP页面内所有的对象及名字空间的访问,包括session等其他隐藏对象;config对象:此信息包括Servlet初始化时所要用到的参数。

    https://blog.csdn.net/leeyefang/article/details/38925017

    *****************

    pageContext对象作用域: 作用域:当前页面,只能在当前访问页面使用或获取属性。

    request对象作用域:在当前请求范围内有效,服务器跳转也算同一次请求,同样有效。

    session对象作用域:在一次会话中有效,既:只要浏览器窗口不关闭 就能获取到session。

    application对象作用域:生命周期随同服务器的开闭。。。既在整个服务器的运行期。

    *****************

    转发是在本次服务器内部的请求调整,共用九大内置对象;而重定向可以将请求重定向到外部服务器的组件,属于又一个请求。

    *****************

    HTTP1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理;增加了Host字段;100(状态码) Status(节约带宽)。https://www.cnblogs.com/gofighting/p/5421890.html

    *****************

    过滤器是基于Servlet容器的组件,一个过滤器会可以拦截所有请求,并作出对拦截对象的变动;拦截器是基于反射机制,对被拦截对象的调用前后做出应激反应。https://blog.csdn.net/longzhongxiaoniao/article/details/85727725

    18. JVM双亲委派机制

      什么是JVM双亲委派机制?JVM双亲委派机制是指在JVM加载一个class类进内存的时候,会先有最顶层的类加载器进行加载,逐级向下试探直到加载完毕或者加载失败,这样做的好处是官方基础类不会被自定义类污染,如你永远也无法加载一个自定义的String类进内存,除非你自定义了一套类加载器,但这种情况下没有什么意义。

    19. 数据库事务

    事务的ACID特性,原子性,一致性,隔离性,持久性。

    隔离级别:数据库的隔离级别共有四等,分布是:

    1.无隔离级别(读未提交)

    2.可避免脏读(读已提交

    3.避免脏读,不可重复读情况的发生(可重复读)

    4.可避免脏读,不可重复度,幻读的发生(串行化)

    串行化读又叫Serializable,就是指一个事务中对同一个数据的读取需要串行化读取,说白了就是你们要one by one的操作这个数据。

    脏读:指一个事务读取了另外一个事务未提交的数据。

    不可重复读:不可重复读是指在事务1内,读取了一个数据,事务1还没有结束时,事务2也访问了这个数据,修改了这个数据,并提交。紧接着,事务1又读这个数据。由于事务2的修改,那么事务1两次读到的的数据可能是不一样的,因此称为是不可重复读。

    幻读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。

      可重复度解决了事务内的数据被其他事务修改导致两次读取值不一致;而不可幻读解决的是事务读取一定范围内的行数,注意此除和不可重复读的区别是行数,因为其他事务插入新的行数,导致当前事务重新读取的行数不对。

    https://blog.csdn.net/Vincent2014Linux/article/details/89669762

  • 相关阅读:
    记一次在Windows10桌面环境搭建Jekins的吐血经历
    Windows系统下的输入法选择
    Linux后台进程启停脚本模板
    crontab采坑总结
    编程软件仓库集合
    CentOS7安装Chrome及驱动
    不错的“淘宝”网站
    软件下载网站集合
    在线API集合
    在线教程集合
  • 原文地址:https://www.cnblogs.com/YsirSun/p/12629323.html
Copyright © 2020-2023  润新知