Java 重要知识点,踩过的坑

(1),关于 LinkedHashMap  TreeMap HashMap 之间的区别:

HashMap 是无序的,LinkedHashMap 由于内部维护了一个记录的链表,数据操作的前后顺序都会在链表上下节点保存着;

而TreeMap 内部的数据是有序的

分析如下:

1.LinkedHashMap 我们看类结构上是实现了HashMap ,在添加元素的时候,在实现添加put 方法时候,重写了其newNode 方法,如下:

我们看HashMap newNode 方法: 就是普通的创建了一个节点对象
// Create a regular (non-tree) node Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next); } 我们再看LinkedHashMap 重写的newNode 方法: Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } //链表操作 // link at the end of list private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }

我们看一看TreeMap 结构: 对于数据都要进行比较,然后再判断放到数的左边还是右边,然后进行红黑树自旋

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

(2).java 类加载器,以及委托机制

  java 类加载器分为引导,扩展,系统 三类类加载器  引导类加载器主要负责加载java 类库下的包,扩展类加载器主要负责加载扩展包(e x t),系统类加载器主要负责加载我们的Java 文件 以及第三方j a r包

  双亲委托机制原理:

  1.当系统加载器加载一个class 的时候,他自己不会自己器加载这个类,而是把这个类加载的请求交给他的父加载器(扩展加载器)ExtClassLoader 去完成;

  2.到了扩展加载器加载时候,他首先也不会自己去尝试加载这个类,而是又把这个类加载请求交给你它的父加载器(引导加载器)去完成

  3.然后到了引导加载器加载的时候,如果加载未找到,则会让它下级ExtClassLoader 加载;

  4.如果ExtClassLoader也加载失败,则就使用系统加载器进行加载,系统记载器如果没有发现,就会抛出ClassNotFound 异常

  为什么会设计这种双亲委托机制进行类的加载:

  为了安全,为了防止外部恶意进行自定义java 类库的类,达到安全的作用以及优先级作用

(3).线程池:

  创建线程池又如下参数需要进行配置: 

    corePoolSize:核心线程数
    
maximumPoolSize:最大线程数
    keepAliveTime:线程存活的时间
    workQueue:任务队列
    threadFactory:线程工厂
    handler:在队列满了以及线程数已经到达了最大线程数的时候,触发此handler

  线程池要与数据库连接池要区别开,线程池这里的实现是如果我的Runnable 任务能够被队列一直容纳的话,线程的数量始终是核心线程的数量,只有在队列满了

之后,才会进行创建新的线程<最大线程数;这个是要注意的;

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {....}

  举个例子:如下代码,只会创建两个核心线程,剩余的4个任务是放到了LinkedBlockingQueue 中;

