打卡知识圈

  • 多线程

    1、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing) ?

           答:线程调度器是一个操作系统服务 ,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它 ,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

     

    2、为什么Thread类的sleep()和yield()方法是静态的 ?

       答:Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

     

    3、volatile关键字在Java中有什么作用 ?

       答:当我们使用volatile关键字去修饰变量的时候,所有线程都会直接读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

    volatile是防止指令重排序来保证可见性对于JVM层面是防止编译器的重排序同时,对于有些cpu来说,他们会通过缓存锁或者中线索来解决缓存可见性但是,目前很多cpu都做了优化,因为缓存一致性MESI会带来性能开销,所以用到了storebuffer机制来做异步处理,而这种机制带来了指令的乱序执行。从而导致可见性问题。那么volatile会在cpu层面会增加内存屏障,来解决cpu的乱序执行带来的可见性问题

    4、什么是ThreadLocal?

      答: ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量,每个线程都会拥有他们自己的Thread变量,它们可以使用get)\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。

    5、什么是阻塞队列?如何使用阻塞队列来实现生产者消费者模型?

      答: java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞, 或者当队列是满时,往队列里添加元素的操作会被阻塞。阻塞队列不接受空值,当你尝试向队列中添加空值的时候 ,它会抛出NullPointerException。阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。BlockingQueue接口是java collections框架的一部分,它主要用于实现生产者消费者问题。

    6、什么是Callable和Future?

    答: Java 5在concurrency包中引入了java.util.concurrent.callable接口,它和Runnable接口很相似,但它可以返回一个对象或者抛出一个异常。

    Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果。java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。

    Dubbo

    1、Dubbo 的使用场景有哪些?

       答:透明化的远程方法调用:就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。

    软负载均衡及容错机制:可在内网替代F5等硬件负载均衡器,降低成本,减少单点。

    服务自动注册与发现:不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。

    2、Dubbo 核心功能有哪些?

      答:Remoting:网络通信框架,提供对多种NIO框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。

    Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

    Registry:服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

    3、Dubbo 核心组件有哪些?

      答:Provider:暴露服务的服务提供方

    Consumer:调用远程服务消费方

    Registry:服务注册与发现注册中心

    Monitor:监控中心和访问调用统计

    Container:服务运行容器

     

    4、Dubbo 服务器注册与发现的流程?

       答:Provider(提供者)绑定指定端口并启动服务。

    提供者连接注册中心,并把本机 IP、端口、应用信息和提供服务信息发送至注册中心存储。

    Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心。

    注册中心根据消费者所求服务信息匹配对应的提供者列表发送至 Consumer 应用缓存。

    Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。

    Provider 状态变更会实时通知注册中心、再由注册中心实时推送至 Consumer。

     

    5、Dubbo的集群容错方案有哪些?

      答:Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。

    Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

    Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

    Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

    Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。

    Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。

    默认的容错方案是 Failover Cluster。

     

    6、Dubbo集群提供了哪些负载均衡策略?

       答:Random LoadBalance:随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀。

    RoundRobin LoadBalance:轮循选取提供者策略,平均分布,但是存在请求累积的问题。

    LeastActive LoadBalance:最少活跃调用策略,解决慢提供者接收更少的请求。

    ConstantHash LoadBalance:一致性 Hash 策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动。

    默认为 Random 随机调用。

     

     

    redis

    1、redis和memcache有什么区别?

      答:redis是现在的企业使用最广泛缓存技术,而在redis以前memcache是一些公司最常   用的缓存技术,它们比较相似,但有如下一些区别:

    (1)redis相对于memcache来说拥有更丰富的数据类型,可以适用更多复杂场景。

    (2)redis原生就是支持cluster集群模式的,但memcache没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。

    (3)redis使用的是单核,memcache使用的是多核,所以redis在存储小数据的时候性能比较高,memcache在存储大一点的数据时候性能更好。

    (4)memcache在使用简单的key-value存储的时候内存利用率更高,但redis如果采用hash的结构来做存储,内存使用率会较好。

    2、redis的单线程模型

    答:redis基于reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器,它的组成结构为4部分:多个套接字(socket)、IO多路复用程序、文件事件分派器、事件处理器。因为这个文件处理器是单线程的,所以redis才叫单线程模型。

    在redis启动初始化的时候,redis会将连接应答处理器跟AE_READABLE事件关联起来,接着如果一个客户端跟redis发起连接,此时会产生一个AE_READABLE事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的套接字(socket),同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来。

    当客户端向redis发起请求的时候,首先就会在socket产生一个AE_READABLE事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据,然后进行执行和处理。

    接着redis这边准备好了给客户端的响应数据之后,就会将socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在socket上产生一个AE_WRITABLE事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入socket,供客户端来读取。命令回复处理器写完之后,就会删除这个socket的AE_WRITABLE事件和命令回复处理器的关联关系。

    3、为什么redis单线程模型的效率很高

       答:首先redis里的数据是存储在内存中,对redis里的数据操作的时候实际上是纯内存操作;其次,他的文件事件处理器的核心机制是非阻塞的IO多路复用程序;最重要的是单线程反而避免了多线程频繁上下文切换带来的损耗。

    4、redis的过期策略和内存淘汰机制

       答:在我们平常使用redis做缓存的时候,我们经常会给这个缓存设置一个过期时间,那么大家都知道如果我们在查过了过期时间的key时是不会有数据的,那么所有过期key数据已经被删除了吗?是如何删除的?

    其实如果一个key过期了,但是数据不一定已经被删除了,因为redis采用的是定期删除和惰性删除。定期删除是指redis默认会每隔100ms会随机抽取一些设置了过期时间的key检查是否过期了,如果过期了就删除。那么为什么不遍历删除所有的而是随机抽取一些呢?是因为可能redis中放置了大量的key,如果你每隔100ms都遍历,那么CPU肯定爆炸,redis也就GG了。那么这样的话,为什么去查过期的key的话会查不到?

    其实这就是redis的惰性删除,在你去查key的时候,redis会检查一下这个key是否设置了过期时间和是否已经过期了,如果是redis会删除这个key,并且返回空。

    那么这样的话岂不是出大问题了,如果过期了又没有去查这个key,垃圾数据大量堆积,把redis的内存耗尽了怎么办?

    其实当内存占用过多的时候,此时会进行内存淘汰,redis提供了如下策略:

    (1)noeviction:当内存不足以容纳新写入数据时,新写入数据会报错。

    (2)allkeys-lru:当内存不足以容纳新写入数据时,会移除最近最少使用的key。

    (3)allkeys-random:当内存不足以容纳新写入数据时,会随机移除某个key。

    (4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

    (5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

    (6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

    以上几个策略最常用的应该是allkeys-lru,其实这个也要根据业务场景去选择。

    5、redis的并发竞争问题以及解决方案

      答:当并发的去set某个值时可能结果与我们期望不同,这时我们就要去采取一些措施来避免。首先可以使用分布式锁来保证,这一块我在上面的双写一致性里面提到过,此处就不在赘述了。其次我们可以采用redis的CAS事务。Redis使用WATCH命令实现事务的“检查再设置”(CAS) 行为。作为WATCH命令的参数的键会受到Redis的监控,Redis能够检测到它们的变化。在执行EXEC命令之前,如果Redis检测到至少有一个键被修改了,那么整个事务便会中止运行,然后EXEC命令会返回一个Null值, 提醒用户事务运行失败。

    6、缓存雪崩和穿透

       笞:缓存雪崩是发生在高并发的情况下,如果redis宕机不可用时大量的请求涌入数据库,导致数据库崩溃以至于整个系统不可用,在这种情况下数据库会直接无法重启,因为起来会被再次打挂。所以要想避免缓存雪崩可以考虑采用以下措施,首先尽量保证redis的高可用(可以通过主从+哨兵或者redis cluster来提供高可用),其次可以使用一些限流降级组件(例如hystrix) 来避免mysq|被打挂,最后如果不是分布式系统可以考虑本地缓存一些数据。

    缓存穿透发生在一些恶意攻击下,在有些恶意攻击中会伪造请求来访问服务器,由于伪造的错误的数据在redis中不能找到,所以请求直接打到了数据库,导致了高并发直接把mysq|打挂,同缓存雪崩一-样,在这种情况下即使重启数据库也不能解决问题。想避免这种问题可以考虑采用以下措施,首先对请求过来的参数做合法性校验,例如用户id不是负数;其次,可以考虑如果有参数频繁的访问数据库而不能查询到,可以在redis给它搞个空值,避免请求直接打在数据库。

    database

    1:请简洁描述 MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别?

    答:

    1)、读未提交,一个事务可以读取到其他事务未提交的数据,会出现脏读的问题

    2)、读已提交,一个事务不可以读取到其他事务未提交的数据,但是可以读取到其他事务已提交的数据,会出现不可重复读的问题

    3)、可重复读,在一个事务中,前后两次读取的数据是一致的,在InnoDB中通过间隙锁解决了幻读的问题

    4)、串行化,事务排队执行,不存在并发的问题,并发效率最低。

    2. 在MySQL中ENUM的用法是什么?

       ENUM是一个字符串对象,用于指定一组预定义的值,并可在创建表时使用。SQL语法如下:

      Create table size(name ENUM('Smail,Medium','L arge');

     3. CHAR和VARCHAR的区别?

        CHAR和VARCHAR类型在存储和检索方面有所不同。CHAR列长度固定为创建表时声明的长度,长度值范围是1到255。当CHAR值被存储时,它们被用空格填充到特定长度,检索CHAR值时需删除尾随空格。

     4.列的字符串类型可以是什么?字符串类型是:

    SET

    BLOB

    ENUM

    CHAR

    TEXT

    VARCHAR

    5. MySQL中使用什么存储引擎?

       MyISAM InnoDB MEMORY MERGE ARCHIVE

    6. TIMESTAMP在UPDATE CURRENT _TIMESTAMP数据类型上做什么?

      创建表时TIMESTAMP列用Zero更新。只要表中的其他字段发生更改,UPDATE

      CURRENT_ TIMESTAMP修饰符就将时间戳字段更新为当前时间。

    jvm

    一.Java 类加载过程?

    Java 类加载需要经历一下 7 个过程:

    1. 加载

    加载是类加载的第一个过程,在这个阶段,将完成一下三件事情:

    • 通过一个类的全限定名获取该类的二进制流。

    • 将该二进制流中的静态存储结构转化为方法去运行时数据结构。

    • 在内存中生成该类的 Class 对象,作为该类的数据访问入口。

    2. 验证

    验证的目的是为了确保 Class 文件的字节流中的信息不回危害到 虚拟机.在该阶段主要完成以下四钟验证:

    • 文件格式验证:验证字节流是否符合 Class 文件的规范,如 主次版本号是否在当前虚拟机范围内,常量池中的常量是否 有不被支持的类型.

    • 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。• 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。

    • 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

    3. 准备

    准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。public static int value=123;//在准备阶段 value 初始值为 0 。在初始化阶段才会变为 123 。

    4. 解析

    该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

    5. 初始化

    初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java 程序代码。

    6. 使用

    7. 卸载

    二.描述一下 JVM 加载 Class 文件的原理机制?

    Java 语言是一种具有动态性的解释型语言,类(Class)只有被加载到 JVM 后才能运行。当运行指定程序时,JVM 会将编译生成的 .class 文件按照需求和一定的规则加载到内存中,并组织成为一个完整的 Java 应用程序。这个加载过程是由类加载器完成,具体来说,就是由ClassLoader 和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。类的加载方式分为隐式加载和显示加载。隐式加载指的是程序在使用 new 等方式创建对象时,会隐式地调用类的加载器把对应的类加载到 JVM 中。显示加载指的是通过直接调用 class.forName()方法来把所需的类加载到 JVM 中。任何一个工程项目都是由许多类组成的,当程序启动时,只把需要的类加载到 JVM 中,其他类只有被使用到的时候才会被加载,采用这种方法一方面可以加快加载速度,另一方面可以节约程序运行时对内存的开销。此外,在 Java 语言中,每个类或接口都对应一个 .class 文件,这些文件可以被看成是一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。在 Java 语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到 JVM 中,至于其他类,则在需要的时候才加载。类加载的主要步骤:

    • 装载。根据查找路径找到相应的 class 文件,然后导入。

    • 链接。链接又可分为 3 个小步:

    • 检查,检查待加载的 class 文件的正确性。

    • 准备,给类中的静态变量分配存储空间。

    • 解析,将符号引用转换为直接引用(这一步可选)

    • 初始化。对静态变量和静态代码块执行初始化工作。

    三 Java 内存分配。

    • 寄存器:我们无法控制。

    • 静态域:static 定义的静态成员。

    • 常量池:编译时被确定并保存在 .class 文件中的(final)常量值和一些文本修饰的符号引用(类和接口的全限定名,字段的名称和描述符,方法和名称和描述符)。

    • 非 RAM 存储:硬盘等永久存储空间。• 堆内存:new 创建的对象和数组,由 Java 虚拟机自动垃圾回收器管理,存取速度慢。

    • 栈内存:基本类型的变量和对象的引用变量(堆内存空间的访问地址),速度快,可以共享,但是大小与生存期必须确定,缺乏灵活性。

    1. Java 堆的结构是什么样子的?什么是堆中的永久代(Perm Gen

    space)?

    JVM 的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在 JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些 对象回收掉之前,他们会一直占据堆内存空间。

    四.GC 是什么? 为什么要有 GC?

       GC 是垃圾收集的意思(GabageCollection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。

    五. 简述 Java 垃圾回收机制。

       在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

    六. 如何判断一个对象是否存活?(或者 GC 对象的判定方法)

    判断一个对象是否存活有两种方法:

    1. 引用计数法

       所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收. 引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A、B 对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。

    2. 可达性算法(引用链法)该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,

       如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。在 Java 中可以作为 GC Roots 的对象有以下几种:

    • 虚拟机栈中引用的对象

    • 方法区类静态属性引用的对象

    • 方法区常量池引用的对象

    • 本地方法栈 JNI 引用的对象

    虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象比不一定会被回收。当一个对象不可达 GC Root时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记. 如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize() 方法。当对象没有覆盖 finalize() 方法或者已被虚拟机调用过,那么就认为是没必要的。 如果该对象有必要执行finalize() 方法,那么这个对象将会放在一个称为 F-Queue 的对队列中,虚拟机会触发一个 Finalize() 线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果finalize() 执行缓慢或者发生了死锁,那么就会造成 F-Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行第二次被标记,这时,该对象将被移除” 即将回收”集合,等待回收。

    一丶垃圾回收的优点和原理。并考虑 2 种回收机制。

        Java语言中一个显著的特点就是引入了垃圾回收机制,使 C++ 程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。

     

    二、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

        对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap) 中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为“不可达”时,GC就有责任回收这些内存空间。

    可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

    三、Java中会存在内存泄漏吗,请简单描述

        所谓内存泄露就是指一个不再被程序使用的对象或变量-直被占据在内存中。Java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java使用有向图的方式进行垃圾回收管理,可以消除弓引|用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的,例如下面的代码可以看到这种情况的内存回收:

    import java.io.lOException;

    public class GarbageTest {

    /**

     * @param args

     * @throws IOException

    */

    public static void main(String[] args) throws IOException{

     // TODO Auto-generated method stub

    try {

    gcTest();

      }catch (lOException e) {

    //TODO-Auto-generated-catch-block

    e.printStackTrace();

    }

    System.out.println("has exited gcTest!");

    System.in.read();

    System.in.read();

    System.out.println("out begin gc!");

    for(int i=0;i<100;i++){

    System.gc();

    System.in.read();

    System.in.read();

    }}

    private static void gcTest() throws lOException{

    System.in.read();

    System.in.read();

    Person p1=new Person();

    System.in.read();

    System.in.read();

    Person p2 = new Person();

    p1.setMate(p2);

    p2.setMate(p1);

    System.out.println("before exit gctest!");

    System.in.read();

    System.in.read();

    System.gc();

    System.out.println("exit gctest!");

    }

    private static class Person

    {

    byte[] data = new byte[ 20000000];

    Person mate = null;

    public void setMate(Person other){

     mate = other;

    }}}

    Java中的内存泄露的情况:

    长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。

    检查Java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。

    如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

     

    下面内容来自于网上(主要特点就是清空堆栈中的某个元素,并不是彻底把它从数组中拿掉,而是把存储的总数减少,本人写得可以比这个好,在拿掉某个元素时,顺便也让它从数组中消失,将那个元素所在的位置的值设置为null 即可) :

    我实在想不到比那个堆栈更经典的例子了,以致于我还要引用别人

    的例子。

    下面的例子不是我想到的,是书上看到的,当然如果没有在书上看到,可能过一段时间我自己也想的到,可是那时我说是我自己想到的也没有人相信的。

    public class Stack {

    private Object[] elements=new Object[10];

    private int size = 0;

    public void push(Object e){

    ensureCapacity();

    elements[size++] = e;

    }

    public Object pop()(

    if( size == 0) throw new EmptyStackException();

    return elements[-- size];

    }

    private void ensureCapacity(){

    if(elements.length == size){

    Object[] oldElements = elements;

    elements = new Object[2 * elements.length+1];

    System.arraycopy(oldElements,0, elements, 0,size);

    }}}

    上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这是个对象是无法回收的,这个才符合了内存泄露的两个条件:无用,无法回收。但是就是存在这样的东西也不一定会导致什么样的后果

    如果这个堆栈用的比较少,也就浪费了几个K内存而已,反正我们的内存都上G了,哪里会有什么影响,再说这个东西很快就会被回收的,有什么关系。下面看两个例子。

    public class Bad{

    public static Stack s=Stack();static{

    s.push(new Object());

    s.pop();

     //这里有一个对象发生内存泄露

    s.push(new Object());

     // 上面的对象可以被回收了,等于

    //是自愈了

    }}

    因为是static,就一直存在到程序退出,但是我们也可以看到它有自愈功能,就是说如果你的Stack最多有100个对象,那么最多也就只有100个对象无法被回收其实这个应该很容易理解,Stack内部持有100个引用,最坏的情况就是他们都是无用的,

    因为我们一旦放新的进去,以前的引用自然消失!

    内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。

    四、深拷贝和浅拷贝。简单来讲就是复制、克隆。

    Person p=new Person(“张三”);

        浅拷贝就是对对象中的数据成员进行简单赋值,如果存在动态成员或者指针就会报错。

        深拷贝就是对对象中存在的动态成员或指针重新开辟内存空间。

     五、System.gc() 和Runtime.gc() 会做什么事情?

        这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。

    六、finalize() 方法什么时候被调用?析构函数(fnalization)的目的是什么?

        垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说flalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。

      那么finalize()究竟是做什么的呢?

    它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI (Java Native Interface)调用non-Java程序(C 或C++),fnalize() 的工作就是回收这部分的内存。

    1、finalize() 方法什么时候被调用?析构函数 (finalization) 的

    目的是什么?

    垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的 finalize() 方法 但是在 Java 中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说 filalize() 可能永远不被执行,显然指望它做收尾工作是靠不住的。 那么finalize() 究竟是做什么的呢? 它最主要的用途是回收特殊渠道申请的内存。Java 程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种 JNI(Java Native Interface)调用non-Java 程序(C 或 C++), finalize() 的工作就是回收这部分的内存。

    2、 如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占

    用的内存?

    不会,在下一个垃圾回收周期中,这个对象将是可被回收的。

    3、 什么是分布式垃圾回收(DGC)?它是如何工作的?

    DGC 叫做分布式垃圾回收。RMI 使用 DGC 来做自动垃圾回收。因为 RMI 包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC 使用引用计数算法来给远程对象提供自动内存管理。

    4、 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?

       吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。 而串行收集器对大多数的小应用(在现代处理器上需要大概 100M 左右的内存)就足够了。

    5、 在 Java 中,对象什么时候可以被垃圾回收?

        当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。

    6、 简述 Java 内存分配与回收策率以及 Minor GC 和 Major

    GC。• 对象优先在堆的 Eden 区分配

    • 大对象直接进入老年代

    • 长期存活的对象将直接进入老年代当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次Minor GC。Minor GC 通常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 Gc 的频率较高,回收速度比较快;Full GC/Major GC 发生在老年代,一般情况下,触发老年代 GC的时候不会触发 Minor GC,但是通过配置,可以在 Full GC 之前进行一次 Minor GC 这样可以加快老年代的回收速度。

    7、 JVM 的永久代中会发生垃圾回收么?

    垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。注:Java 8 中已经移除了永久代,新加了一个叫做元数据区的native 内存区。

    8、 Java 中垃圾收集的方法有哪些?

    标记 - 清除:这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:

    1. 效率不高,标记和清除的效率都很低;

    2. 会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次 GC 动作。

    复制算法:为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存交 Eden 区,其余是两块较小的内存区叫 Survior 区。每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,然后清除 Eden区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。(java 堆又分为新生代和老年代)

    标记 - 整理:该算法主要是为了解决标记 - 清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。分代收集:现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保。

    9.什么是类加载器?类加载器有哪些?

    实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

    主要有一下四种类加载器:

    ●启动类加载器(Bootstrap ClassL oader)用来加载Java核心类库,无法被Java程序直接引用。

    ●扩展类加载器(extensions class loader) :它用来加载Java的扩展库。Java虚拟机的实现会提供一一个扩展库目录。该类加载器在此目录里面查找并加载Java类。

    ●系统类加载器(system class loader) :它根据Java应用的类路径(CL ASSPATH)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过ClassL oader.getSystemClassL oader()来获取它。

    ●用户自定义类加载器,通过继承< java.lang.ClassL oader类的方式实现。

    10、类加载器双亲委派模型机制?

    当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载反馈给子类,由子类去完成类的加载。

    集合框架

    1. ArrayList 和 Vector 的区别。 
       这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素(本来题目问的与 hashset 没有任何关系,但为了说清楚 ArrayList 与 Vector 的功能,我们使用对比方式,更有利于说明问题)。接着才说ArrayList 与 Vector 的区别,这主要包括两个方面。 
    · 同步性: 
    Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。 
    备注:对于 Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住 Vector 与 Hashtable 是旧的,是 java 一诞生就提供了的,它们是线程安全 
    的,ArrayList 与 HashMap 是 java2 时才提供的,它们是线程不安全的。所以,我们讲课时先讲老的。
    · 数据增长: 
    ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,而ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的 1.5 倍)。ArrayList 与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。总结:即 Vector 增长原来的一倍,ArrayList 增加原来的 0.5 倍。 
    2. 说说 ArrayList,Vector, LinkedList 的存储性能和特性。 
    ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数 据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 由于使用了 synchronized 方法(线程安全)。通常性能上较 ArrayList 差,而 LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快 。 
    ArrayList 在查找时速度快,LinkedList 在插入与删除时更具优势。 
    3. 快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?Iterator 的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。 
    java.util 包下面的所有的集合类都是快速失败的,而 java.util.concurrent 包下面的 
    所有的类都是安全失败的。快速失败的迭代器会抛出 
    ConcurrentModificationException 异常,而安全失败的迭代器永远不会抛出这样的异常。

    4. hashmap的数据结构。

       在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap 也不例外。Hashmap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列“)

    5. HashMap的工作原理是什幺?

       Java中的HashMap是以鍵値対(key- -value) 的形式存儲元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和檢索元素。当凋用put()方法的吋候,HashMap 会汁算key的hash値,然后把键值対存偖在集合中合透的索引上。如果key已経存在了value会被更新成新値。

    HashMap的一些重要的特性是它的容量(capacity),負載因子(load factor)和抗容板限(threshold resizing)。

    6. HashMap什么时候进行扩容?

       当hashmap中的元素个数超过数组大小loadFactor时就会进行数组扩容,loadFactor的默认值为0.75, 也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过160.75=12的时候,就把数组的大小扩展为216=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效

    的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000),但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000, hashmap也自动会将其设置为1024。但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000,也就是说为了让0.75 * size > 1000,我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

    7. List、Map、Set 三个接口,存取元素时,各有什么特点?这样的题属于随意发挥题:这样的题比较考水平,两个方面的水平:一是要真正明白这些内容,二是要有较强的总结和表述能力。如果你明白,但表述不清楚,在别人那里则等同于不明白。首先,List 与 Set 具有相似性,它们都是单列元素的集合,所以,它们有一个功共同的父接口,叫 Collection。Set 里面不允许有重复的元素,所谓重复,即不能有两个相等(注意,不是仅仅是相同)的对象 ,即假设 Set 集合中有了一个 A 对象,现在我要向 Set 集合再存入一个 B 对象,但 B 对象与 A 对象 equals 相等,则 B 对象存储不进去,所以,Set 集合的 add 方法有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时,则返回 true,当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加入该元素,返回结果为 false。

    Set 取元素时,没法说取第几个,只能以 Iterator 接口取得所有的元素,再逐一遍历各个元素。List 表示有先后顺序的集合, 注意,不是那种按年龄、按大小、按价格之类的排序。

    当我们多次调用 add(Obj e) 方法时,每次加入的对象就像火车站买票有排队顺序一样,按先来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e) 方法,就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进 List 中,

    每调用一次 add 方法,这个对象就**入进集合中一次,其实,并不是把这个对象本身存储进了集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被add 多次时,即相当于集合中有多个索引指向了这个对象,如图 x 所示。List 除了可以以 Iterator 接口取得所有的元素,再逐一遍历各个元素之外,还可以调用get(index i) 来明确说明取第几个。Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:

    put(obj key,obj value),每次存储时,要存储一对 key/value,不能存储重复的key,这个重复的规则也是按 equals 比较相等。取则可以根据 key 获得相应的value,即 get(Object key) 返回值为 key 所对应的 value。另外,也可以获得所有的 key 的结合,还可以获得所有的 value 的结合,还可以获得 key 和 value 组合成的 Map.Entry 对象的集合。

    List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素, 内部排序。

    Map 保存 key-value 值,value 可多值。HashSet 按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode值的大小进行存储。例如,"abc" ---> 78,"def" ---> 62,"xyz" ---> 65 在hashSet 中的存储顺序不是 62,65,78,这些问题感谢以前一个叫崔健的学员提出,最后通过查看源代码给他解释清楚,看本次培训学员当中有多少能看懂源码。 LinkedHashSet 按插入的顺序存储,那被存储对象的 hashcode 方法还有什么作用呢?学员想想! hashset 集合比较两个对象是否相等,首先看 hashcode 方法是否相等,然后看 equals 方法是否相等。new 两个 Student 插入到 HashSet 中,看HashSet 的 size,实现 hashcode 和 equals 方法后再看 size。同一个对象可以在 Vector 中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。往 HashSet 中却加不了多次的。

     8. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用==还是equals()?它们有何区别?

          Set里的元素是不能重复的,元素重复与否是使用equaIs()方法进行判断的。

          equals()和==方法决定引用值是否指向同一对象equaIs()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。

          9.两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

    对。如果对象要保存在HashSet或HashMap中,它们的equals相等,那么,它们的hashcode值就必须相等。如果不是要保存在HashSet或HashMap,则与hashcode没有什么关系了,这时候hashcode不等是可以的,例如arrayL ist存储的对象就不用实现hashcode,当然,我们没有理由不实现,通常都会去实现的。

    10. heap和stack 有什么区别。

       Java的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间, 用于存储这个方法内部的局部变量,

    当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。

    堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用fnal修饰后,放在堆中,而不是栈中。

     11. Java集合类框架的基本接口有哪些?

          集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。

          Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:

          Collection:代表一组对象, 每一个对象都是它的子元素

          Set:不包含重复元素的Collection。

          List:有顺序的collection,并且可以包含重复元素。

          Map:可以把键(key)映射到值(value) 的对象,键不能重复。

    12. HashSet和TreeSet有什么区别?

        HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),

    remove(),contains(),TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(), contains() 方法的时间复杂度是O(logn)。

    13. HashSet 的底层实现是什么? 
    通过看源码知道 HashSet 的实现是依赖于 HashMap 的,HashSet 的值都是存储在 HashMap 中的。在 HashSet 的构造法中会初始化一个 HashMap 对象,HashSet 不允许值重复,因此,HashSet 的值是作为 HashMap 的 key 存储在HashMap 中的,当存储的值已经存在时返回 false。 
    14. LinkedHashMap 的实现原理? 
    LinkedHashMap 也是基于HashMap 实现的,不同的是它定义了一个 Entry header,这个 header 不是放在 Table 里,它是额外独立出来的。LinkedHashMap 通过继承 hashMap 中的 Entry,并添加两个属性 Entry before,after, 和 header 结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。LinkedHashMap 定义了排序模式 accessOrder,该属性为 boolean 型变量,对于访问顺序,为 true;对于插入顺序,则为false。一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。 
    15. 为什么集合类没有实现 Cloneable 和 Serializable 接口? 
        克隆 (cloning) 或者是序列化 (serialization) 的语义和含义是跟具体的实现相关的。 因此,应该 由集合类的具体实现来决定如何被克隆或者是序列化。 
    16. 什么是迭代器 (Iterator)? 
        Iterator 接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代 器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素, 但是不可以直接调用集合的 remove(Object Obj) 删除,可以通过迭代器的 remove() 方法删除。 
    17. Iterator 和 ListIterator 的区别是什么? 
    下面列出了他们的区别: 
    Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List。 
    Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。 
    ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
    18. 数组 (Array) 和列表 (ArrayList) 有什么区别?什么时候应该使用 Array 而不是 ArrayList? 
    Array 可以包含基本类型和对象类型,ArrayList 只能包含对象型。 
    Array 大小是固定的,ArrayList 的大小是动态变化的。 
    ArrayList 处理固定大小的基本数据类型的时候,这种方式相对比较慢。

    19. Java 集合类框架的最佳实践有哪些?

    · 假如元素的大小是固 定的,而且能事先知道,我们就应该用 Array 而不是ArrayList。

    有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置 初始容量来避免重新计算 hash 值或者是扩容。

    · 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的 ClassCastException。

    · 使用 JDK 提供的不变类 (immutable class) 作为 Map 的键可以避免为我们自己的类实现 hashCode()和 equals()方法。

    · 编程的时候接口优于实现。

    · 底层的集合实际上是空的情况下,返回长度是 0 的集合或者是数组,不要返回 null。

    20. Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是equals()?        它们有何区别?

    Set 里的元素是不能重复的,那么用 iterator() 方法来区分重复与否。equals() 是判读两个 Set 是否相等equals() 和 == 方法决定引用值是否指向同一对象 equals() 在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值

    21. Comparable 和 Comparator 接口是干什么的?列出它们的区别。

    Java提供了只包含一个 compareTo() 方法的 Comparable 接口。这个方法可以给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。

    Java 提供了包含 compare() 和 equals() 两个方法的 Comparator 接口。 compare() 方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals() 方法需要一个对象作为参数,它用来决定输入参数是否和 comparator 相等。只有当输入参数也是一个 comparator 并且输入参数和当前 comparator 的排序结果是相同的时 候,这个方法才返回 true。

    22. Collection 和 Collections 的区别。

    collection 是集合类的上级接口, 继承与它的接口主要是 set 和 list。

    collections 类是针对集合类的一个帮助类. 它提供一系列的静态方法对各种集合的搜索, 排序, 线程安全化等操作。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值