Java面试:朝花夕拾

SpringBoot
    Spring 组件一站式解决方案,简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

SpringBoot项目打包部署的两种方式
    1.jar包方式
        使用SpringBoot内置的tomcat启动,不需要外置的tomcat
        将jar包放到任意目录,执行Java -jar命令
    2.war方式
        在maven中排除自带的tomcat插件
        将项目打成war包,放入tomcat的webapps目录下面,启动tomcat

SpringBoot两大核心功能
    1.起步依赖:本质上是一个Maven项目对象模型,定义了对其他库的传递依赖(将具备某种功能的坐标打包到一起,并提供一些默认的功能)
    2.自动配置:SpringBoot的自动配置是一个运行时(应用程序启动时)的过程,由Spring自动完成的

Spring Boot 的核心注解
    @SpringBootApplication
        @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
        @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能。
        @ComponentScan:Spring组件扫描。

Spring Data JPA
    JPA:Java Persistence API,Java持久层API,本质上就是一种ORM规范

说说Spring 的核心 IOC 和AOP?
    IOC控制翻转:依赖对象的创建交由spring容器创建。只需要根据架构加上响应的注解即可例如控制层加@Controller注解 业务层加@Service 注解 数据访问层加@Respostory注解 。需要使用响应对象只需要声明属性用@Autowired注解或者@Resource注解即可
    AOP:面向切面。理解起来就是一个横切逻辑。可以动态的在目标方法之前或者之后进行预操作或者后处理行为。例如spring的AOP最典型的应用就是事物处理、日志记录、权限校验、资源池管理等。AOP可采用动态代理实现。例如jdk的动态代理。每次都会调用代理对象的invoke方法。可以重写此方法。在该方法中反射调用目标方法,然后可以在目标方法之前或之后做增强。传统的装饰者模式也可以。但是需要目标类和代理类需要实现相同的接口。 

mysql数据库类型
    1. int:整数类型
        * age int,
    2. double:小数类型
        * score double(5,2)
    3. date:日期,只包含年月日,yyyy-MM-dd
    4. datetime:日期,包含年月日时分秒     yyyy-MM-dd HH:mm:ss
    5. timestamp:时间戳类型    包含年月日时分秒     yyyy-MM-dd HH:mm:ss    
        * 如果不给这个字段赋值,或赋值为null,则默认使用当前的系统时间(数据库服务器),来自动赋值
    6. varchar:字符串
        * name varchar(20):姓名最大20个字符
        * zhangsan 8个字符  张三 2个字符
        
Sql语句书写顺序
    select--from--where--group by--having--order by 

执行顺序
    from--where--group by--having--select--order by
        from:需要从哪个数据表检索数据 
        where:过滤表中数据的条件 
        group by:如何将上面过滤出的数据分组 
        having:对上面已经分组的数据进行过滤的条件  
        select:查看结果集中的哪个列,或列的计算结果 
        order by :按照什么样的顺序来查看返回的数据 

SQL Select语句的执行顺序【从DBMS使用者角度】
    1、from子句组装来自不同数据源的数据; 
    2、where子句基于指定的条件对记录行进行筛选; 
    3、group by子句将数据划分为多个分组; 
    4、使用聚集函数进行计算; 
    5、使用having子句筛选分组; 
    6、计算所有的表达式; 
    7、使用order by对结果集进行排序。 

注意:having子句的执行在select之前,但在mysql中,group by之后都可以使用select中字段的别名

sql优化
    查询优化核心:避免全表扫描(在合适的字段上添加覆盖索引)
    合理的增加冗余的字段(减少表的联接查询)
    建表的时候能使用数字类型的字段就使用数字类型(type,status...),数字类型的字段作为条件查询比字符串的快
    尽可能的使用 varchar 代替 char 
    索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率
    where和having都可以使用优先用where(where先过滤,分组的时候数据就少了)
    避免全表扫描,不查询多余的列与行,首先应考虑在 where 及 order by 涉及的列上建立索引
    避免在索引列上使用计算。WHERE子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描
    避免在 where 子句中对字段进行 null 值判断
    避免在 where 子句中使用!=或<>操作符
    避免在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算
    避免在 where 子句中使用 or 来连接条件
    对于连续的数值,能用 between 就不要用 in

