Java面试题1-Java基础

别等时光非礼了梦想. 提交于 2020-01-20 10:47:39

实例方法和静态方法有什么不一样

 


1.在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。

而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。

 

2.静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许

访问实例成员变量和实例方法,如果需要调用,则需要先实例化;实例方法则无此限制

 

3.静态方法是在类中使用staitc修饰的方法,在类定义的时候已经被装载和分配。而非静态方法是

不加static关键字的方法,在类定义时没有占用内存,非静态方法只有在类被实例化成对象时,对象

调用该方法才被分配内存

 

Java中的异常有哪几类?分别怎么使用

Throwable是所有异常的根,java.lang.Throwable
Error:错误,Java.lang.Error
Exception:异常,java.lang.Exception

Exception分为CheckedException和RuntimeException,所有RuntimeException类及其子类的实例
被称为Runtime异常,不属于该范畴的异常则被称为CheckedException

checkedException:
只有Java语言提供了Checked异常,Java认为checked异常都是可以被处理的异常,
所以Java程序必须显示处理checked异常。如果程序没有处理checked异常,
该程序在编译时会发生错误无法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。
对Checked异常处理方法有两种:
1、当前方法知道如何处理该异常,则用try…catch块来处理。
2、当前方法不知道如何处理,则在定义该方法时声明抛出该异常。

RuntimeException
Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和
运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。
当然如果你有处理要求也可以显示捕获它们。

 

常用的集合类有哪些?List如何排序?

 

ArrayList和LinkedList的区别

 

 

ArrayList LinkedList

数据结构

动态数组

双向链表

get(index)

O(1)

O(n)

add(E)插入末尾

O(1) 

O(1)

add(index,E )

O(n)

O(n)

remove()

O(n)

O(1)

 

 

1.对于随机访问get,ArrayList优于LinkedList,因为LinkedList要移动指针
2.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

 

BlockingQueue的使用。(take,poll的区别,put,offer的区别)


入队:

poll():如果没有元素,直接返回null;如果有元素,出队

take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断-->阻塞

 

出队:

offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false

put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断-->阻塞

 

ClassLoader有什么用

 

众所周知, Java 或者其他运行在 JVM(java 虚拟机)上面的程序都需要最终便以为字节码,然后被 JVM加载运行,
那么这个加载到虚拟机的过程就是 classloader 类加载器所干的事情.直白一点,就是 通过一个类的全限定类
名称来获取描述此类的二进制字节流 的过程.

双亲委派模型
说到 Java 的类加载器,必不可少的就是它的双亲委派模型,从 Java 虚拟机的角度来看,只存在两种不同的类加载器:
1.启动类加载器(Bootstrap ClassLoader), 由 C++语言实现,是虚拟机自身的一部分.
2.其他的类加载器,都是由 Java 实现,在虚拟机的外部,并且全部继承自java.lang.ClassLoader
在 Java 内部,绝大部分的程序都会使用 Java 内部提供的默认加载器.

启动类加载器(Bootstrap ClassLoader)
负责将$JAVA_HOME/lib或者 -Xbootclasspath 参数指定路径下面的文件(按照文件名识别,如 rt.jar) 
加载到虚拟机内存中.

扩展类加载器(Extension ClassLoader)
负责加载$JAVA_HOME/lib/ext 目录中的文件,或者java.ext.dirs 系统变量所指定的路径的类库.

应用程序类加载器(Application ClassLoader)
一般是系统的默认加载器,比如用 main 方法启动就是用此类加载器,也就是说如果没有自定义过类加载器,
同时它也是getSystemClassLoader() 的返回值.

工作流程:
1.收到类加载的请求
2.首先不会自己尝试加载此类,而是委托给父类的加载器去完成.
3.如果父类加载器没有,继续寻找父类加载器.
4.搜索了一圈,发现都找不到,然后才是自己尝试加载此类.

能不能自己写个类叫java.lang.System?如果不行,请说明原因;如果可以,请说明实现思路


通常不可以,但可以采取另类方法达到这个需求。

  • 为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。
  •  但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。

 

==和equals的区别

 