public static void main(String[] args) {
        /**
         * 核心线程为2
         * 最大线程为6
         * LinkedBlockingQueue 容纳 Integer.MAX_VALUE个任务
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 6,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());


        pool.submit(()-> fs());
        pool.submit(()-> fs());
        pool.submit(()-> fs());
        pool.submit(()->fs());
        pool.submit(()->fs());
        pool.submit(()->fs());
        pool.submit(()->fs());
        pool.submit(()->fs());





    }
    
    public static void fs(){
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        }catch (Exception E){

        }

    }

 (4).maven 重要的使用:

1.查看项目完整的依赖数:mvn dependency:tree -Dverbose
2.查看依赖树中包含某个groupId和artifactId的依赖链 mvn dependency:tree -Dverbose -Dincludes=com.alibaba:ee-article
3.本地包打包 mvn install:install-file -Dfile=sql_parser-1.0.0.jar -DgroupId=com.cys -DartifactId=sql-parser -Dversion=4.0 -Dpackaging=jar

(5).git常用命令:

场景:在切换分支,想要保存原有分支修改未提交的文件;在切换回来的时候还原
git stash save "name": 将未提交的文件保存到仓库里并命名;
git stash list :查看仓库保存的文件;
git stash pop stash@{0} :仓库的文件被弹出恢复根据stash list 索引

 git 版本会退:

  //查看日志

  git reflog --pretty=oneline

 git reset --hard 目标版本号

(6).SL4J 日志框架体系(使用slf4j 进行统一日志管理):

  1. SLF4J 是Java 日志的门面,用于统一管理java 混乱的日志框架与项目日志框架不统一的问题,并不提供日志系统的统一实现

  2.SLF4J 的具体实现有slf4j-simple、logback,要使用log4j 需要使用slf4j-log4j12来实现slf4j;

  3.jcl-over-slf4j ,log4j-over-slf4j, jul-to-slf4j 用于替换commons-logging, log4j, java util logging 原有实现;

(7) .java  默认的环境变量:



 (8).Cookie 与Session 的区别见解:

  http协议是无状态的,当我们开发一些有状态的接口时候,cookie 与session 弥补了这一块的能力

  对于http 协议中,cookie 只是请求头当中的一个字段,与其他请求头没有多大的区别

  浏览器对cookie 做了默认的支持,同时也限制了cookie ,比如同源策略,同源策略是浏览器的一种安全机制,限制的同域(相同的域名与端口)才能访问cookie 的内容,在做sso单点登录的时候,会把cookie 放到一级域名下面

  session 是服务器为每一个web 用户分配的独立的状态存储空间,(后端集群中,session信息存放到redis 或者db 中)

(9).高并发大流量思路:

  系统的瓶颈取决于你系统中性能最差的模块,最容易出现系统瓶颈的是io 即磁盘io 和网络io ,磁盘io 就是我们一般的调用非内存型数据库,mysql oracle 等,网络io 就是我们系统之间的rpc 调用,如何优化于架构,大致3个思路:

  1.性能不够,机器来凑。

  2.移花接木。 将数据的冷热进行分离,热数据放在缓存系统如redis 中,冷数据则落盘在mysql 中,对于团队秒杀等系统,常见做法就是锁以及串行无锁化(mq)

  3.火影鸣人影分身。数据量很大,可以通过分库分表分实例,读写分离。

  (10). Redis:

  为什么速度快 : 1. redis 是纯内存的操作  2.单线程,避免了cpu 频繁切换上下文 3.IO 多路复用机制。

  缓存移除策略:1.volatile-lru  设置了expire 的key ,优先删除最少使用的key。

         2.allkeys-lru 所有的key 优先删除最少使用的key。

         3.volatile-random 随机删除设置了expire 的key。

         4. allkeys-random 所有的key 随机删除。

         5. volatile-ttl expire key 的剩余时间删除。

         6. noeviction 从来不删除。

         7.volatile-lfu :expire key 频繁使用删除。

         8.allkeys-lfu :所有key 频繁使用删除

  (11). zookeeper:

(12). 1.8 HotSpot jvm:

  1.jvm 从内存整体分为堆内存,元数据区和栈 三大块内容,堆内存是JVM 最大的一块有新生代和老年代组成,新生代又被分为Eden ,From Survivor ,TO Survivor 组成。 

  2. java 8 版本后移除了永久带,被元数据空间所替代,字符串常量池,静态变量等信息放入了堆中,元数据空间在本地内存中,,大小不受 jvm 的影响,而是受本地内存大小的影响。

(13).jvm 常用的诊断命令:

   1.jstat :查看jvm当前的一些内存以及gc 情况。

  2.jstack : 查看程序线程运行的信息。

(14). AQS  (队列 state CAS ) :

   1. 抽象队列同步器,是JUC 并发工具类都是基于AQS 实现的,比如CountDownLatch lock 等实现。

  2. AQS 通过内置的FIFO双向队列来完成线程的排队工作的,每一个任务都是一个Node 节点,节点中用waitStatus 表示线程的状态, 节点中标示了head 节点以及tail 节点,通过cas的操作来比较替换head 以及tail 节点来完成工作。

  3. AQS维护了一个state 状态值,通过cas 的操作进行获取以及修改这个值的状态,比如CountDownLatch 就是操作这个state 进行计数的,重入锁通过这个状态值进行锁的重入操作。

  4. 采用  LockSupport.park  以及unpark 的操作进行线程的阻塞与放行操作。

  (15).CAS 含义与意义:

   CAS : compareAndSwap ,比较并交换,基于冲突检测的乐观并发策略,要求操作与冲突检测两个步骤需要有原子性,不可分割,靠硬件来实现这种原子操作,依赖的是硬件指令级的发展。不管风险,先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据的确被争用,产生了冲突,进行补偿的措施,最常用的补偿措施是不断的重试。

   CAS 指令需要三个操作数,内存位置(变量地址),旧的预期值,准备设置的新值;不断尝试将一个新的值赋给自己,如果失败了,说明执行CAS 操作的时候,旧值已经发生改变,于是在此循环进行下一次操作,直到成功;

  (16).分布式事物:

  

  (17).数据库主键索引与普通索引的区别:

(18).索引失效的场景:

  (19).诊断sql:

(20)linux :

  (21) 分布式锁:

 分布式锁具备的条件: 1.高可用的获取锁与释放锁 2.高性能的获取锁与释放锁 3.分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行到 4.具备可重入性 5.具备锁的失效机制,防止死锁 6.具备非阻塞的特性.没有获取锁直接返回获取锁失败

 实现方案:

    1. 数据库锁的方式:

      通过建立唯一索引的方式,在多个请求同事提交到数据库,数据库保证只会有一个操作可以成功。

      但是失效时间需要自己去编码,否则会导致其他线程无法获取到次锁  ,强依赖数据库,一旦数据库挂掉,整个业务不可用。

    2.redis 锁方式(目前常用redisson):

      通过设置key 以及失效时间的方式进行实现redis 锁,且redis 设置key 以及失效时间是同步原子方式一起执行的。

    3.zookeeper 锁的方式:

   (22).IO 多路复用:

  多路指的是多个网络连接,复用指的是复用一个或更少的线程,可以让单个线程高效的处理多个连接请求,使用 系统 select poll epoll  进行管理多个网络请求,高级之处在于可以同时等待多个文件描述符,这些文件描述符其中的人一个进入读就绪状态,select() 函数就返回了。

  select poll   epoll  区别(https://www.cnblogs.com/zhaodahai/p/6831456.html):

    select 默认支持的文件描述符有限制,poll 没有最大文件描述符的限制 ,select 与poll(链表) 需要不断的轮询文件描述符,线性扫描,且需要将文件描述符信息从用户态往内核态拷贝一次。  

    epoll 则使用的是事件通知型的机制,通过callback 机制来激活文件描述符,非轮询的方式,只有活跃可用的文件描述符才会进行回调,只管活跃连接,跟连接总数无关。

  

(23). Spring 的 理解:

  1 .Spring 运行首先先创建一个ApplicationContext 容器 

  2. 容器里维护了一个BeanFactory,这个BeanFactory 维护了Spring 运行中所有的bean实例对象,每一个bean 都有一个BeanDefinition 来描述类的信息,进行后续初始化。

  3. bean 的注册主要是通过实现  BeanFactoryPostProcessor 以及  BeanDefinitionRegistryPostProcessor 工厂钩子函数来实现自定义bean 的注册和修改,比如 ConfigurationClassPostProcessor spring 重要的注册bean 处理器,实现了包扫描,@bean @import 等bean 注册操作 完成了基本 90%的bean 的定义,我们定义的工厂处理器都是基于此进行扩展的。

  4. bean 属性注入以及AOP 实现主要是通过实现 BeanPostProcessor 以及扩展接口来实现的,我们可以通过这个工厂钩子来进行bean 的修改,属性注入等操作,比如AutowiredAnnotationBeanPostProcessor  , CommonAnnotationBeanPostProcessor 后置处理器,来完成@Autowrized,@Value 注入 ,@PostConstruct 等操作。

  5.通过实现 FactoryBean 可以让我们自定义 bean 的创建,真实的bean 对象通过getObject() 方法返回,通过这一特性可以实现接口的动态代理功能,比如mybatis 接口的动态代理以及dubbo reference 接口代理实现都是依赖这一特性来实现的。


(24) .Mybatis 的理解:

  1. Mybatis 通过 xml 解析技术进行解析我们的mapper 文件,解析后的对应关系全部放在Mybatis 维护的Configuration 容器中,并构造出 SqlsessionfFactoryBean。

  2.基于spring 包扫描的扩展进行自定义自己的包扫描策略,通过实现FactoryBean 的 MapperFactoryBean 来实现接口的动态代理与Congiguration 容器配合使用。

  3.Mybatis 一二级缓存机制,一级缓存默认是开启的,是基于 sqlSession (线程级别) 级别的缓存,随着sqlSession 销毁而不存在,二级缓存是基于Mapper(一个接口对应一个mapper)   级别的缓存;

(25). Netty 的理解:

   1. 理解 Netty 需要理解Reactor 模式,Netty 服务端的执行是 bossGroup 以及 childGroup 协调完成的,每个线程都是一个EventLoop 都维护着自己的Selector 进行循环 select 处理事件。

   2. NioEventLoop select 事件循环基本每一个操作系统都会支持,EpollEventLoop 是基于Linux 平台独有的,基于文件描述符(fd)进行事件的循环驱动,可以通过os.name 来编码判断选择使用的EventLoop 。  EpollEventLoop 的性能会更优一些,在连接数量多的场景。

   3. bossGroup 工作主要就是监听连接事件,一旦有新的连接接入(ServerBootstrapAcceptor),则会递交给childgroup 来进行后续的io 事件处理。

   4.事件的处理器都会放入pipeline 中,分为 inbound 处理器,outbound 处理器,分别处理读操作以及写的操作。pipeline 是一个链表结构,头节点以及末尾节点的handle Netty 已经帮我们实现,handle 的执行严格按照链表的顺序进行执行。

  5.Netty 的编解码器,所谓的编码就是在服务端我们需要传递的数据进行编码成 byteBuf,来进行底层传输,解码就是在客户接受端将 byteBuf  转换成为我们需要的数据格式,具体的数据结构有自己来定义,只需要解决的是网络传输的拆包与粘包(一般通过自定义头头方式进行解决)。

 (26). 数据库分库分表:

  垂直分表;拆分表的字段,冷热数据进行拆分到不同的表中。

  垂直分库:根据业务类型,把不同业务的表分配到不同的数据库中。

  水平分表:将表的数据拆分成若干个小表。

  水平分库:将表的数据拆分成若干个小表放到不同的数据库中。

  

  

  

  

原文地址:https://www.cnblogs.com/iscys/p/9865332.html