MySQL存储引擎:5版本之前默认的用myIsam,之后用innodb
    myIsam:不支持事务,表锁(维护简单,占用资源较少)
    innodb:支持事务,行锁(维护复杂,性能高,占用资源较多)

# JVM #
JVM内存结构,和Java虚拟机的运行时区域有关。
Java对象模型,和Java对象在虚拟机中的表现形式有关。
Java内存模型,和Java的并发编程有关。
    Java的多线程之间是通过共享内存进行通信的, 而由于采用共享内存进行通信, 在通信过程中会存在一系列如可见性、原子性、顺序性等问题, 而JMM(Java Memory Model)就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。它只是一个抽象的概念, 是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。JMM定义了一些语法集, 这些语法集映射到Java语言中就是volatile、synchronized等关键字。每个线程都有自己的本地内存空间(java栈中的帧)。线程执行时,先把变量从内存读到线程自己的本地内存空间,然后对变量进行操作。 对该变量操作完成后,在某个时间再把变量刷新回主内存。

Java虚拟机的多线程
    通过线程轮流切换并分配处理器执行时间的方式来实现,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令,因此,为了程序切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。

Java虚拟机运行时数据区
    程序计数器:当前线程所执行的字节码的行号指示器。(内存较小,线程私有)
        如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
    Java虚拟机栈:生命周期与线程相同,描述的是Java方法执行的内存模型。
        每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    本地方法栈:本地方法栈为虚拟机使用到的Native方法服务。
    Java堆:是Java虚拟机管理内存中最大的一块,被所有线程共享,在虚拟机启动时创建。
        此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这分配内存。Java堆中还可以细分:新生代和老生代,进一步划分的目的是为了更好地回收内存、更快地分配内存。Java堆可以处于物理上不连续的内存空间中,只要在逻辑上是连续的即可,在实现时,既可以实现成固定大小的,也可以是可扩展的(主流)。
    方法区:各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    运行时常量池:方法区的一部分,存放编译期生成的各种字面量和符号引用。

直接内存:不是Java虚拟机规范中定义的内存区域。
    在JDK1.4中新加入NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

JVM判断对象死亡
    引用计数算法
        给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加一,当引用失效时,计数器减一,计数器为0的对象就是不可能再被使用的。    
        优点:实现简单,判定效率高
        缺点:无法解决对象之间相互循环引用的问题
    可达性分析算法
        通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可以用。

回收方法区(HotSpot虚拟机把GC分代收集扩展至方法区,使用永久代来实现方法区)
    主要回收:废弃常量和无用的类
    判断是否废弃常量:没有任何String对象引用
    判断一个类是否是无用的类
        Java堆中不存在该类的任何实例
        加载该类的ClassLoader已经被回收
        该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
    虚拟机可以对无用类进行回收,但是不是和对象一样,不使用了就必然会回收

GC算法
    标记-清除算法(最基础的回收算法,后续的算法都是基于此的改进)
        首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
        效率问题:标记和清除两个过程的效率都不高
        空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致之后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
    复制算法
        将内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完就将还存活着的对象复制到另外一块上面,然后把已使用的清理掉。
        优点:每次都是对半个分区进行内存回收,内存分配时不用考虑内存碎片等复杂情况,只要移动堆栈指针,按顺序分配内存即可,实现简单,运行高效。
        缺点:可用内存缩小为原来的一半。对象存活率较高时要进行较多的复制操作,效率将会变低。
        用于回收新生代
            将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,回收时将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。
    标记-整理算法
        首先标记出所有需要回收的对象,然后将所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
    分代收集算法(*)
        根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老生代,根据各个年代的特点采用最适当的收集算法。新生代中,每次垃圾收集时都只有少量存活,那就选用复制算法,老年代中因为对象存活率高、没有额外空间对他进行分配担保,使用“标记-清理”或者“标记-整理”算法进行回收。