==是一个比较运算符,基本数据类型比较的是值,引用数据类型比较的是地址值

equals()是一个方法,只能比较引用数据类型。重写前比较的是地址值,重写后比一般是比较对象的属性

 

hashCode方法的作用

 

1、hashCoed 的特性:
(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储
地址;
(2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同;
(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个
对象在一个散列存储结构中。
(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写。

2、hashCode 的作用:
Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,
但元素不可重复。 equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有
1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。 
于是,Java采用了哈希表的原理。

哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。这样一来,当集合要添加新的元素时,
先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。
(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;
(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,
将所有产生相同HashCode的对象放到这个单链表上去,串在一起。这样一来实际调用equals方法的次数就大大降低
了。 

所以hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,
每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就
可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。

 

Object类中有哪些方法

 

1.toString
2.clone
3.equals和hashCode
4.finalize 当这个对象的内存不再被使用时,GC(关于GC请自行查阅资料)在收集垃圾时就会调用这个方法
5.wait notify notifyAll 控制线程状态使用

 

HashMap数据结构、扩展策略,如何实现线程安全的HashMap

 

1.HashMap的数据接口为散列桶+链表(jdk8之前),jdk8之后如果链表的长度大于8会自动转化为红黑树

2.当Hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了
提高查询的效率,就要对hashmap的数组进行扩容。 那么hashmap什么时候进行扩容呢?当hashmap中的元素
个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,
数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍
,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作。

3.HashMap 在jdk1.8之前默认使用链表来解决hash冲突,1.8之后对于链表长度大于8的会转化为红黑树

4.线程安全的HashMap可以使用 HashTable(不建议,性能非常低),ConcurrentHashMap(使用分段锁来实现)
性能高。

 

HashSet与HashMap怎么判断集合元素重复

 

HashMap中判断元素是否相同主要有两个方法,一个是判断key是否相同,一个是判断value是否相同
HashSet不能添加重复的元素,当调用add(Object)方法时候,
首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素;
如果已存在则调用Object对象的equals方法判断是否返回true,如果为true则说明元素已经存在,
如为false则插入元素。
发现HashSet竟然是借助HashMap来实现的,利用HashMap中Key的唯一性,来保证HashSet中不出现重复值。
从这段代码中可以看出,HashMap中的Key是根据对象的hashCode() 和 euqals()来判断是否唯一的。
结论:为了保证HashSet中的对象不会出现重复值,在被存放元素的类中必须要重写hashCode()和equals()
这两个方法。

 

 

 

 

Java中基础类型字节数

 

boolean 1 byte 1 char 2 int 4
float 4 long 8 double 8

 

创建一个类的实例都有哪些办法

 

1、关键字 new。工厂模式是对这种方式的包装;
2、类实现克隆接口,克隆一个实例。原型模式是一个应用实例;
3、用该类的加载器,newinstance。 Class.forName/Object.class
4、sun.misc.Unsafe类,allocateInstance方法创建一个实例。(Java官方不建议直接使用的Unsafe类)
5、实现序列化接口的类,通过IO流反序列化读取一个类,获得实例。

 

如何获取一个类的反射

 

利用反射机制可以获取类对象(也就是我们前面介绍的类对象,获取类对象之后我们便获取了类的模板,可以对类进行一些操作),有以下三种方法:

  • 类名.class()
  • 对象名.getClass()
  • Class.forName(具体的类名)

 

public static void main(String[] args) throws Exception {
        
        //1.类名.class
        Class clz = User.class;
        System.out.println(clz);

        //2.对象名.getClass()
        Class clz1 = new User().getClass(); 
        System.out.println(clz==clz1);
        
        //3.Class.forName()
        Class clz2 = Class.forName("cn.itcast_01.User");
        System.out.println(clz==clz2);
    }

 

java反射可以获取什么

 

构造方法 getConstructors

Class clazz1 = Class.forName("Reflect.Student");
        //获取所有的构造方法
        Constructor[] constructors = clazz1.getConstructors();
        //遍历所有的构造方法
        for(int i= 0;i<constructors.length;i++) {
            //获取每个构造函数中的参数类型字节码
            Class[] parameterTypes = constructors[i].getParameterTypes();
            //获取构造函数中参数类型
            System.out.println("第"+i+"个构造函数");
            for(int j=0;j<parameterTypes.length;j++) {
                //获取构造函数中参数类型
                System.out.println(parameterTypes[j].getName()+",");
            }
        }

成员变量 getFields

Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")方法获取,通过set(obj, "李四")方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值.

 

// 通过反射获得字节码文件对象
        Class clazz1 = Class.forName("Reflect.Student");
        // 实例化
        Student student = (Student) clazz1.newInstance();
        // 赋值操作
        student.setId(3);
        student.setName("zhangsan");
        // 将私有的属性一并获取
        Field[] fields = clazz1.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            // 打开操作权限
            fields[i].setAccessible(true);
            // 获取成员变量的值
            System.out.println(fields[i].get(student));
        }

 

获得方法并使用 getMethod

 

获得该类的所有接口 getInterfaces

Class[] getInterfaces():确定此对象所表示的类或接口实现的接口

返回值:接口的字节码文件对象的数组

 

 

 

 

final/finally/finalize的区别

 

1.final为用于标识常量的关键字,final标识的关键字存储在常量池中

2.finalize()方法在Object中进行了定义,用于在对象“消失”时,由JVM进行调用用于对对象进行垃圾回收,
类似于C++中的析构函数;用户自定义时,用于释放对象占用的资源(比如进行I/0操作);

3.finally{}用于标识代码块,与try{}进行配合,不论try中的代码执行完或没有执行完(这里指有异常),该代码
块之中的程序必定会进行;

 

LinkingBlockingQueue与ArrayBlockingQueue的区别

 

LinkedBlockingQueue和ArrayBlockingQueue的“共性”:

第一,首先它们都继承了BlockingQueue的接口,也就是说,它们都是阻塞式的队列,这里的阻塞情况无外乎2种:
一种是队列满时阻塞等待,另一种就是队列空时阻塞等待,前者等待的生成者,后者则是消费者。所以这种结构用在
生产者-消费者的使用场景中是比较适用的。还有一点,因为是队列,所以肯定保证FIFO顺序性的。

第二,既然都继承了BlockingQueue的接口,那么它们所对外提供的方法也是一致的,add/offer/put,
take/poll/remove。这里面还能够支持阻塞,非阻塞的调用形式,总体来说还是非常灵活的。

LinkedBlockingQueue和ArrayBlockingQueue的区别:
第一,ArrayBlockingQueue是有界的,而LinkedBlockingQueue默认是无界的(可以通过指定大小来变为有界)

第二点,锁使用的比较。ArrayBlockingQueue内部使用1个锁来控制队列项的插入、取出操作,
而LinkedBlockingQueue则是使用了2个锁来控制,一个名为putLock,另一个是takeLock。
不过两个使用的都是ReentrantLock。

 

Session/Cookine的区别

 

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
   考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
   考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5、所以个人建议:
   将登陆信息等重要信息存放为SESSION
   其他信息如果需要保留,可以放在COOKIE中

 

String/StringBuffer/StringBuilder的区别

 

String(大姐,出生于JDK1.0时代)          不可变字符序列
StringBuffer(二姐,出生于JDK1.0时代)    线程安全的可变字符序列
StringBuilder(小妹,出生于JDK1.5时代)   非线程安全的可变字符序列

1. StringBuffer与String的可变性问题。 
    (1) String中的是常量(final)数组,只能被赋值一次。 
    (2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。
2.StringBuffer与StringBuilder的线程安全性问题    
    StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
    String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改,不存在线程安全性
3.String和StringBuffer的效率问题
    大量字符串累加时,StringBuffer的append()效率远好于String对象的"累+"连接 

 

Servlet的生命周期

 

Servlet 生命周期
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

Servlet 通过调用 init () 方法进行初始化。
Servlet 调用 service() 方法来处理客户端的请求。
Servlet 通过调用 destroy() 方法终止(结束)。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

 

如何用Java分配一段连续的1G的内存空间

 

ByteBuffer.allocateDirect(1024*1024*1024);

 

Java里面用对象作为Key需要注意些什么? 如何实现hashcode


看如下代码:

返回结果:

 

这是因为get元素的时候,执行了equals方法和hashCode方法。 

new一个新的对象时,地址变了,不能保证hash值和equals结果还是一样。所以取不到对应的value

 

所以需要重写equals 和hashCode方法 注意这两个方法需要同时重写 重写hashCode 可以保证hash操作

可以找到散列表的同一个位置 重写equals可以保证数据比较value的时候返回true 最终取到对应的value

 

返回结果:

 

讲一下常见编码方式

ASCII 码 

学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。 

ISO-8859-1 

128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。 

GB2312 

它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

GBK 

全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。 

UTF-16 

说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。 

UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因

UTF-8 

UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成

 

utf-8编码中的中文占几个字节;int型几个字节

 

utf-8的编码规则:

如果一个字节,最高位为0,表示这是一个ASCII字符(00~7F)

如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数

一个utf8数字占1个字节

一个utf8英文字母占1个字节

少数是汉字每个占用3个字节,多数占用4个字节

 

说说你对Java注解的理解

java注解:

注解,也叫元数据。一种代码级别的说明,在JDK1.5之后引入的特性,与类、接口、枚举同一层次。可以声明在包、类、字段、方法、局部变量、方法参数等前面,来对这些元素进行说明,注释等。

作用分类:

1)编写文档:通过代码里的标识的元数据生成文档【生成文档doc文档】

2)代码分析:通过代码里的标识的元数据对代码进行分析【使用反射】

3)编译检查:通过代码里的标识的元数据让编译器能过实现基本的编译检查【Override】

元注解:

java提供了四个元注解,所谓元注解就是负责注解其他注解。

  • @Target :规定注解所修饰的对象范围。

1)ElementType.CONSTRUCTIR; 构造器声明

2)ElementType.FIELD; 成员变量,对象,属性(包括enum实例)

3)ElementType.LOCAL_VARIABLE; 局部变量声明

