精华内容
下载资源
问答
  • 外话 如果你们觉得下载太慢了,告诉你们个方法~ 下载谷歌上网助手,点击Chrome右上角->更多工具->拓展程序,开启开发者模式,将文件拖进去 开启谷歌上网助手,进入谷歌商店搜索Astar软件,一般第一个就是,下载...

    1.下载Erlang语言开发包

    RabbitMQ是用Erlang编程语言进行开发,所以先下载安装Erlang语言开发包,后下载安装RabbitMQ
    Erlang官网下载地址:http://www.erlang.org/downloads
    根据不同的位系统选择不同的下载文件
    也可以选择其他OTP版本,不过如果RabbitMQ版本较新的话,最好选择较新的OTP版本,下载后默认选项安装即可,路径任意选择

    2.下载RabbitMQ

    RabbitMQ官网下载址:http://www.rabbitmq.com/install-windows.html
    在这里插入图片描述
    如果想要以前的版本,点击 这里 通往github下载

    我随机选择其中一个版本,这里提供了对Erlang语言包的要求
    在这里插入图片描述
    下载后默认选项安装即可,路径任意选择

    3.题外话

    如果你们觉得下载太慢了,告诉你们个方法~

    1. 下载谷歌上网助手,点击Chrome右上角->更多工具->拓展程序,开启开发者模式,将文件拖进去
    2. 开启谷歌上网助手,进入谷歌商店搜索Astar软件,一般第一个就是,下载下来再拖进拓展程序
    3. 然后开启Astar,下载RabbitMQ体验不一样的速度~

    这种软件很适合下载国外的东西~

    展开全文
  • Intellij IDEA下载插件太慢怎么办

    千次阅读 2021-02-22 17:08:41
    在使用Intellij IDEA下载插件时,常常会遇到插件市场和插件详情加载失败、插件下载速度太慢导致下载失败等等。这并不是因为Jetbrains的服务器在国外,导致访问速度很慢。实际上,Jetbrains在世界各地都有下载加速...

    一、下载插件速度慢的原因

    在使用Intellij IDEA下载插件时,常常会遇到插件市场和插件详情加载失败、插件下载速度太慢导致下载失败等等。这并不是因为Jetbrains的服务器在国外,导致访问速度很慢。实际上,Jetbrains在世界各地都有下载加速服务器,不过因为电脑自动查询到的网站服务器可能不是离自己最近的服务器,而访问较远的服务器速度较慢,所以下载很慢。只要能找到访问最快的服务器,就能快速地下载插件。

    二、如何加快下载速度

    (1)查询自己的网络服务提供商

    访问iP138查询网,查看自己的网络服务提供商并记下它。比如我访问该网站截图如下:
    我的网络服务提供商是移动(为保护隐私,部分内容打码)
    如果你已经知道自己的网络服务提供商就不需要这一步。

    (2)查找访问插件网站最快的IP

    Intellij IDEA的插件主页地址是https://plugins.jetbrains.com。打开网站测速 - 站长工具,将插件的主页地址填入输入框内,点击查看分析按钮。一段时间后,就可以看到全中国各个地区访问插件主页的速度。点击表头“总耗时”右侧的小按钮,令全国各地访问插件主页的总耗时按增长顺序排列,这样耗时最短、速度最快的行就在最上方。从表格中找到与自己网络服务提供商相同的行,记下对应的IP地址。下面是我测量的访问插件主页的速度的结果:
    我的网络服务提供商是移动,我记下的IP是54.192.23.52

    (3)在hosts文件中为plugins.jetbrains.com添加相关条目

    用文本编辑器打开C:\Windows\System32\drivers\etc\hosts文件。在文件最下方添加一行文字:
    [你记下的IP地址] plugins.jetbrains.com
    [你记下的IP地址]用你记下的IP地址代替,记住不要带上方括号,而且IP地址和plugins.jetbrains.com之间有空格。例如我在文件中添加的文字是:
    13.225.160.7 plugins.jetbrains.com
    注意:编辑hosts文件需要管理员权限。

    三、题外话

    这种方法并不只限于加快下载IDEA插件的速度,只要你知道网络资源的网址,而且该网络资源使用了下载加速服务器,都可以用这种方法。

    展开全文
  • ,求支招[img=https://forum.csdn.net/PointForum/ui/scripts/csdn/Plugin/003/onion/27.gif][/img]
  • 这本来就是老师留的一道思考~但是希望从这道...1. 在出发前整理的太慢 2. 出发前我得查一下怎么走,看一下目的地在哪个地方 3. 在出行的过程遇到堵车了 4. 车太慢,路太窄 5. 太远了无法直达,得转几次车

    这本来就是老师留的一道思考题~但是希望从这道题的答案给大家一个分析问题的思路,整理我们学过的网络知识。(当然里面也有一些相对细致的内容)

     

    其实网速慢无非就是类比出行么

    1.      在出发前整理的太慢

    2.      出发前我得查一下怎么走,看一下目的地在哪个地方

    3.      在出行的过程遇到堵车了

    4.      车太慢,路太窄

    5.      太远了无法直达,得转几次车

    6.      地方太远了车得开好久

    7.      中间由于特殊情况,干脆不想去了,就留在某个中转站了

    8.      到了目的地,发现今天宾馆都满了不接待了

     

    上面给大家举得例子不需要死记硬背,你只要动脑想一想你出门旅游会遇到哪些情况,基本上就不会差了。

    所以一般情况下,从微观上来讲,无非就是从排队时延,处理时延,传输时延,传播时延四个方面去思考变慢的原因,因为一个影响一个分组的传输就是由这几个时延来导致的。从宏观上来讲,就是比平时多执行了某些特别的操作,或者某些过程执行了多次,或是数据出发前就遇到了很多问题。

     

    下面呢我们就具体的分析一下原因:


    1.上网带宽不足

    所谓带宽,就是指理论上单位时间传输的数据量,这明显就是旅行中的马路宽度么,路越宽,能同时行走的车辆就越多。所以,本机上网的网络带宽的大小会限制你的上网速度,几年前一般都是2兆--4兆,现在理论上都到了10兆—20兆。为什么说理论上呢?有谁觉得自己家电脑真的到了那么快?

    这里呢,就再简单给大家普及一个名词——吞吐量,它是指在规定时间,空间以及网络路径一定的情况下,下载文件时实际获得的带宽。由于受用户计算机性能,网络设备质量,资源使用情况,信号衰减等多个原因,所以我们的网络速度要比理论上小的多。

     

    2.网络拥塞

    这个很明显就是堵车嘛~在网络高峰时期,通信链路上存在大量的分组,这样会严重影响网络报文的传输。按照日常生活的理解一般也就是造成延迟的结果,但是在网络中却不是这么简单的影响。如果网络拥塞,网络节点(就是主机或者路由器)的接收能力小于数据到达的速率,就会出现数据包的丢失,延时增加等情况。为了缓解网络信道的压力,网络协议设计时就制定了应对的措施,比如TCP的拥塞控制手段,在面对网络拥塞时,会迅速减小发送速率。

     

    3.服务器与客户端的硬件配置不足

    网络本身就是一个请求与应答的过程,所以数据无非就是在两个机器上流动着。你请求数据,这个请求就得在你的机器上经过一系列处理,通过应用层,运输层,网络层,链路层,物理层一层一层的传下去,这就类比出行前你得准备各种东西吧~

    而服务器发送数据也要先一层层的解析出你的请求是什么,然后又要经过一些处理把这个数据给客户端发出去,这个过程是相当复杂的。说这些就是想说,你的数据肯定是要在两个机器上进行各种处理的。

    从服务器上说,因为服务器要处理大量访问申请,所以必须要有足够大的运行空间和足够快的处理速度,一般的大一些网站的服务器都是服务器集群,有非常多的cpu和相当大的内存,这样才能保证能及时的接收并处理大量的客户端请求,对于大数据的处理,就是通过把任务分给多个节点去执行,最后把结果整合到一台机器上。

    而客户端的配置,包括CPU,内存等也会影响本地的软件运行性能,所以你觉得网速慢也很有可能是你机器的问题。

     

    4.DNS解析慢

    DNS解析一般来说还是很快的,如果你的本地DNS服务器缓存了对方的IP数据,那就几乎省略了这一步。DNS解析就像出行前要询问一下地方在哪,不过不像我们用一下百度地图就能查到,需要从根DNS服务器——顶级域——权威域一层层递归或是迭代来查询。

    每次访问都需要去用DNS来解析出IP地址,这一步虽然是基于UDP协议,但是仍然也需要多次完整的传输过程,所以DNS的解析速度是会影响上网速度的。总之,DNS的解析涉及到本地有没有缓存,查询的迭代次数(或者是递归几层)等原因。

     

    5.访问的服务器距离太远

    这点可以从最简单的传播速度上考虑,也可以进一步从节点数量上考虑。假设从中国北京传输信息到美国不需要其他节点,北京到上海也不需要其他节点,很明显传输到上海的要快,因为传播速度相同,距离远了,自然就慢了。但是实际上,并不是这样,北京到美国一个服务器,中间不知道要经历多少个节点,节点越多,就会增加各种处理时延,传输时延等,所以会严重影响上网速度,也就是为什么访问国外网的慢的一个重要因素。当然这也只是理论上的,因为访问国外网站要经过国内各种过滤,筛选,拦截解析等等,所以经常慢的不行,甚至无法访问。

     

    6.如果是浏览器访问慢的话,页面包含冗余的代码,或者有大的图片等资源文件都会影响访问速度

    因为网页需要按照一定的顺序执行html,css,js来布局和获取文件。进一步解释,由于js位于html文件的不同位置,js如果对网页的节点有重构行为,就会延迟网页的加载。如果大的图片先加载可能会让个页面加载的很慢,所以可以先加载整个布局,再去加载图片。这里面涉及html等文件的解析顺序,属于网站优化了~

     

    7.另外从服务器或客户端的防火墙等软件来看,也是导致速度变慢的一个原因,因为防火墙或是其他的一些安全软件需要对各个包进行解析处理。而软件的性能,复杂的功能等等,都会占用系统资源,拖慢运行速度,从而影响上网软件的执行速度。

    展开全文
  • 3w 字长文爆肝 Java 基础面试顶了!!!

    万次阅读 多人点赞 2021-04-08 09:35:49
    hey guys ,这不是也到了面试季了么,cxuan 又打算重新一下 Java 相关的面试,先从基础的开始吧,这些面试属于基础系列,不包含多线程相关面试和 JVM 相关面试,多线程和 JVM 的我放在后面了,下面不多说...

    hey guys ,这不是也到了面试季了么,cxuan 又打算重新写一下 Java 相关的面试题,先从基础的开始吧,这些面试题属于基础系列,不包含多线程相关面试题和 JVM 相关面试题,多线程和 JVM 的我放在后面了,下面不多说,搞起!

    Java 基础篇

    Java 有哪些特点

    • 并发性的: 你可以在其中执行许多语句,而不必一次执行它
    • 面向对象的:基于类和面向对象的编程语言。
    • 独立性的: 支持一次编写,到处运行的独立编程语言,即编译后的代码可以在支持 Java 的所有平台上运行。

    Java 的特性

    Java 的特性有如下这几点

    • 简单,Java 会让你的工作变得更加轻松,使你把关注点放在主要业务逻辑上,而不必关心指针、运算符重载、内存回收等与主要业务无关的功能。
    • 便携性,Java 是平台无关性的,这意味着在一个平台上编写的任何应用程序都可以轻松移植到另一个平台上。
    • 安全性, 编译后会将所有的代码转换为字节码,人类无法读取。它使开发无病毒,无篡改的系统/应用成为可能。
    • 动态性,它具有适应不断变化的环境的能力,它能够支持动态内存分配,从而减少了内存浪费,提高了应用程序的性能。
    • 分布式,Java 提供的功能有助于创建分布式应用。使用远程方法调用(RMI),程序可以通过网络调用另一个程序的方法并获取输出。您可以通过从互联网上的任何计算机上调用方法来访问文件。这是革命性的一个特点,对于当今的互联网来说太重要了。
    • 健壮性,Java 有强大的内存管理功能,在编译和运行时检查代码,它有助于消除错误。
    • 高性能,Java 最黑的科技就是字节码编程,Java 代码编译成的字节码可以轻松转换为本地机器代码。通过 JIT 即时编译器来实现高性能。
    • 解释性,Java 被编译成字节码,由 Java 运行时环境解释。
    • 多线程性,Java支持多个执行线程(也称为轻量级进程),包括一组同步原语。这使得使用线程编程更加容易,Java 通过管程模型来实现线程安全性。

    面向对象的特征有哪些

    面向对象的特征主要有三点

    • 封装:封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

    • 继承:继承指的是使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

    • 多态:多态是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

    JDK 和 JRE 有什么区别

    • JRE 的英文名称是 Java Runtime Environment,Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
    • JDK 的英文名称是 Java Development Kit,Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。

    这里还需要解释一下 JVM 是什么

    • JVM 的英文名称是 Java Virtual Machine,指的就是 Java 虚拟机。Java 虚拟机是跨平台实现的核心

    大致来说,JRE、JDK 和 JVM 的关系如下

    描述一下值传递和引用传递的区别

    要想真正理解的话,可以参考这篇文章 : https://www.zhihu.com/question/31203609

    简单理解的话就是

    值传递是指在调用函数时将实际参数复制一份到函数中,这样的话如果函数对其传递过来的形式参数进行修改,将不会影响到实际参数

    引用传递 是指在调用函数时将对象的地址直接传递到函数中,如果在对形式参数进行修改,将影响到实际参数的值。

    == 和 equals 区别是什么

    == 是 Java 中一种操作符,它有两种比较方式

    • 对于基本数据类型来说, == 判断的是两边的是否相等
    public class DoubleCompareAndEquals {
    
        Person person1 = new Person(24,"boy");
        Person person2 = new Person(24,"girl");
        int c = 10;
    
        private void doubleCompare(){
    
            int a = 10;
            int b = 10;
    
            System.out.println(a == b);
            System.out.println(a == c);
            System.out.println(person1.getId() == person2.getId());
    
        }
    }
    
    • 对于引用类型来说, == 判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域。
    private void equals(){
    
      System.out.println(person1.getName().equals(person2.getName()));
    }
    

    equals 是 Java 中所有对象的父类,即 Object 类定义的一个方法。它只能比较对象,它表示的是引用双方的值是否相等。所以记住,并不是说 == 比较的就是引用是否相等,equals 比较的就是值,这需要区分来说的。

    equals 用作对象之间的比较具有如下特性

    • 自反性:对于任何非空引用 x 来说,x.equals(x) 应该返回 true。
    • 对称性:对于任何非空引用 x 和 y 来说,若x.equals(y)为 true,则y.equals(x)也为 true。
    • 传递性:对于任何非空引用的值来说,有三个值,x、y 和 z,如果x.equals(y) 返回true,y.equals(z) 返回true,那么x.equals(z) 也应该返回true。
    • 一致性:对于任何非空引用 x 和 y 来说,如果 x.equals(y) 相等的话,那么它们必须始终相等。
    • 非空性:对于任何非空引用的值 x 来说,x.equals(null) 必须返回 false。

    Java 中的基本数据类型有哪些,各自占用多少字节

    在 Java 中,数据类型只有四类八种

    • 整数型:byte、short、int、long

    byte 也就是字节,1 byte = 8 bits,byte 的默认值是 0 ;

    short 占用两个字节,也就是 16 位,1 short = 16 bits,它的默认值也是 0 ;

    int 占用四个字节,也就是 32 位,1 int = 32 bits,默认值是 0 ;

    long 占用八个字节,也就是 64 位,1 long = 64 bits,默认值是 0L;

    所以整数型的占用字节大小空间为 long > int > short > byte

    • 浮点型

    浮点型有两种数据类型:float 和 double

    float 是单精度浮点型,占用 4 位,1 float = 32 bits,默认值是 0.0f;

    double 是双精度浮点型,占用 8 位,1 double = 64 bits,默认值是 0.0d;

    • 字符型

    字符型就是 char,char 类型是一个单一的 16 位 Unicode 字符,最小值是 \u0000 (也就是 0 ),最大值是 \uffff (即为 65535),char 数据类型可以存储任何字符,例如 char a = ‘A’。

    • 布尔型

    布尔型指的就是 boolean,boolean 只有两种值,true 或者是 false,只表示 1 位,默认值是 false。

    以上 x 位都指的是在内存中的占用。

    String 中的 equals 是如何重写的

    String 代表的是 Java 中的字符串,String 类比较特殊,它整个类都是被 final 修饰的,也就是说,String 不能被任何类继承,任何 修改 String 字符串的方法都是创建了一个新的字符串。

    equals 方法是 Object 类定义的方法,Object 是所有类的父类,当然也包括 String,String 重写了 equals 方法,下面我们来看看是怎么重写的

    • 首先会判断要比较的两个字符串它们的引用是否相等。如果引用相等的话,直接返回 true ,不相等的话继续下面的判断
    • 然后再判断被比较的对象是否是 String 的实例,如果不是的话直接返回 false,如果是的话,再比较两个字符串的长度是否相等,如果长度不想等的话也就没有比较的必要了;长度如果相同,会比较字符串中的每个 字符 是否相等,一旦有一个字符不相等,就会直接返回 false。

    下面是它的流程图

    这里再提示一下,你可能有疑惑什么时候是

    if (this == anObject) {
      return true;
    }
    

    这个判断语句如何才能返回 true?因为都是字符串啊,字符串比较的不都是堆空间吗,猛然一看发现好像永远也不会走,但是你忘记了 String.intern() 方法,它表示的概念在不同的 JDK 版本有不同的区分

    在 JDK1.7 及以后调用 intern 方法是判断运行时常量池中是否有指定的字符串,如果没有的话,就把字符串添加到常量池中,并返回常量池中的对象。

    验证过程如下

    private void StringOverrideEquals(){
    
      String s1 = "aaa";
      String s2 = "aa" + new String("a");
      String s3 = new String("aaa");
    
      System.out.println(s1.intern().equals(s1));
      System.out.println(s1.intern().equals(s2));
      System.out.println(s3.intern().equals(s1));
    
    }
    
    • 首先 s1.intern.equals(s1) 这个无论如何都返回 true,因为 s1 字符串创建出来就已经在常量池中存在了。

    • 然后第二条语句返回 false,因为 s1 返回的是常量池中的对象,而 s2 返回的是堆中的对象

    • 第三条语句 s3.intern.equals(s1),返回 true ,因为 s3 对象虽然在堆中创建了一个对象,但是 s3 中的 “aaa” 返回的是常量池中的对象。

    为什么重写 equals 方法必须重写 hashcode 方法

    equals 方法和 hashCode 都是 Object 中定义的方法,它们经常被一起重写。

    equals 方法是用来比较对象大小是否相等的方法,hashcode 方法是用来判断每个对象 hash 值的一种方法。如果只重写 equals 方法而不重写 hashcode 方法,很可能会造成两个不同的对象,它们的 hashcode 也相等,造成冲突。比如

    String str1 = "通话";
    String str2 = "重地";
    

    它们两个的 hashcode 相等,但是 equals 可不相等。

    我们来看一下 hashCode 官方的定义

    总结起来就是

    • 如果在 Java 运行期间对同一个对象调用 hashCode 方法后,无论调用多少次,都应该返回相同的 hashCode,但是在不同的 Java 程序中,执行 hashCode 方法返回的值可能不一致。
    • 如果两个对象的 equals 相等,那么 hashCode 必须相同
    • 如果两个对象 equals 不相等,那么 hashCode 也有可能相同,所以需要重写 hashCode 方法,因为你不知道 hashCode 的底层构造(反正我是不知道,有大牛可以传授传授),所以你需要重写 hashCode 方法,来为不同的对象生成不同的 hashCode 值,这样能够提高不同对象的访问速度。
    • hashCode 通常是将地址转换为整数来实现的。

    两个对象的 hashcode 相同,那么 equals 是否也一定为 true

    这个肯定是不一定的,举个非常简单的例子,你重写了 hashcode 方法,来算取余数,那么两个对象的 hashcode 很可能重复,但是两个对象的 equals 却不一定相同。

    就算你不重写 hashcode 方法,我给你一段代码示例

    String str1 = "通话";
    String str2 = "重地";
    System. out. println(String. format("str1:%d | str2:%d",  str1. hashCode(),str2. hashCode()));
    System. out. println(str1. equals(str2));
    

    上面两段代码的输出结果是

    str1:1179395 | str2:1179395
    false

    这两个字符串的 equals 并不相同。也就是说,就算是 hashcode 相同的字符串,equals 也有可能不同。

    String s1 = new String(“abc”) 在内存中创建了几个对象

    一个或者两个,String s1 是声明了一个 String 类型的 s1 变量,它不是对象。使用 new 关键字会在堆中创建一个对象,另外一个对象是 abc ,它会在常量池中创建,所以一共创建了两个对象;如果 abc 在常量池中已经存在的话,那么就会创建一个对象。

    详细请翻阅笔者的另外一篇文章 一篇与众不同的 String、StringBuffer、StringBuilder 详解

    String 为什么是不可变的、jdk 源码中的 String 如何定义的、为什么这么设计。

    首先了解一下什么是不可变对象,不可变对象就是一经创建后,其对象的内部状态不能被修改,啥意思呢?也就是说不可变对象需要遵守下面几条原则

    • 不可变对象的内部属性都是 final 的
    • 不可变对象的内部属性都是 private 的
    • 不可变对象不能提供任何可以修改内部状态的方法、setter 方法也不行
    • 不可变对象不能被继承和扩展

    与其说问 String 为什么是不可变的,不如说如何把 String 设计成不可变的。

    String 类是一种对象,它是独立于 Java 基本数据类型而存在的,String 你可以把它理解为字符串的集合,String 被设计为 final 的,表示 String 对象一经创建后,它的值就不能再被修改,任何对 String 值进行修改的方法就是重新创建一个字符串。String 对象创建后会存在于运行时常量池中,运行时常量池是属于方法区的一部分,JDK1.7 后把它移到了堆中。

    不可变对象不是真的不可变,可以通过反射来对其内部的属性和值进行修改,不过一般我们不这样做。

    static 关键字是干什么用的?谈谈你的理解

    static 是 Java 中非常重要的关键字,static 表示的概念是 静态的,在 Java 中,static 主要用来

    • 修饰变量,static 修饰的变量称为静态变量、也称为类变量,类变量属于类所有,对于不同的类来说,static 变量只有一份,static 修饰的变量位于方法区中;static 修饰的变量能够直接通过 类名.变量名 来进行访问,不用通过实例化类再进行使用。
    • 修饰方法,static 修饰的方法被称为静态方法,静态方法能够直接通过 类名.方法名 来使用,在静态方法内部不能使用非静态属性和方法
    • static 可以修饰代码块,主要分为两种,一种直接定义在类中,使用 static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用 static class xxx 来进行定义。
    • static 可以用于静态导包,通过使用 import static xxx 来实现,这种方式一般不推荐使用
    • static 可以和单例模式一起使用,通过双重检查锁来实现线程安全的单例模式。

    深入理解请参考这篇文章 一个小小的 static 还能难得住我?

    final 关键字是干什么用的?谈谈你的理解

    final 是 Java 中的关键字,它表示的意思是 不可变的,在 Java 中,final 主要用来

    • 修饰类,final 修饰的类不能被继承,不能被继承的意思就是不能使用 extends 来继承被 final 修饰的类。
    • 修饰变量,final 修饰的变量不能被改写,不能被改写的意思有两种,对于基本数据类型来说,final 修饰的变量,其值不能被改变,final 修饰的对象,对象的引用不能被改变,但是对象内部的属性可以被修改。final 修饰的变量在某种程度上起到了不可变的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为 final 变量进行赋值,有利于减少额外的同步开销。
    • 修饰方法,final 修饰的方法不能被重写。
    • final 修饰符和 Java 程序性能优化没有必然联系

    抽象类和接口的区别是什么

    抽象类和接口都是 Java 中的关键字,抽象类和接口中都允许进行方法的定义,而不用具体的方法实现。抽象类和接口都允许被继承,它们广泛的应用于 JDK 和框架的源码中,来实现多态和不同的设计模式。

    不同点在于

    • 抽象级别不同:类、抽象类、接口其实是三种不同的抽象级别,抽象程度依次是 接口 > 抽象类 > 类。在接口中,只允许进行方法的定义,不允许有方法的实现,抽象类中可以进行方法的定义和实现;而类中只允许进行方法的实现,我说的方法的定义是不允许在方法后面出现 {}
    • 使用的关键字不同:类使用 class 来表示;抽象类使用 abstract class 来表示;接口使用 interface 来表示
    • 变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。

    重写和重载的区别

    在 Java 中,重写和重载都是对同一方法的不同表现形式,下面我们针对重写和重载做一下简单的区分

    • 子父级关系不同,重写是针对子级和父级的不同表现形式,而重载是在同一类中的不同表现形式;
    • 概念不同,子类重写父类的方法一般使用 @override 来表示;重写后的方法其方法的声明和参数类型、顺序必须要与父类完全一致;重载是针对同一类中概念,它要求重载的方法必须满足下面任何一个要求:方法参数的顺序,参数的个数,参数的类型任意一个保持不同即可。

    构造器能否被重载,能否被重写?

    这道题考到你对于构造器的理解和认识。

    我们 Java 中创建一个对象其实就是调用了该对象的构造方法,比如下面代码

    InstanceObject IO = new InstanceObject() ; // 调用了无参构造方法
    InstanceObject IO = new InstanceObject(xxx) ; // 调用了有参数的构造方法
    

    而重载的概念是什么呢?

    它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,JVM 就会根据不同的参数样式,来选择合适的方法执行

    也就是说,重载的概念更多描述的是对相同命名的方法的不同描述。那么我们上面这段代码很显然就是重载了,因为名称相同,我可以通过有无参数来判断调用不同的构造方法来进行初始化。

    那么构造器能否被重写呢?这里我们先来看一下什么是重写

    重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。从重写的概念定义来说我们就知道构造器不能被重写了。

    首先,构造器没有返回值,第二点,构造器的名称必须和类名一致。

    你总不能在类 A 中写了 public A();在类 B 中也写 public A() 吧,这显然是不能通过编译的。

    byte的取值范围是多少,怎么计算出来的

    byte 的取值范围是 -128 -> 127 之间,一共是 256 个。一个 byte 类型在计算机中占据一个字节,那么就是 8 bit,所以最大就是 2^7 = 1111 1111。

    Java 中用补码来表示二进制数,补码的最高位是符号位,最高位用 0 表示正数,最高位 1 表示负数,正数的补码就是其本身,由于最高位是符号位,所以正数表示的就是 0111 1111 ,也就是 127。最大负数就是 1111 1111,这其中会涉及到两个 0 ,一个 +0 ,一个 -0 ,+0 归为正数,也就是 0 ,-0 归为负数,也就是 -128,所以 byte 的范围就是 -128 - 127。

    HashMap 和 HashTable 的区别

    相同点

    HashMap 和 HashTable 都是基于哈希表实现的,其内部每个元素都是 key-value 键值对,HashMap 和 HashTable 都实现了 Map、Cloneable、Serializable 接口。

    不同点

    • 父类不同:HashMap 继承了 AbstractMap 类,而 HashTable 继承了 Dictionary

    • 空值不同:HashMap 允许空的 key 和 value 值,HashTable 不允许空的 key 和 value 值。HashMap 会把 Null key 当做普通的 key 对待。不允许 null key 重复。

    • 线程安全性:HashMap 不是线程安全的,如果多个外部操作同时修改 HashMap 的数据结构比如 add 或者是 delete,必须进行同步操作,仅仅对 key 或者 value 的修改不是改变数据结构的操作。可以选择构造线程安全的 Map 比如 Collections.synchronizedMap 或者是 ConcurrentHashMap。而 HashTable 本身就是线程安全的容器。
    • 性能方面:虽然 HashMap 和 HashTable 都是基于单链表的,但是 HashMap 进行 put 或者 get􏱤 操作,可以达到常数时间的性能;而 HashTable 的 put 和 get 操作都是加了 synchronized 锁的,所以效率很差。

    • 初始容量不同:HashTable 的初始长度是11,之后每次扩充容量变为之前的 2n+1(n为上一次的长度)而 HashMap 的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。

    HashMap 和 HashSet 的区别

    HashSet 继承于 AbstractSet 接口,实现了 Set、Cloneable,、java.io.Serializable 接口。HashSet 不允许集合中出现重复的值。HashSet 底层其实就是 HashMap,所有对 HashSet 的操作其实就是对 HashMap 的操作。所以 HashSet 也不保证集合的顺序,也不是线程安全的容器。

    HashMap 的底层结构

    JDK1.7 中,HashMap 采用位桶 + 链表的实现,即使用链表来处理冲突,同一 hash 值的链表都存储在一个数组中。但是当位于一个桶中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。

    所以,与 JDK 1.7 相比,JDK 1.8 在底层结构方面做了一些改变,当每个桶中元素大于 8 的时候,会转变为红黑树,目的就是优化查询效率。

    HashMap 的长度为什么是 2 的幂次方

    这道题我想了几天,之前和群里小伙伴们探讨每日一题的时候,问他们为什么 length%hash == (n - 1) & hash,它们说相等的前提是 length 的长度 2 的幂次方,然后我回了一句难道 length 还能不是 2 的幂次方吗?其实是我没有搞懂因果关系,因为 HashMap 的长度是 2 的幂次方,所以使用余数来判断在桶中的下标。如果 length 的长度不是 2 的幂次方,小伙伴们可以举个例子来试试

    例如长度为 9 时候,3 & (9-1) = 0,2 & (9-1) = 0 ,都在 0 上,碰撞了;

    这样会增大 HashMap 碰撞的几率。

    HashMap 多线程操作导致死循环问题

    HashMap 不是一个线程安全的容器,在高并发场景下,应该使用 ConcurrentHashMap,在多线程场景下使用 HashMap 会造成死循环问题(基于 JDK1.7),出现问题的位置在 rehash 处,也就是

    do {
        Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了
        int i = indexFor(e.hash, newCapacity);
        e.next = newTable[i];
        newTable[i] = e;
        e = next;
    } while (e != null);
    

    这是 JDK1.7 的 rehash 代码片段,在并发的场景下会形成环。

    JDK1.8 也会造成死循环问题。

    HashMap 线程安全的实现有哪些

    因为 HashMap 不是一个线程安全的容器,所以并发场景下推荐使用 ConcurrentHashMap ,或者使用线程安全的 HashMap,使用 Collections 包下的线程安全的容器,比如说

    Collections.synchronizedMap(new HashMap());
    

    还可以使用 HashTable ,它也是线程安全的容器,基于 key-value 存储,经常用 HashMap 和 HashTable 做比较就是因为 HashTable 的数据结构和 HashMap 相同。

    上面效率最高的就是 ConcurrentHashMap。

    讲一下 HashMap put 的过程

    首先会使用 hash 函数来计算 key,然后执行真正的插入方法

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
      Node<K,V>[] tab; Node<K,V> p; int n, i;
      // 如果table 为null 或者没有为table分配内存,就resize一次
      if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
      // 指定hash值节点为空则直接插入,这个(n - 1) & hash才是表中真正的哈希
      if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
      // 如果不为空
      else {
        Node<K,V> e; K k;
        // 计算表中的这个真正的哈希值与要插入的key.hash相比
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
          e = p;
        // 若不同的话,并且当前节点已经在 TreeNode 上了
        else if (p instanceof TreeNode)
          // 采用红黑树存储方式
          e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // key.hash 不同并且也不再 TreeNode 上,在链表上找到 p.next==null
        else {
          for (int binCount = 0; ; ++binCount) {
            if ((e = p.next) == null) {
              // 在表尾插入
              p.next = newNode(hash, key, value, null);
              // 新增节点后如果节点个数到达阈值,则进入 treeifyBin() 进行再次判断
              if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                treeifyBin(tab, hash);
              break;
            }
            // 如果找到了同hash、key的节点,那么直接退出循环
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
              break;
            // 更新 p 指向下一节点
            p = e;
          }
        }
        // map中含有旧值,返回旧值
        if (e != null) { // existing mapping for key
          V oldValue = e.value;
          if (!onlyIfAbsent || oldValue == null)
            e.value = value;
          afterNodeAccess(e);
          return oldValue;
        }
      }
      // map调整次数 + 1
      ++modCount;
      // 键值对的数量达到阈值,需要扩容
      if (++size > threshold)
        resize();
      afterNodeInsertion(evict);
      return null;
    }
    

    HashMap put 方法的核心就是在 putval 方法,它的插入过程如下

    • 首先会判断 HashMap 中是否是新构建的,如果是的话会首先进行 resize
    • 然后判断需要插入的元素在 HashMap 中是否已经存在(说明出现了碰撞情况),如果不存在,直接生成新的k-v 节点存放,再判断是否需要扩容。
    • 如果要插入的元素已经存在的话,说明发生了冲突,这就会转换成链表或者红黑树来解决冲突,首先判断链表中的 hash,key 是否相等,如果相等的话,就用新值替换旧值,如果节点是属于 TreeNode 类型,会直接在红黑树中进行处理,如果 hash ,key 不相等也不属于 TreeNode 类型,会直接转换为链表处理,进行链表遍历,如果链表的 next 节点是 null,判断是否转换为红黑树,如果不转换的话,在遍历过程中找到 key 完全相等的节点,则用新节点替换老节点

    关于 HashMap 的深入理解请参考这篇文章 看完这篇 HashMap ,和面试官扯皮就没问题了

    ConcurrentHashMap 底层实现

    ConcurrentHashMap 是线程安全的 Map,它也是高并发场景下的首选数据结构,ConcurrentHashMap 底层是使用分段锁来实现的。

    Integer 缓存池

    Integer 缓存池也就是 IntegerCache ,它是 Integer 的静态内部类。

    image-20200608064905703

    它的默认值用于缓存 -128 - 127 之间的数字,如果有 -128 - 127 之间的数字的话,使用 new Integer 不用创建对象,会直接从缓存池中取,此操作会减少堆中对象的分配,有利于提高程序的运行效率。

    例如创建一个 Integer a = 24,其实是调用 Integer 的 valueOf ,可以通过反编译得出这个结论

    然后我们看一下 valueOf 方法

    如果在指定缓存池范围内的话,会直接返回缓存的值而不用创建新的 Integer 对象。

    缓存的大小可以使用 XX:AutoBoxCacheMax 来指定,在 VM 初始化时,java.lang.Integer.IntegerCache.high 属性会设置和保存在 sun.misc.VM 的私有系统属性中。

    UTF-8 和 Unicode 的关系

    由于每个国家都有自己独有的字符编码,所以Unicode 的发展旨在创建一个新的标准,用来映射当今使用的大多数语言中的字符,这些字符有一些不是必要的,但是对于创建文本来说却是不可或缺的。Unicode 统一了所有字符的编码,是一个 Character Set,也就是字符集,字符集只是给所有的字符一个唯一编号,但是却没有规定如何存储,不同的字符其存储空间不一样,有的需要一个字节就能存储,有的则需要2、3、4个字节。

    UTF-8 只是众多能够对文本字符进行解码的一种方式,它是一种变长的方式。UTF-8 代表 8 位一组表示 Unicode 字符的格式,使用 1 - 4 个字节来表示字符。

    U+ 0000 ~ U+ 007F: 0XXXXXXX
    U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
    U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
    U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
    

    可以看到,UTF-8 通过开头的标志位位数实现了变长。对于单字节字符,只占用一个字节,实现了向下兼容 ASCII,并且能和 UTF-32 一样,包含 Unicode 中的所有字符,又能有效减少存储传输过程中占用的空间。

    项目为 UTF-8 环境,char c = ‘中’,是否合法

    可以,因为 Unicode 编码采用 2 个字节的编码,UTF-8 是 Unicode 的一种实现,它使用可变长度的字符集进行编码,char c = ‘中’ 是两个字节,所以能够存储。合法。

    Arrays.asList 获得的 List 应该注意什么

    Arrays.asList 是 Array 中的一个静态方法,它能够实现把数组转换成为 List 序列,需要注意下面几点

    • Arrays.asList 转换完成后的 List 不能再进行结构化的修改,什么是结构化的修改?就是不能再进行任何 List 元素的增加或者减少的操作。
    public static void main(String[] args) {
      Integer[] integer = new Integer[] { 1, 2, 3, 4 };
      List integetList = Arrays.asList(integer);
      integetList.add(5);
    }
    

    结果会直接抛出

    Exception in thread "main" java.lang.UnsupportedOperationException
    

    我们看一下源码就能发现问题

    // 这是 java.util.Arrays 的内部类,而不是 java.util.ArrayList 
    private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable
    

    继承 AbstractList 中对 add、remove、set 方法是直接抛异常的,也就是说如果继承的子类没有去重写这些方法,那么子类的实例去调用这些方法是会直接抛异常的。

    下面是AbstractList中方法的定义,我们可以看到具体抛出的异常:

    public void add(int index, E element) {
      throw new UnsupportedOperationException();
    }
    public E remove(int index) {
      throw new UnsupportedOperationException();
    }
    public E set(int index, E element) {
      throw new UnsupportedOperationException();
    }
    

    虽然 set 方法也抛出了一场,但是由于 内部类 ArrayList 重写了 set 方法,所以支持其可以对元素进行修改。

    • Arrays.asList 不支持基础类型的转换

    Java 中的基础数据类型(byte,short,int,long,float,double,boolean)是不支持使用 Arrays.asList 方法去转换的

    Collection 和 Collections 的区别

    Collection 和 Collections 都是位于 java.util 包下的类

    Collection 是集合类的父类,它是一个顶级接口,大部分抽象类比如说 AbstractListAbstractSet 都继承了 Collection 类,Collection 类只定义一节标准方法比如说 add、remove、set、equals 等,具体的方法由抽象类或者实现类去实现。

    Collections 是集合类的工具类,Collections 提供了一些工具类的基本使用

    • sort 方法,对当前集合进行排序, 实现 Comparable 接口的类,只能使用一种排序方案,这种方案叫做自然比较
    • 比如实现线程安全的容器 Collections.synchronizedListCollections.synchronizedMap
    • reverse 反转,使用 reverse 方法可以根据元素的自然顺序 对指定列表按降序进行排序。
    • fill,使用指定元素替换指定列表中的所有元素。

    有很多用法,读者可以翻阅 Collections 的源码查看,Collections 不能进行实例化,所以 Collections 中的方法都是由 Collections.方法 直接调用。

    你知道 fail-fast 和 fail-safe 吗

    fail-fast 是 Java 中的一种快速失败机制,java.util 包下所有的集合都是快速失败的,快速失败会抛出 ConcurrentModificationException 异常,fail-fast 你可以把它理解为一种快速检测机制,它只能用来检测错误,不会对错误进行恢复,fail-fast 不一定只在多线程环境下存在,ArrayList 也会抛出这个异常,主要原因是由于 modCount 不等于 expectedModCount

    fail-safe 是 Java 中的一种 安全失败 机制,它表示的是在遍历时不是直接在原集合上进行访问,而是先复制原有集合内容,在拷贝的集合上进行遍历。 由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 ConcurrentModificationException。java.util.concurrent 包下的容器都是安全失败的,可以在多线程条件下使用,并发修改。

    ArrayList、LinkedList 和 Vector 的区别

    这也是一道老生常谈的问题了

    ArrayList、LinkedList、Vector 都是位于 java.util 包下的工具类,它们都实现了 List 接口。

    • ArrayList 的底层是动态数组,它是基于数组的特性而演变出来的,所以ArrayList 遍历访问非常快,但是增删比较慢,因为会涉及到数组的拷贝。ArrayList 是一个非线程安全的容器,在并发场景下会造成问题,如果想使用线程安全的容器的话,推荐使用 Collections.synchronizedList;ArrayList 在扩容时会增加 50% 的容量。
    • LinkedList 的底层是双向链表,所以 LinkedList 的增加和删除非常快,只需把元素删除,把各自的指针指向新的元素即可。但是 LinkedList 遍历比较慢,因为只有每次访问一个元素才能知道下一个元素的值。LinkedList 也是一个非线程安全的容器,推荐使用 Collections.synchronizedList
    • Vector 向量是最早出现的集合容器,Vector 是一个线程安全的容器,它的每个方法都粗暴的加上了 synchronized 锁,所以它的增删、遍历效率都很低。Vector 在扩容时,它的容量会增加一倍。

    Exception 和 Error 有什么区别

    Exception 泛指的是 异常,Exception 主要分为两种异常,一种是编译期出现的异常,称为 checkedException ,一种是程序运行期间出现的异常,称为 uncheckedException,常见的 checkedException 有 IOException,uncheckedException 统称为 RuntimeException,常见的 RuntimeException 主要有NullPointerExceptionIllegalArgumentExceptionArrayIndexOutofBoundException等,Exception 可以被捕获。

    Error 是指程序运行过程中出现的错误,通常情况下会造成程序的崩溃,Error 通常是不可恢复的,Error 不能被捕获。

    详细可以参考这篇文章 看完这篇 Exception 和 Error ,和面试官扯皮就没问题了

    String、StringBuilder 和 StringBuffer 有什么区别

    String 特指的是 Java 中的字符串,String 类位于 java.lang 包下,String 类是由 final 修饰的,String 字符串一旦创建就不能被修改,任何对 String 进行修改的操作都相当于重新创建了一个字符串。String 字符串的底层使用 StringBuilder 来实现的

    StringBuilder 位于 java.util 包下,StringBuilder 是一非线程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中 + 号的拼接效率高。StringBuilder 一般不用于并发环境

    StringBuffer 位于 java.util 包下,StringBuffer 是一个线程安全的容器,多线程场景下一般使用 StringBuffer 用作字符串的拼接

    StringBuilder 和 StringBuffer 都是继承于AbstractStringBuilder 类,AbstractStringBuilder 类实现了 StringBuffer 和 StringBuilder 的常规操作。

    深入理解可以参考一下这篇文章 深入理解 String、StringBuilder 和 StringBuffer

    动态代理是基于什么原理

    代理一般分为静态代理动态代理,它们都是代理模式的一种应用,静态代理指的是在程序运行前已经编译好,程序知道由谁来执行代理方法。

    而动态代理只有在程序运行期间才能确定,相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。可以说动态代理是基于 反射 实现的。通过反射我们可以直接操作类或者对象,比如获取类的定义,获取声明的属性和方法,调用方法,在运行时可以修改类的定义。

    动态代理是一种在运行时构建代理、动态处理方法调用的机制。动态代理的实现方式有很多,Java 提供的代理被称为 JDK 动态代理,JDK 动态代理是基于类的继承。

    理解动态代理可以参考这篇文章 动态代理深入理解

    动态代理的几种实现方式

    动态代理的实现方式我给你罗列四种,分别是

    • JDK 动态代理:JDK 动态代理也是 Java 动态代理,它是基于接口的动态代理,它通常代理接口下的所有类。
    • CGLIB 动态代理:CGLIB 动态代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,也就是说 CGLIB 动态代理采用类继承 -> 方法重写的方式进行的。
    • Javassist 代理:Javassist是在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类, 并在 JVM 加载时修改类文件。
    • ASM 代理:ASM 是一套 Java 字节码生成架构,它可以动态生成二进制格式的子类或其它代理类,或者在类被 Java 虚拟机装入内存之前,动态修改类。

    关于这四种字节码的实现可以参考 动态代理竟然如此简单!

    解释下 Serialization 和 Deserialization

    Serialization 表示的是序列化,实现序列化和反序列化的对象必须实现 serializable 接口,序列化是将对象变成字节流,存储到磁盘或网络中。反序列化是序列化的反过程

    我在学到序列化的时候就经常有个疑问,为什么对象需要序列化呢?

    为了数据传输的目的,因为这个对象在你代码中是对象格式,但是它在传输的过程中可不是对象格式,所以,当其他进程或者其他机器想要和你通信时,你们双方会发送各种类型的数据,也有可能会把这个对象发送过去,所以这个对象势必会转换为能够在网络中或者磁盘中能够识别的格式,也就是二进制。

    这也就是说,发送方需要把这个 Java 对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为 Java 对象,这也是 Deserialization 接口所做的工作。

    int 和 Integer 的区别

    int 和 Integer 区别可就太多了

    • int 是 Java 中的基本数据类型,int 代表的是 整型,一个 int 占 4 字节,也就是 32 位,int 的初始值是默认值是 0 ,int 在 Java 内存模型中被分配在栈中,int 没有方法。
    • Integer 是 Java 中的基本数据类型的包装类,Integer 是一个对象,Integer 可以进行方法调用,Integer 的默认值是 null,Integer 在 Java 内存模型中被分配在堆中。int 和 Integer 在计算时可以进行相互转换,int -> Integer 的过程称为 装箱,Integer -> int 的过程称为 拆箱,Integer 还有 IntegerCache ,会自动缓存 -128 - 127 中的值

    深入理解拆箱和装箱可以参考这篇文章 详解 Java 中的自动装箱与拆箱

    Java 提供了哪些 I/O 方式

    Java I/O 方式有很多种,传统的 I/O 也称为 BIO,主要流有如下几种。

    Java I/O 包的实现比较简单,但是容易出现性能瓶颈,传统的 I/O 是基于同步阻塞的。

    JDK 1.4 之后提供了 NIO,也就是位于 java.nio 包下,提供了基于 channel、Selector、Buffer的抽象,可以构建多路复用、同步非阻塞 I/O 程序。

    JDK 1.7 之后对 NIO 进行了进一步改进,引入了 异步非阻塞 的方式,也被称为 AIO(Asynchronous IO)。可以用生活中的例子来说明:项目经理交给手下员工去改一个 bug,那么项目经理不会一直等待员工解决 bug,他肯定在员工解决 bug 的期间给其他手下分配 bug 或者做其他事情,员工解决完 bug 之后再告诉项目经理 bug 解决完了。

    深入理解 IO 可以参考这篇文章 深入理解 IO

    谈谈你知道的设计模式

    一张思维导图镇场

    比如全局唯一性可以用 单例模式

    可以使用 策略模式 优化过多的 if…else…

    制定标准用 模版模式

    接手其他人的锅,但不想改原来的类用 适配器模式

    使用 组合 而不是继承

    使用 装饰器可以制作加糖、加奶酪的咖啡

    代理 可以用于任何中间商…

    写出几种单例模式实现

    这也是一道老生常谈的面试题了,一般不会让你手写单例模式。就算是手写的话,只需要写出关键代码来就可以了。这道题其实想问的是单例模式的几种实现方式,并且每种实现方式有没有什么问题。

    一般可以这样回答下

    • 饿汉式:线程安全,调用效率高,但是不能延时加载
    • 懒汉式:线程安全,调用效率不高,但是能延时加载
    • Double CheckLock 实现单例:DCL 也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用
    • 静态内部类实现模式:线程安全,调用效率高,可以延时加载
    • 枚举类:线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用

    如果想要深入了解这道面试题,可以查阅 我向面试官讲解了单例模式,他对我竖起了大拇指

    Comparator 和 Comparable 有什么不同

    • Comparable 更像是自然排序

    • Comparator 更像是定制排序

    同时存在时采用 Comparator(定制排序)的规则进行比较。

    对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了Comparable 接口,实现了 compareTo 方法,我们可以直接使用。

    而对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建 Comparator 接口,然后使用特定的 Comparator 实现进行比较。

    关于 Comparator 和 Comparable 的深入理解,可以参考这篇文章 Comparable 和 Comparator的理解

    Object 类中一般都有哪些方法

    Object 类是所有对象的父类,它里面包含一些所有对象都能够使用的方法

    • hashCode():用于计算对象的哈希码
    • equals():用于对象之间比较值是否相等
    • toString(): 用于把对象转换成为字符串
    • clone(): 用于对象之间的拷贝
    • wait(): 用于实现对象之间的等待
    • notify(): 用于通知对象释放资源
    • notifyAll(): 用于通知所有对象释放资源
    • finalize(): 用于告知垃圾回收器进行垃圾回收
    • getClass(): 用于获得对象类

    Java 泛型和类型擦除

    关于 Java 泛型和擦除看着一篇就够了。

    反射的基本原理,反射创建类实例的三种方式是什么

    反射机制就是使 Java 程序在运行时具有自省(introspect) 的能力,通过反射我们可以直接操作类和对象,比如获取某个类的定义,获取类的属性和方法,构造方法等。

    创建类实例的三种方式是

    • 对象实例.getClass();
    • 通过 Class.forName() 创建
    • 对象实例.newInstance() 方法创建

    深入理解反射的文章,请查阅 学会反射后,我被录取了!(干货)

    强引用、若引用、虚引用和幻象引用的区别

    我们说的不同的引用类型其实都是逻辑上的,而对于虚拟机来说,主要体现的是对象的不同的可达性(reachable) 状态和对垃圾收集(garbage collector)的影响。

    可以通过下面的流程来对对象的生命周期做一个总结

    对象被创建并初始化,对象在运行时被使用,然后离开对象的作用域,对象会变成不可达并会被垃圾收集器回收。图中用红色标明的区域表示对象处于强可达阶段。

    JDK1.2 介绍了 java.lang.ref 包,对象的生命周期有四个阶段:􏲧强可达􏰛(Strongly Reachable􏰜)软可达(Soft Reachable􏰜)弱可达(Weak Reachable􏰜)幻象可达(Phantom Reachable􏰜)

    如果只讨论符合垃圾回收条件的对象,那么只有三种:软可达、弱可达和幻象可达。

    • 软可达:软可达就是􏱬我们只能通过软引用􏳂才能访问的状态,软可达的对象是由 SoftReference 引用的对象,并且没有强引用的对象。软引用是用来描述一些还有用但是非必须的对象。垃圾收集器会尽可能长时间的保留软引用的对象,但是会在发生 OutOfMemoryError 之前,回收软引用的对象。如果回收完软引用的对象,内存还是不够分配的话,就会直接抛出 OutOfMemoryError。

    • 弱可达:弱可达的对象是 WeakReference 引用的对象。垃圾收集器可以随时收集弱引用的对象,不会尝试保留软引用的对象。

    • 幻象可达:幻象可达是由 PhantomReference 引用的对象,幻象可达就是没有强、软、弱引用进行关联,并且已经被 finalize 过了,只有幻象引用指向这个对象的时候。

    除此之外,还有强可达和不可达的两种可达性判断条件

    • 强可达:就是一个对象刚被创建、初始化、使用中的对象都是处于强可达的状态
    • 不可达(unreachable):处于不可达的对象就意味着对象可以被清除了。

    下面是一个不同可达性状态的转换图

    判断可达性条件,也是 JVM 垃圾收集器决定如何处理对象的一部分考虑因素。

    所有的对象可达性引用都是 java.lang.ref.Reference 的子类,它里面有一个get() 方法,返回引用对象。 如果已通过程序或垃圾收集器清除了此引用对象,则此方法返回 null 。也就是说,除了幻象引用外,软引用和弱引用都是可以得到对象的。而且这些对象可以人为拯救,变为强引用,例如把 this 关键字赋值给对象,只要重新和引用链上的任意一个对象建立关联即可。

    深入理解引用问题,可以看看这一篇文章 深入理解各种引用问题

    final、finally 和 finalize() 的区别

    这三者可以说是没有任何关联之处,我们上面谈到了,final 可以用来修饰类、变量和方法,可以参考上面 final 的那道面试题。

    finally 是一个关键字,它经常和 try 块一起使用,用于异常处理。使用 try…finally 的代码块种,finally 部分的代码一定会被执行,所以我们经常在 finally 方法中用于资源的关闭操作。

    JDK1.7 中,推荐使用 try-with-resources 优雅的关闭资源,它直接使用 try(){} 进行资源的关闭即可,就不用写 finally 关键字了。

    finalize 是 Object 对象中的一个方法,用于对象的回收方法,这个方法我们一般不推荐使用,finalize 是和垃圾回收关联在一起的,在 Java 9 中,将 finalize 标记为了 deprecated, 如果没有特别原因,不要实现 finalize 方法,也不要指望他来进行垃圾回收。

    深入理解 final、finally 和 finalize ,可以看看这一篇 看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了

    内部类有哪些分类,分别解释一下

    在 Java 中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

    内部类的分类一般主要有四种

    • 成员内部类
    • 局部内部类
    • 匿名内部类
    • 静态内部类

    静态内部类就是定义在类内部的静态类,静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;

    成员内部类 就是定义在类内部,成员位置上的非静态类,就是成员内部类。成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。

    定义在方法中的内部类,就是局部内部类。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。

    匿名内部类 就是没有名字的内部类,除了没有名字,匿名内部类还有以下特点:

    • 匿名内部类必须继承一个抽象类或者实现一个接口
    • 匿名内部类不能定义任何静态成员和静态方法。
    • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
    • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

    说出几种常用的异常

    • NullPointerException: 空指针异常
    • NoSuchMethodException:找不到方法
    • IllegalArgumentException:不合法的参数异常
    • IndexOutOfBoundException: 数组下标越界异常
    • IOException:由于文件未找到、未打开或者I/O操作不能进行而引起异常
    • ClassNotFoundException :找不到文件所抛出的异常
    • NumberFormatException: 字符的UTF代码数据格式有错引起异常;
    • InterruptedException: 线程中断抛出的异常

    静态绑定和动态绑定的区别

    一个Java 程序要经过编写、编译、运行三个步骤,其中编写代码不在我们讨论的范围之内,那么我们的重点自然就放在了编译运行这两个阶段,由于编译和运行阶段过程相当繁琐,下面就我的理解来进行解释:

    Java 程序从源文件创建到程序运行要经过两大步骤:

    1、编译时期是由编译器将源文件编译成字节码的过程

    2、字节码文件由Java虚拟机解释执行

    绑定

    绑定就是一个方法的调用与调用这个方法的类连接在一起的过程被称为绑定。

    绑定主要分为两种:

    静态绑定 和 动态绑定

    绑定的其他叫法

    静态绑定 == 前期绑定 == 编译时绑定

    动态绑定 == 后期绑定 == 运行时绑定

    为了方便区分: 下面统一称呼为静态绑定和动态绑定

    静态绑定

    在程序运行前,也就是编译时期 JVM 就能够确定方法由谁调用,这种机制称为静态绑定

    识别静态绑定的三个关键字以及各自的理解

    如果一个方法由 private、static、final 任意一个关键字所修饰,那么这个方法是前期绑定的

    构造方法也是前期绑定

    private:private 关键字是私有的意思,如果被 private 修饰的方法是无法由本类之外的其他类所调用的,也就是本类所特有的方法,所以也就由编译器识别此方法是属于哪个类的

    public class Person {
    
        private String talk;
    
        private String canTalk(){
            return talk;
        }
    }
    
    class Animal{
    
        public static void main(String[] args) {
            Person p = new Person();
            // private 修饰的方法是Person类独有的,所以Animal类无法访问(动物本来就不能说话)
    //        p.canTalk();
        }
    }
    

    final:final 修饰的方法不能被重写,但是可以由子类进行调用,如果将方法声明为 final 可以有效的关闭动态绑定

    public class Fruit {
    
        private String fruitName;
    
        final String eatingFruit(String name){
            System.out.println("eating " + name);
            return fruitName;
        }
    }
    
    class Apple extends Fruit{
    
          // 不能重写final方法,eatingFruit方法只属于Fruit类,Apple类无法调用
    //    String eatingFruit(String name){
    //        super.eatingFruit(name);
    //    }
    
        String eatingApple(String name){
            return super.eatingFruit(name);
        }
    }
    

    static: static 修饰的方法比较特殊,不用通过 new 出某个类来调用,由类名.变量名直接调用该方法,这个就很关键了,new 很关键,也可以认为是开启多态的导火索,而由类名.变量名直接调用的话,此时的类名是确定的,并不会产生多态,如下代码:

    public class SuperClass {
    
        public static void sayHello(){
            
            System.out.println("由 superClass 说你好");
        }
    }
    
    public class SubClass extends SuperClass{
    
        public static void sayHello(){
            System.out.println("由 SubClass 说你好");
        }
    
        public static void main(String[] args) {
            SuperClass.sayHello();
            SubClass.sayHello();
        }
    }
    

    SubClass 继承 SuperClass 后,在

    是无法重写 sayHello 方法的,也就是说 sayHello() 方法是对子类隐藏的,但是你可以编写自己的 sayHello() 方法,也就是子类 SubClass 的sayHello() 方法,由此可见,方法由 static 关键词所修饰,也是编译时绑定

    动态绑定

    在运行时根据具体对象的类型进行绑定

    除了由 private、final、static 所修饰的方法和构造方法外,JVM 在运行期间决定方法由哪个对象调用的过程称为动态绑定

    如果把编译、运行看成一条时间线的话,在运行前必须要进行程序的编译过程,那么在编译期进行的绑定是前期绑定,在程序运行了,发生的绑定就是后期绑定。

    public class Father {
    
        void drinkMilk(){
            System.out.println("父亲喜欢喝牛奶");
        }
    }
    
    public class Son extends Father{
    
        @Override
        void drinkMilk() {
            System.out.println("儿子喜欢喝牛奶");
        }
    
        public static void main(String[] args) {
            Father son = new Son();
            son.drinkMilk();
        }
    }
    

    Son 类继承 Father 类,并重写了父类的 dringMilk() 方法,在输出结果得出的是儿子喜欢喝牛奶。那么上面的绑定方式是什么呢?

    上面的绑定方式称之为动态绑定,因为在你编写 Father son = new Son() 的时候,编译器并不知道 son 对象真正引用的是谁,在程序运行时期才知道,这个 son 是一个 Father 类的对象,但是却指向了 Son 的引用,这种概念称之为多态,那么我们就能够整理出来多态的三个原则:

    • 继承

    • 重写

    • 父类引用指向子类对象

    也就是说,在 Father son = new Son() ,触发了动态绑定机制。

    动态绑定的过程

    1. 虚拟机提取对象的实际类型的方法表;
    2. 虚拟机搜索方法签名;
    3. 调用方法。

    动态绑定和静态绑定的特点

    静态绑定

    静态绑定在编译时期触发,那么它的主要特点是

    1、编译期触发,能够提早知道代码错误

    2、提高程序运行效率

    动态绑定

    1、使用动态绑定的前提条件能够提高代码的可用性,使代码更加灵活。

    2、多态是设计模式的基础,能够降低耦合性。

    Java 中有哪些语法糖

    语法糖指的是计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。因为 Java 代码需要运行在 JVM 中,JVM 是并不支持语法糖的,语法糖在程序编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖

    下面我就列出来 Java 中有哪些语法糖

    • 泛型:泛型是一种语法糖。在 JDK1.5 中,引入了泛型机制,但是泛型机制的本身是通过类型擦除 来实现的,在 JVM 中没有泛型,只有普通类型和普通方法,泛型类的类型参数,在编译时都会被擦除。

    • 自动拆箱和自动装箱:自动拆箱和自动装箱是一种语法糖,它说的是八种基本数据类型的包装类和其基本数据类型之间的自动转换。简单的说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

    • 内部类:内部类其实也是一个语法糖,因为其只是一个编译时的概念,一旦编译完成,编译器就会为内部类生成一个单独的class 文件,名为 outer$innter.class。

    • 变长参数:变长参数也是一个比较小众的用法,所谓变长参数,就是方法可以接受长度不定确定的参数。一般我们开发不会使用到变长参数,而且变长参数也不推荐使用,它会使我们的程序变的难以处理。

    • 增强 for 循环:增强 for 循环与普通 for 循环相比,功能更强并且代码更加简洁,你无需知道遍历的次数和数组的索引即可进行遍历;增强 for 循环的对象要么是一个数组,要么实现了 Iterable 接口。这个语法糖主要用来对数组或者集合进行遍历,其在循环过程中不能改变集合的大小。

    • Switch 支持字符串和枚举:switch 关键字原生只能支持整数类型。如果 switch 后面是 String 类型的话,编译器会将其转换成 String 的hashCode 的值,所以其实 switch 语法比较的是 String 的 hashCode 。

    • 条件编译:一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是 条件编译(conditional compile)

    • 断言:也就是所谓的 assert 关键字,是 jdk 1.4 后加入的新功能。它主要使用在代码开发和测试时期,用于对某些关键数据的判断,如果这个关键数据不是你程序所预期的数据,程序就提出警告或退出。

    • try-with-resources :JDK 1.7 开始,java引入了 try-with-resources 声明,将 try-catch-finally 简化为 try-catch,这其实是一种语法糖,在编译时会进行转化为 try-catch-finally 语句。新的声明包含三部分:try-with-resources 声明、try 块、catch 块。它要求在 try-with-resources 声明中定义的变量实现了 AutoCloseable 接口,这样在系统可以自动调用它们的 close 方法,从而替代了 finally 中关闭资源的功能。

    • 字符串相加:这个想必大家应该都知道,字符串的拼接有两种,如果能够在编译时期确定拼接的结果,那么使用 + 号连接的字符串会被编译器直接优化为相加的结果,如果编译期不能确定拼接的结果,底层会直接使用 StringBuilderappend 进行拼接

    深入理解 Java 语法糖,可以参考这篇文章 Java 中的语法糖,真甜。

    List 和 Set 的异同

    这也是一道老生常谈的问题了。

    相同点

    Java 集合中总共有三种类型,分别是 List、Set 和 Map,它们都位于 java.util 包下,而且都是接口,它们有各自的实现类。

    List 的实现类有ArrayList、LinkedList、Vector、CopyOnWriteArrayList

    Set 的实现类有HashSet、TreeSet、LinkedHashSet、SortedSet

    它们同样有自己的抽象类,List 的抽象类是 AbstractList、Set 的抽象类是 AbstractSet。抽象类主要用作顶级接口也就是 List、Set 这类接口的拓展,实现了一些顶级接口功能的同时也定义了一些模版方法,这用到了模版设计模式。

    List 和 Set 都继承了 Collection 接口,都属于一种 集合

    下面来聊一聊 List 和 Set 的不同点

    不同点

    有序性

    List 表示的是一种 有序集合,也就是说,List 中的元素都是有序排列的,List 接口能够准确的控制每个元素的插入顺序,可以通过索引来查找元素。

    Set 对集合的顺序没有要求,不同的实现类可以定义各自的顺序,比如 HashSet 就是一种无序的数据结构,HashSet 中的元素是乱序的;TreeSet 则会按照元素的自然顺序进行排序;LinkedHashSet 会按照插入 Set 中的元素顺序进行排序;SortedSet 会根据 Comparable 或者 Comparator 的比较顺序进行排序,也就是说你可以自定义排序规则。

    元素是否重复

    List 允许重复元素存在的,并且 List 能够允许插入 null 元素,而 Set 是不允许重复元素的,所以 Set 也只能允许一个 null 元素存在。

    说一下你了解的 Map

    Map 同样也是我们日常开发中使用频率非常高的集合类。

    Map 是一个支持 key-value 也就是 键值对 存储的对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。

    Map 接口的实现类有:HashMap、TreeMap、LinkedHashMap 类。

    HashMap 是我们最常用的 Map 实现了,HashMap 的底层构造就是 数组 + 链表,它会根据 key 的 hashCode 值存储数据,可以使用 get(key) 来获取键对应的值,HashMap 不是线程安全的容器,并且只能允许一条 null 键值对。在 HashMap 中元素比较少的时候,HashMap 是以链表的形式存储元素的,等到数据量到达一定程度后,链表会转为红黑树进行存储。

    TreeMap 是一个红黑树的 Map,它会对键按照指定的顺序进行排序,TreeMap 不是一个线程安全的 Map,TreeMap 中不能允许 null 值,否则会抛出空指针异常。

    LinkedHashMap 保证了元素的插入顺序,在查询遍历时会比 HashMap 慢,同样也不是线程安全的容器。LinkedHashMap 可以允许空元素。

    值得注意的是,Map 的实现还有 HashtableConcurrentHashMap,这两个元素都是线程安全的 Map。

    Hashtable 我们一般不常用,因为它的线程安全都只是使用了简单暴力的 synchronized 同步锁,同步锁的开销比较大,不推荐使用。

    还有最后一个就是 ConcurrentHashMap 了,ConcurrentHashMap 是一个基于多线程高并发的 Map,它经常用于多线程场景,ConcurrentHashMap 的底层使用了分段锁,分段锁对每个段进行加锁,在保证线程安全的同时降低了锁的粒度。

    ArrayList 、LinkedList 和 Vector 的区别

    ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。

    对于随机访问 get 和 set 操作,ArrayList 要优于 LinkedList,因为 LinkedList 要移动指针。

    对于新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。

    ArrayList 和 LinkedList 都不是线程安全的容易,它们都需要手动加锁,或者使用 Collections 中的线程安全的实现 Collections.synchronizedList 来构造。它们都具有 fail-fast 快速失败机制的。

    这个时候可能还会问你和 Vector的区别

    主要有两点区别:线程安全性和扩容方面

    Vector 与 ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。

    当 Vector 或 ArrayList 中的元素超过它的初始大小时,Vector 会将它的容量翻倍,而 ArrayList 只增加 50% 的大小。这样,ArrayList 就有利于节约内存空间。

    Collections.sort 和 Arrays.sort 的实现原理

    先呈现一下 Collections.sort 和 Arrays.sort 的源码

    从这三段代码可知,Collections.sort 最终调用的是 Arrays.sort 中的方法,那么 Collections.sort 不用看了,直接看 Arrays.sort 即可。

    Arrays.sort 源码中有三个分支判断,如果没有提供外部 Comparator 比较器的话,会直接调用 sort 方法,调用后的 sort 方法如下

    首先会进行LegacyMergeSort.userRequested 的判断,那么这个判断是什么意思呢?

    是这样,在 JDK1.6 中使用的是 LegacyMergeSort,在 JDK1.7 中使用的是 TimeSort,如果想要使用原来的 LegacyMergeSort 的话,就需要在系统变量中加上

    -Djava.util.Arrays.useLegacyMergeSort=true
    

    这个配置。

    如果没有使用这个配置的话,默认调用的是 ComparableTimSort.sort 方法,这其实是一种 TimSort 的优化,而 TimSort 的排序算法就是归并排序

    我们从 Arrays.sort 中的第二个判断也可以看到这一点,ComparableTimSort 和 TimSort 最大的区别就是看你有没有提供外部的比较器了。

    Iterator 和 ListIterator 有什么区别

    我们日常开发过程中使用 Iterator 用的非常多,ListIterator 很少接触,这道题主要考察你对 List 源码的熟悉程度以及对 fail-fast 机制是否了解。

    一般来讲,Iterator 和 ListIterator 的主要区别有

    • Iterator 在遍历过程中,不能修改集合中元素的数量操作,否则会抛出异常。

    • 使用范围不同,Iterator 可以在所有集合中使用,而 ListIterator 只能用在 List 类型与子类型

    • listIterator 有 add 方法,可以向集合中添加元素,而 iterator 不能。

    • listiterator 和 iterator 都有 hasnext 和 next 方法可以顺序遍历元素, 但是 listiterator 有 hasprevious 和 previous 方法,可以逆向遍历元素

    • listiterator 可以定位当前的索引位置 nextIndex 和 previousIndex 可以实现,iterator 没有此功能

    • listiterator 可以实现对对象的修改 set() 方法可以实现,iterator 仅可以遍历,不能修改。

    说一说 ArrayList 的扩容机制

    今天面试官又问到你 ArrayList 的扩容机制了。。。。。。那就直接看源码呗

    话说什么时候会扩容啊,肯定是调用 add 方法添加元素的时候呀

    ArrayList 添加元素会进行判断,这也是ensureCapacityInternal 方法干的事情,也就是判断并确定容量大小的一个方法,我们直接跟进去

    这个方法会进行一个判断,我们上面注释也给出来了,就是说你这个 ArrayList 在构造的时候是否指定了初始容量,如果 List 中没有元素,就会取你设置初始容量和 ArrayList 中最大容量判断,取大的为准。

    然后调用 ensureExplicitCapacity 方法,判断是否需要扩容

    可以看到,如果此时 ArrayList 数组元素的 size 要比存储的元素大的话,就会调用 grow 方法,这个 grow 方法其实就是扩容方法。

    这段代码就是 ArrayList 扩容机制了。我们来分析一下

    首先会获取 ArrayList 元素的数量,然后把 ArrayList 的容量扩容 50%,把扩容后的容量和参数容量进行比较,如果比参数小的话,取值更大的容量进行元素拷贝。如果扩容后的容量比 ArrayList 的最大容量还要大,就会进行最大容量的取值

    hugeCapacity 方法首先判断参数是否小于 0 ,如果是的话那么就会抛出 OutOfMemoryError 异常,如果不是的话就判断参数和 ArrayList 最大容量到底谁大,如果参数大的话,就会取值为 Integer.MAX_VALUE,如果小的话就会取 MAX_ARRAY_SIZE。

    BIO、NIO、AIO 的区别

    简单来说,BIO(Blocking I/O),它是一种同步阻塞式的 IO,数据的读写必须在一个线程中进行完成,也就是说它只有一个执行顺序流,如果遇到 IO 流的读取操作,它必须等到 IO 流的读取或者写入完成后,才能进行接下来的工作。

    NIO(non-blocking IO) 其实也被称为 new io,它是 JDK 1.4 及其以上版本中新增加的一种 IO 流,它是一种同步非阻塞的 IO 模型。在 NIO 中,线程发起请求后会立刻返回,同步指的是必须等待 IO 缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待 IO 缓冲区,可以先做一些其他操作,但是要定时轮询检查 IO 缓冲区数据是否就绪。Java 中 的 NIO 是 new IO 的意思。其实是 NIO 加上 IO 多路复用技术。普通的 NIO 是线程轮询查看一个 IO 缓冲区是否就绪,而 Java 中的 new IO 指的是线程轮询地去查看一堆 IO 缓冲区中哪些就绪,这是一种 IO 多路复用的思想。IO 多路复用模型中,将检查 IO 数据是否就绪的任务,交给系统级别的 select 或 epoll 模型,由系统进行监控,减轻用户线程负担。

    AIO 是真正意义上的异步非阻塞 IO 模型。 上述 NIO 实现中,需要用户线程定时轮询,去检查 IO 缓冲区数据是否就绪,占用应用程序线程资源,其实轮询相当于还是阻塞的,并非真正解放当前线程,因为它还是需要去查询哪些 IO 就绪。而真正的理想的异步非阻塞 IO 应该让内核系统完成,用户线程只需要告诉内核,当缓冲区就绪后,通知我或者执行我交给你的回调函数。

    深拷贝和浅拷贝区别

    在 Java 中,根据对对象属性的拷贝程度(基本数据类和引用类型),会分为两种:

    • 浅拷贝 Shallow Copy
    • 深拷贝 Deep Copy

    浅拷贝:浅拷贝会重新创建一个对象,这个对象有着原始对象属性值的一份拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制,即逐个成员依次拷贝,也就是只复制对象空间而不复制资源。

    实现浅拷贝,需要实现 Cloneable 接口,并覆写 clone() 方法。

    深拷贝:对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

    Java 创建对象的五种方式

    Java 创建对象的方式主要有五种

    1. 使用 new 来创建对象
    Object obj = new Object();
    
    1. 使用 newInstance 方法来创建
    User user = (User)Class.forName("com.cxuan.test.User").newInstance();
    
    // 或者使用
    
    User user = User.class.newInstance();
    
    1. 使用反射来创建对象
    Constructor<User> constructor = User.class.getConstructor();
    User user = constructor.newInstance();
    
    1. 使用对象克隆来创建对象
    Constructor<User> constructor = User.class.getConstructor();
    User user = constructor.newInstance();
    user.setName("cxuan");
    
    User user2 = (User)user.clone();
    System.out.println(user2.getName());
    
    1. 使用反序列化创建对象
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxx"));
    out.writeObject(user2);
    out.close();
    //Deserialization
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("xxx"));
    User user3 = (User) in.readObject();
    in.close();
    user3.setName("cxuan003");
    System.out.println(user3 + ", hashcode : " + user3.hashCode());
    

    深入理解这几种创建对象的方式,可以参考 盘点 Java 创建对象的 n 种操作

    花絮

    这篇写了很长时间,微信公众号的统计是 3w 字,31 张图,阅读时间大概是 77 分钟。

    我知道这个阅读时间可以坐地铁 10 号线绕北京一圈了,也会有很多人产生反感,文章写这么长,谁有时间看完啊?这种快餐式的疑问我已经听到过无数遍了,但是我每次都会当作耳旁风。是啊,我们每天,刷抖音看美女,刷头条看美女,刷公众号看一群大佬们天天吹牛逼,然后骂道:“卧槽,又特么浪费老子 1 个小时”,然后假装认真的看了 15 分钟代码,继续浪费。

    我知道这篇文章仍旧没多少人看,因为马上就要被信息洪流淹没了,可能你看完了就会放在收藏夹中,甚至你觉得我写的小儿科,也可能这篇文章在你的编程生涯中泛不起一点涟漪,我不想过多的描述一种现象。我只说一下我做了哪些事情吧。这篇文章中大概有不到 20 条链接,其中有 14 条都是我公众号的内容。

    一篇与众不同的 String、StringBuffer、StringBuilder 详解

    一个小小的 static 还能难得住我?

    看完这篇 HashMap ,和面试官扯皮就没问题了

    看完这篇 Exception 和 Error ,和面试官扯皮就没问题了

    动态代理深入理解

    动态代理竟然如此简单!

    深入理解 IO

    我向面试官讲解了单例模式,他对我竖起了大拇指

    Comparable 和 Comparator的理解

    学会反射后,我被录取了!(干货)

    深入理解各种引用问题

    看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了

    Java 中的语法糖,真甜。

    盘点 Java 创建对象的 n 种操作

    我想,这能够对的起我在 github https://github.com/crisxuan/bestJavaer 上面说过的话。

    欢迎大家关注我的 CSDN 账号哦,如果觉得这篇文章不错的话,还请给这篇文章点赞哦!

    展开全文
  • Oracle官网下载速度慢

    万次阅读 2019-07-18 21:30:07
    ,本周想搭一个Oracle数据库,在官网下载一直下不动。开始以为是网速的问题,更换几个网络后发现根本不是那么回事。简直恼火了,多次尝试,发现了问题,记录一下: Fn+f12打开调试页面,找到对应的连接,在...
  • 解决pandas loc iloc ix速度的问题

    千次阅读 2020-03-09 20:50:09
    。 一般来说在pandas上不要去改值,而是把要改值的那一列全部提出来,统一修改后,直接覆盖原引用。 下面给一个例子供大家参考 import pandas as pd import numpy as np from sklearn.datasets import load_...
  • 前端最全面试题

    万次阅读 多人点赞 2019-05-04 10:39:20
    今天给朋友们分享一到三年前端最全的面试。 JS基础: JS的数据类型有哪些? 答案见:JS数据类型详解 如何判断JS变量的数据类型? typeof() instanceof constructor toString typeof求解的可能...
  • Redis面试(2020最新版)

    万次阅读 多人点赞 2019-12-13 10:38:01
    Redis有哪些优缺点 优点 读写性能优异, Redis能读的速度是110000次/s,速度是81000次/s。 支持数据持久化,支持AOF和RDB两种持久化方式。 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作...
  • MySQL数据库面试(2020最新版)

    万次阅读 多人点赞 2020-03-10 17:20:40
    数据保存在内存 优点: 存取速度快 缺点: 数据不能永久保存 数据保存在文件 优点: 数据永久保存 缺点:1)速度比内存操作,频繁的IO操作。2)查询数据不方便 数据保存在数据库 1)数据永久保存 2)使用SQL语句,...
  • Linux面试(2020最新版)

    万次阅读 多人点赞 2020-03-01 11:14:38
    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试,对文章进行了精简优化,欢迎...
  • ,有几个窗体,每个大概拖了十几个DEV的控件,光初始化(InitializeComponent)一下就得500ms以上,导致界面第一次点开给人感觉不流畅,有啥办法解决不?换控件重做界面不现实。
  • ,WIN10中用着用着会发现任务栏的文件夹右键菜单(下图)弹出来比较: 刚开始我怀疑是有程序注册了文件夹的右键菜单弹出而导致, 但是,有一个现象:在桌面或者在计算机其它地方的文件夹右键菜单弹出速度...
  • Ruby做纯计算还是太慢

    千次阅读 热门讨论 2007-04-08 04:11:00
    正题前先罗嗦一句:Google的字库没有校对么?,那么多低级错别字,还不让人删除。至于哪些错别字,三表的文章里有很多搜狗的例子。Google做的改进就是把正确的词排在错误的词前面。我靠,删除缺省词库很难啊?通过...
  • BAT机器学习面试1000系列(第1~305

    万次阅读 多人点赞 2017-09-28 11:37:49
    BAT机器学习面试1000系列 整理:July、元超、立娜、德伟、贾茹、王剑、AntZ、孟莹等众人。本系列大部分题目来源于公开网络,取之分享,用之分享,且在撰写答案过程中若引用他人解析则必注明原作者及来源链接...
  • ,Building Workspace速度慢的很大一部分原因是在没必要地validate那些JS文件。 而:关掉Preference -> General -> Workspace中的Build automatically,或者:将Preference -> MyEclipse -> Validation中...
  • Java面试大全(2021版)

    万次阅读 多人点赞 2020-11-25 11:55:31
    发现网上很多Java面试都没有答案,所以花了很长时间搜集整理出来了这套Java面试大全,希望对大家有帮助哈~ 本套Java面试大全,全的不能再全,哈哈~ 一、Java基础知识面试 1、Java概述 ①. 何为编程 ...
  • Java集合容器面试(2020最新版)

    万次阅读 多人点赞 2020-03-01 11:08:34
    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试,对文章进行了精简优化,欢迎...
  • Java基础知识面试(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试,对文章进行了精简优化,欢迎...
  • 于LeetCode刷完900之际的感想

    千次阅读 多人点赞 2019-11-01 08:34:36
    时光匆匆,距离开始博客转瞬便是大半年已过, 从最初寒假每天十的刷题狂热信徒,到如今一周也做不到二十的佛系上岸选手,有了很多感想经验,因而有了这篇文章。 我的第一轮刷题,始于二叉树,接着按照二叉...
  • Linux 测试 IO 性能(磁盘读写速度

    万次阅读 2018-01-10 13:54:39
    Linux 测试 IO 性能(磁盘读写速度) 这几天做MySQL性能测试,偌大一个公司,找几台性能测试机器都很纠结,终于协调到两台,IO的性能如何还不知道。 数据库属于IO密集型的应用,所以还是先评估下Server的IO...
  • 请问如何提高文件的读写速度

    千次阅读 2009-03-08 22:45:00
     在提取游戏资源的时候,我编程用的是C++中的 ifstream , ofstream, 可是在读文件的时候速度,硬盘狂转,请问一下有人知道如何提高速度么?多谢!悠久扬笛2007-05-15 11:22用内存映射方式可能会好点 ...
  • 面试-Redis宕机怎么办

    千次阅读 2019-11-19 10:00:16
    持久化 ...RDB是一个快照文件,数据很紧凑,适合用于灾难恢复,而且恢复大数据集时的速度比 AOF 的恢复速度要快。 保存整个数据集的快照,也不可能频繁。因此服务器故障时候会丢失数据。 AOF...
  • 教你如何迅速秒杀掉:99%的海量数据处理面试

    万次阅读 多人点赞 2012-03-22 12:51:07
    教你如何迅速秒杀掉:99%的海量数据处理面试作者:July出处:结构之法算法之道blog前言 一般而言,标题含有“秒杀”,“99%”,“史上最全/最强”等词汇的往往都脱不了哗众取宠之嫌,但进一步来讲,如果读者读罢...
  • 几率大的Redis面试(含答案)

    万次阅读 多人点赞 2019-04-29 09:43:46
    本文的面试如下: Redis 持久化机制 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题 热点数据和冷数据是什么 Memcache与Redis的区别都有哪些? 单线程的redis为什么这么快 redis的数据类型,以及每种...
  • 百日闭关修炼,每日三道高频面试。一起冲进大厂!看什么看?快上车!今天是Reds,香
  • 消息中间件MQ与RabbitMQ面试(2020最新版)

    万次阅读 多人点赞 2020-03-01 11:11:21
    最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,死了死了。用户通过浏览器发起请求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个...
  • 在数据库查询过程中,有时候我们会遇到一些...而通过with as 语法先把它“暂存”一下速度则会快很多。 基本用例如下: 1 我想做一个错题统计,先把错题ID和错题数查询出来 select info.answerexeid AS exeid,...
  • 整理中....未整理完 大家好,本人面试基本 “笔试死”、问"基础死"、"Java常识死"那一类型的....面试的过程中也从曾经被“鄙视”过,被...(我面试时从来没有做完过一套,只是选择其中1-2...
  • Linux运维常见面试汇总

    万次阅读 多人点赞 2016-10-19 09:01:08
    Linux面试 一、填空 1. 在Linux 系统 中,以文件方式访问设备 。 2. Linux 内核引导时,从文件/etc/fstab中读取要加载的文件系统 。 3. Linux 文件系统中每个文件用indoe节点来标识。 4. 全部磁盘块由...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,793
精华内容 18,717
关键字:

写题速度太慢怎么办