内存的分配担保
    如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。

垃圾收集器
    Java虚拟机规范中垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。

G1(Garbage-First)收集器:一款面向服务器端应用的垃圾收集器。
    并发和并行:充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间
    分代收集:采用不同的方式处理新对象和熬过多次GC的旧对象以获取更好的收集效果
    空间整合:整体上基于“标记-整理”,局部上基于“复制”,因此运行期间不会产生内存空间碎片,收集后能提供规整的可用内存
    可预测的停顿:除了追求低停顿之外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒

Minor GC(新生代GC)
Major/Full GC(老年代GC)

finally vs final vs finalize(不要使用)
    finalize()是Object中的方法,当垃圾回收器将要回收对象所占内存之前被调用,即当一个对象被虚拟机宣告死亡时会先调用它finalize()方法,让此对象处理它生前的最后事情(这个对象可以趁这个时机挣脱死亡的命运)。
    如何自我救赎:
        1.对象覆写了finalize()方法(这样在被判死后才会调用此方法,才有机会做最后的救赎);
        2.在finalize()方法中重新引用到”GC  Roots”链上(如把当前对象的引用this赋值给某对象的类变量/成员变量,重新建立可达的引用)
            finalize()只会在对象内存回收前被调用一次
            finalize()的调用具有不确定行,只保证方法会调用,但不保证方法里的任务会被执行完

线程安全与同步机制:volatile vs synchronized vs Lock(ReentrantLock)
    volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化而省略,且要求每次直接读值。(使变量在值发生改变时能尽快地让其他线程知道。)

volatile和synchronized区别
    1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
    2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
    3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
      《Java编程思想》上说,定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性。 
    4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
    5、当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,如Range类的lower和upper边界,必须遵循lower<=upper的限制。
    6、使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。

synchronized和lock区别
    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
    2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
    3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
    4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
    5)Lock可以提高多个线程进行读操作的效率。

同步代码块和同步函数的区别?
        *同步代码块使用的锁可以是任意对象。
        *同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

ThreadLocal:线程本地变量(资源消耗大)
    如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

线程池(底层实现)
    Java里面线程池的顶级接口是`java.util.concurrent.Executor`,但是Executor并不是一个线程池,而只是一个执行线程的工具。
    真正的线程池接口是`java.util.concurrent.ExecutorService`。
    `java.util.concurrent.Executors`线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。

常用的几种线程池:
    1.newFixedThreadPool
        创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
        线程池的大小一旦达到最大值就会保持不变.
    2.newSingleThreadExecutor
        创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
        如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
    3.newScheduleThreadPool
        创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
    4.newCachedThreadPool
        创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
        缺点:工作线程的创建数量几乎没有限制,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪

AbstractExecutorService实现了ExecutorService接口,实现了其中大部分的方法,ThreadPoolExecutor,继承了AbstractExecutorService,是ExecutorService的默认实现。
    public ThreadPoolExecutor(
    int corePoolSize,    //核心线程数
    int maximumPoolSize,  //线程池最大线程数
    long keepAliveTime,//表示线程没有任务执行时最多保持多久时间会终止
    TimeUnit unit,   //时间单位
    BlockingQueue<Runnable> workQueue, //一个阻塞队列,用来存储等待执行的任务
    ThreadFactory threadFactory,  //线程工厂,主要用来创建线程
    RejectedExecutionHandler handler) //表示当拒绝处理任务时的策略,默认是不处理,抛异常
    }