4)ElementType.METHOD ; 方法声明

5)ElementType.PACKAGE; 包声明

6)ElementType.PARAMETER;参数声明

7)ElementType.TYPE; 类、接口(包括注解类型)或enum声明

  • @Retention : 表示注解的生命周期

1)RetentionPolicy.SOUREC: 在源文件中有效

2)RetentionPolicy.CLASS; 在class文件中有效

3)RetentionPolicy.RUNTIME;在运行时有效

  • @Inherited : 标记注解,主要说明了一种继承性,意思是子类可以继承父类中的该注解(注意:只有当被贴上@Inherited标签的注解被用在类上的时候子类才能获得这个注解)。
  • @Documented : 用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化

 

ThreadLocal原理


首先在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals

 

为什么ThreadLocalMap里Entry为何声明为WeakReference

 

WeakReference是Java语言规范中为了区别直接的对象引用(程序中通过构造函数声明出来的对象引用)而定义的另外一种引用关系。

WeakReference标志性的特点是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象之外所有的对象解除引用后,该对象便可以被GC回收),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null

 

WeakReference对应用的对象users是弱引用,不会影响到users的GC行为。

如果是强引用的话,在线程运行过程中,我们不再使用users了,将users置为null,但users在线程的ThreadLocalMap里还有引用,导致其无法被GC回收(当然,可以等到线程运行结束后,整个Map都会被回收,但很多线程要运行很久,如果等到线程结束,便会一直占着内存空间)。

 

而Entry声明为WeakReference,users置为null后,线程的threadLocalMap就不算强引用了,users就可以被GC回收了。map的后续操作中,也会逐渐把对应的"stale entry"清理出去,避免内存泄漏

 

所以,我们在使用完ThreadLocal变量时,尽量用threadLocal.remove()来清除,避免threadLocal=null的操作。

前者remove()会同时清除掉线程threadLocalMap里的entry,算是彻底清除

而后者虽然释放掉了threadLocal,但线程threadLocalMap里还有其"stale entry",后续还需要处理

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!