线程的三种创建方式
    继承 Thread 类,重写父类 run()方法
    实现 runnable 接口
    使用 ExecutorService、 Callable、 Future 实现有返回结果的多线程(JDK5.0 以后)

如何避免死锁?
    指定获取锁的顺序,并强制线程按照指定的顺序获取锁。如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁。

异步阻塞 IO(Java NIO):
    此种方式下是指应用发起一个 IO 操作以后,不等待内核 IO 操作的完成,等内核完成 IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问 IO 是否完成,那么为什么说是阻塞的呢?因为此时是通过 select 系统调用来完成的,而 select 函数本身的实现方式是阻塞的,而采用 select 函数有个好处就是它可以同时监听多个文件句柄(如果从 UNP 的角度看, select 属于同步操作。因为 select 之后,进程还需要读写数据),从而提高系统的并发性!
异步非阻塞 IO(Java AIO(NIO.2)):
    在此种模式下,用户进程只需要发起一个 IO 操作然后立即返回,等 IO 操作真正的完成以后,应用程序会得到 IO 操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的 IO 读写操作,因为真正的 IO 读取或者写入操作已经由内核完成了。

为什么要克隆?
    克隆的对象可能包含一些已经修改过的属性,保留着你想克隆对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值。即当需要一个新的对象来保存当前对象的“状态”就靠clone方法了
    浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
    深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。

如何实现克隆
    对象的类实现Cloneable接口;
    覆盖Object类的clone()方法 (覆盖clone()方法,访问修饰符设为public,默认是protected)
    在clone()方法中调用super.clone()

Set集合元素无序、不可重复。
    1、如果我们需要将元素排序, 那么使用TreeSet
    2、如果我们不需要排序, 使用HashSet, HashSet比TreeSet效率高
    3、如果我们需要保留存储顺序, 又要过滤重复元素, 那么使用LinkedHashSet
HashSet
    线程不安全,存取速度快
    底层实现是一个HashMap
    默认初始容量为16
    加载因子为0.75
    扩容增量:原容量的 1 倍
TreeSet
    排序规则制定
    元素自身具备比较性
    容器具备比较性


List集合元素有序、可重复。
Vector vs ArrayList
        ArrayList线程不安全,性能高,Vector线程安全,性能低
        ArrayList、Vector默认初始容量为10,负载因子为1,Vector扩容增量为原来的一倍,ArrayList扩容增量为原来的0.5倍。
        Vector可以由我们自己来设置增长的大小,ArrayList没有提供相关的方法

ArrayList vs LinkedList
    前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列
    当随机访问List时,ArrayList比LinkedList的效率更高
    当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高

CopyOnWriteArrayList、 CopyOnWriteArraySet
    CopyOnWrite 容器即写时复制的容器。当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对 CopyOnWrite 容器进行并发的读,因为当前容器不会添加任何元素。所以 CopyOnWrite 容器也是一种读写分离的思想,读和写不同的容器。
    内存占用问题:因为 CopyOnWrite 的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象,如果这些对象占用的内存比较大,那么这个时候很有可能造成频繁的 Yong GC 和 FullGC。
    数据一致性问题:CopyOnWrite 容器只能保证数据的最终一致性,不能保证数据的实时一致性。

HashMap和Hashtable的区别
    Hashtable是线程安全的,HashMap不是线程安全的。(Hashtable 所有的元素操作都是 synchronized 修饰的,而 HashMap 并没有。)
    Hashtable 性能较差,HashMap 性能较好。(如果要线程安全又要保证性能,建议使用 JUC 包下的 ConcurrentHashMap。)
    Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。Hashtable key 为 null 会直接抛出空指针异常,value 为 null 手动抛出空指针异常,而 HashMap 的逻辑对 null 作了特殊处理。
    Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
    HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
    HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 的 Enumerator 不是 fail-fast 的。所以,当其他线程改变了HashMap 的结构,如:增加、删除元素,将会抛出 ConcurrentModificationException (并发修改)异常,而 Hashtable 则不会。这同样也是 Enumeration 和 Iterator 的区别。

快速失败(fail-fast)和安全失败(fail-safe)区别
    快速失败和安全失败是对迭代器而言的。
    快速失败:当在迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModification异常,java.util下都是快速失败。 
    安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影响下层。在java.util.concurrent下都是安全失败。

ConcurrentHashMap的锁分段技术
    容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率
    ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

ConcurrentHashMap的get操作
    get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读,我们知道HashTable容器的get方法是需要加锁的,那么ConcurrentHashMap的get操作是如何做到不加锁的呢?原因是它的get方法里将要使用的共享变量都定义成volatile,如用于统计当前Segement大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。之所以不会读到过期的值,是根据java内存模型的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值,这是用volatile替换锁的经典应用场景。

ConcurrentHashMap的put操作
    由于put方法里需要对共享变量进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
    是否需要扩容。在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阀值,数组进行扩容。值得一提的是,Segment的扩容判断比HashMap更恰当,因为HashMap是在插入元素后判断元素是否已经到达容量的,如果到达了就进行扩容,但是很有可能扩容之后没有新元素插入,这时HashMap就进行了一次无效的扩容。
    如何扩容。扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再hash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式
JavaScript中JSON的使用
    json对象:json对象的属性可以用对象.属性调用。
        var a={"name":"tom","sex":"男","age":"24"};
    json字符串:是一个用引号引起来的字符串,因为符合json格式,所以叫做json字符串
        var b='{"name":"Mike","sex":"女","age":"29"}';
    JSON字符串和JSON对象的转换
        JSON.parse(string) :接收一个 JSON 字符串并将其转换成一个 JSON 对象。
        JSON.stringify(obj) :接收一个 JSON 对象并将其转换为一个 JSON 字符串。

计算机网络分为哪几层?
    OSI(Open System Interconnection,开放式系统互联)模型把网络通信的工作分为7层,分别是:应用层 表示层 会话层 传输层 网络层 数据链路层 物理层, TCP/IP协议簇分为四层:应用层 传输层 网络层 数据链路层,tcp对应传输层,ip对应网络层


TCP连接需要几次握手?几次分手?
TCP连接需要三次握手,四次分手。


配置AOP没有生效的问题
    spring和springMVC用的不是一个应用上下文(容器)
    AOP配置要写在springMVC的配置文件里

classpath 和 classpath* 区别
    classpath:只会到你指定的class路径中查找文件;
    classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找.

java中单继承和多实现的问题
        *如果一个类继承的父类和实现的父接口中有同名方法,子类可以不实现该方法,默认父类中的方法为实现方法。

为什么要有Runnable接口的出现?
        *通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。

Linux下重新获取IP
        *dhclient

java.util.Date和java.sql.Date的区别
        *java.util.Date是java.sql.Date的父类
        *java.sql.Date是针对SQL语句使用的,它只包含日期而没有时间部分,java.util.Date是在除了SQL语句的情况下面使用

序列化和反序列化
        *序列化:把对象转换为字节序列的过程称为对象的序列化
        *反序列化:把字节序列恢复为对象的过程称为对象的反序列化

对象序列化的作用
        *把内存中的对象保存到硬盘上
        *在网络上传送对象的字节序列(进程通信)

StringBuilder和StringBuffer应用场景
          *操作少量的数据使用String
        *单线程操作字符串缓冲区下大量数据使用StringBuilder(线程不安全)
        *多线程操作字符串缓冲区下大量数据使用StringBuffer(线程安全)

## 设计模式 ##
观察者模式(各种事件监听器)
策略模式(比较器Comparator)
迭代器模式(ArrayList等集合框架中的迭代器)
生产者消费者模式(消息队列)

单例模式
        *饿汉式:未实现延迟加载,线程安全(静态常量、静态代码块)
        *懒汉式:延迟加载,多线程下会有线程安全问题,使用同步锁后运行效率低下
        *双重校验锁: 实现了延迟加载,线程安全
        *静态内部类:线程安全,实现延迟加载
        *枚举:未延迟加载,线程安全,原生防止反射与反序列化击穿

装饰者模式
        *在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能(通过创建一个包装对象包裹真实的对象)
        应用场景:若想对一个类从不同角度进行功能扩展,例如java.io中,InputStream是一个抽象类,标准类库中提供了FileInputStream\ByteArrayInputStream等各种不同的子类,分别从不同角度对InputStream进行了功能扩展。这些不同的实现类其构造函数的输入均为InputStream(的实现类),然后对InputStream添加不同层次的逻辑,从而实现不同的功能,这就是装饰。

代理模式
        *静态代理:为其他对象提供一种代理以控制对这个对象的访问
        *动态代理:在程序运行时,通过反射机制动态地创建一个代理类

装饰者模式和代理模式的区别
        *装饰器模式侧重于对对象功能的增强,代理模式侧重于控制对对象的访问(目的不同)

模板方法模式
        *一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
        *当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

工厂模式
        *使用工厂方法代替new操作

构造者模式
        * 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示(方法返回当前对象进行链式调用)

工厂模式和构造者模式的区别
        *工厂模式注重的是整体对象的创建(关心整体),而构造者模式注重的是部件构建的过程,旨在通过一步一步的精确构造创建出一个复杂的对象(关心细节)

mybatis中用到的设计模式
        *工厂模式,代理模式,构造者模式

Tomcat如何创建Servlet
先到缓存中寻找有没有这个对象
    如果没有: 1、通过反射去创建相应的对象
              2、tomcat会把对象存放到缓存中 
              3、执行初始化方法init
    如果有该对象,直接获取到这个对象

【资源说明】 1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用!在使用过程中,如有问题或建议,请及时私信沟通。 2.主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 本文介绍了基于QEM(Quadric Error Metrics,二次误差度量)的优化网格简化算法的C和C++实现源码及其相关文档。这一算法主要应用于计算机图形学领域,用于优化三维模型的多边形数量,使之在保持原有模型特征的前提下实现简化。简化的目的是为了提高渲染速度,减少计算资源消耗,以及便于网络传输等。 本项目的核心是网格简化算法的实现,而QEM作为该算法的核心,是一种衡量简化误差的数学方法。通过计算每个顶点的二次误差矩阵来评估简化操作的误差,并以此来指导网格简化过程。QEM算法因其高效性和准确性在计算机图形学中广泛应用,尤其在实时渲染和三维打印领域。 项目代码包含C和C++两种语言版本,这意味着它可以在多种开发环境中运行,增加了其适用范围。对于计算机相关专业的学生、教师和行业从业者来说,这个项目提供了丰富的学习和实践机会。无论是作为学习编程的入门材料,还是作为深入研究计算机图形学的项目,该项目都具有实用价。 此外,项目包含的论文文档为理解网格简化算法提供了理论基础。论文详细介绍了QEM算法的原理、实施步骤以及与其他算法的对比分析。这不仅有助于加深对算法的理解,也为那些希望将算法应用于自己研究领域的人员提供了参考资料。 资源说明文档强调了项目的稳定性和可靠性,并鼓励用户在使用过程中提出问题或建议,以便不断地优化和完善项目。文档还提醒用户注意查看,以获取使用该项目的所有必要信息。 项目的文件名称列表中包含了加水印的论文文档、资源说明文件和实际的项目代码目录,后者位于名为Mesh-Simplification-master的目录下。用户可以将这些资源用于多种教学和研究目的,包括课程设计、毕业设计、项目立项演示等。 这个项目是一个宝贵的资源,它不仅提供了一个成熟的技术实现,而且为进一步的研究和学习提供了坚实的基础。它鼓励用户探索和扩展,以期在计算机图形学领域中取得更深入的研究成果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值