Java基础面试

目录

  1. 返回首页
  2. 数据类型
  3. 引用类型
  4. 运算符
  5. 字符串
  6. ==和equals
  7. JAVA集合
  8. 异常

JAVA数据类型

一、java的数据类型有哪些?

基本数据类型有四类八种:
整型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
浮点型:float(4字节)、double(8字节)
字符型:char(2字节)
布尔型:boolean(1位)
引用类型:类class、接口interface、数组[]

二、float和long哪个存放的数据更大,为什么?

Float存放的更大,因为虽然float占用4个字节,long占用8个字节,但是float存储结构不同, 是把32位分成了两部分,一部分存放阶码(左移位数的2进制表示,等价于10进制指数),一部分存放尾数(移动后小数点后面的数字,等价于10进制底数)。

三、Integer和int的区别

  1. Integer是int的包装类,int则是java的一种基本数据类型
  2. Integer变量必须实例化后才能使用,而int变量不需要
  3. Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值;
  4. Integer的默认值是null,int的默认值是0;

四、可不可以使用char来存汉字?

可以,因为char是2个字节,汉字也是2个字节

五、i一定小于i+1吗?

不一定,如byte i = 127,加完之后结果为-128

六、Java类型转换

  • 自动转换(小转大/子类转父类):byte,short,char——>int——>long——>float——>double; byte,short,char相互之间不转换,他们参与运算首先转换为int类型 ;boolean类型不参与转换。
  • 强制转换:目标类型 变量名=(目标类型)(被转换的数据),可能会丢失精度;

七、Double a=0.09+0.01的结果是多少?怎么处理?

结果不是0.1,会丢失精度,用BigDecimal,注意使用字符串进行构造,如new BigDecimal(“0.09”);同样大整形的时候可以使用BigInteger。
浮点数转二进制,是把小数点后面的值一直*2,一直到小数点后面为0结束,如果到了小数的底数表示的位数的时候还没有结束,就会直接丢失。

八、short s= 1; s=s+1有问题没有?s+=1;有没有问题?

第一个有问题,第二个没问题,因为+=做了特殊处理,相当于+完之后强转了

九、请输出下面的结果(包装类和基本类型)

int a = 128;
int b = 128;
System.out.println(a == b); //①

Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); //②

Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); //③

解析:对于①,由于a和b都是基本数据类型,==对比的时候采用的是值对比,所以相同,打印true
对于②和③,由于是对包装类型进行对于,所以比较的是引用,然后,对于整数,127到-128之间的数据存在了常量缓冲区,所以a2和b2并没有重新创建对象,而是指定了同一个地址,所以③为true,而a1和b1超出了范围,会创建出新的对象,所以不同,②打印false

回到目录

JAVA引用类型

JAVA里面引用有四种类型:强引用、软引用、弱引用、虚引用

  • 强引用:可以用来直接访问对象,指向的对象永远都不会被JVM回收,哪怕抛出OOM异常;可能导致内存泄漏
  • 软引用:强引用之前最强的引用。当堆使用率接近阈值的时候,会被回收。引用的对象被回收之前,通过SoftReference类的get方法可以返回对象,回收之后会返回null。可以用于实现对内存敏感的缓存系统。
  • 弱引用:是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。所以也可以用来做缓存,当内存充足的时候,可以用来提速,内存不足的时候,会自动回收。
  • 虚引用:所有类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
  • 参看文档https://www.cnblogs.com/yueshutong/p/9531347.html

回到目录

运算符

一、最快的速度把一个数a*8

a << 3

二、&和&&的区别

  • 都可以做逻辑运算符,&还可以作为位运算符
  • &&是短路运算符,如果左边的表达式是false,右边不执行
    测试方式:
    if(b() && a()){
    	}
    	boolean a{
    		打印一句话
    		return true;
    	}
    
    	boolean b {
    		打印一句话
    		return false;
    
    	}
    

三、怎么交换a和b的值

  • 方式一
    int tmp = a ;
    a = b;
    b =tmp;
    
  • 方式二
    a = a+b;  
    b = a-b;  
    a = a-b;
    
  • 方式三
    a=a^b;
    b=a^b;
    a=a^b;
    
  • 方式四
    a = (a+b) - (b=a)
    

四、%运算符

有时候,笔试题目可能会遇到取模运算,有正数也有负数,我们只需要按照下面规则处理即可:

  • 结果的值:两个数的绝对值模运算的结果
  • 结果的符号:和第一个值的符号一样,负数取模是负数,整数取模是整数。

回到目录

字符串

一、String/StringBuffer/StringBuilder

相同点
这三个类都是用来处理字符串的。

是否可变
String是不可变字符串,StringBuffer和StringBuilder是可变字符串。

安全性
StringBuffer是线程安全的,效率较低;StringBuilder是线程不安全的,效率高一些。

二、String是否有length()方法,数组呢?

String有length()方法,数组没有,有lenth属性。

三、new String(“123”)会产生几个对象

1个或者2个,因为new,一定会在堆中开辟空间,如果”123”在字符串常量池已经存在,就不会再字符串常量池中创建对象了,这样就只会有1个;如果串池(字符串常量池)中没有,那么就会在串池中创建一个对象,这样,就有两个对象。

==和equals的区别

  • == 的作用
      基本类型:比较的就是值是否相同
      引用类型:比较的就是地址值是否相同
  • equals 的作用
      仅仅用于引用类型的比较。引用类型:默认情况下,比较的是地址值。
    注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同

回到目录

JAVA集合

一、集合概述

  1. 集合用来解决数组长度不可变的问题;
  2. Java中集合主要分为两种:单列的Collection和双列基于key-value的Map;
  3. Collection有两个子接口:List和Set。其中List是有序可重复的集合;Set是无序不可重复的接口;
  4. List常用的实现有ArrayList、LinkedList、Vector;
  5. Set常用实现有HashSet、TreeSet;
  6. Map主要实现有HashMap和HashTable和CurrentHashMap;

二、ArrayList/Vector/LinkedList

相同点

  1. 这三个类都是List的实现,所以都是有序可重复的集合 ;

底层结构

  • ArrayList和Vector是基于数组的,存放在连续的内存块,LinkedList底层基于链表的可以分散存储;
  • ArrayList和Vector查询快(通过索引也就是下标直接访问),增删慢(需要移动后面的元素);而LinkedList查询慢(需要基于指针从头一个一个查找,最坏的情况要遍历整个集合才能找到),增删快(指针移动到指定的位置之后);

线程安全性

  • ArrayList线程不安全,可以使用下面方式转换成线程安全的
    Collections.synchronizedList(List)
    
  • Vector线程安全(使用synchronized关键字锁定方法),然而,Vector能较低,所以一般不怎么使用(并没有废弃)
  • LinkedList线程不安全,可以使用ConcurrentLinkedQueue

大小和增长方式

  • ArrayList构造的时候,大小为0,第一次添加元素的时候,容量变成为默认大小10(构造没有指定容量的情况下);当元素个数超过集合容量的时候进行扩容,扩容方式为当前容量的1.5倍。
  • Vector构造的时候容量就是10;当元素个数超过集合容量的时候进行扩容,扩容为原来的2倍(如果构造的时候指定了增加元素个数,就按这个进行增长);
  • LinkedList是链表,所以不存在什么初始容量的问题;

三、CopyOnWriteArrayList

CopyOnWriteArrayList 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。
从 CopyOnWriteArrayList 的名字就能看出CopyOnWriteArrayList 是满足CopyOnWrite 的ArrayList,所谓CopyOnWrite 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。

四、HashMap/HashTable/ LinkedHashMap/ConcurrentHashMap

相同点

  • 这三个类都是都是Map的实现,也就是说,都是双列的集合,都是存放键值对的,底层实现都是基于数组+链表的结构,也就是hash表。

底层结构

  • HashMap和HashTable都是数组+链表的结构,需要注意的是,jdk8以后,在链表长度大于8的时候,HashMap的链会转换成红黑树结构;
  • 而LinkedHashMap还在此基础上维护了一个双向链表,用来保证元素插入的顺序,所以LinkedHashMap可以保证元素的插入顺序,其他的几个则不可以。

null值要求

  • HashMap的key和值都可以为null;
  • HashTable的键和值都不能为null;
  • LinkedHashMap的key和值都可以是null
  • ConcurrentHashMap的键和值可以为null;

容量和增长方式

  • HashMap : 默认大小16,负载因子0.75,当hash表的容量超过负载因子的时候开始扩容,扩容为原始容量的2倍。
  • HashTable:初始容量为11,负载因子为0.75。超过负载因子容量开始扩容,扩容为旧的容量2+1。
  • 扩容之后,会重新调整元素的位置,因为hash算法导致元素位置发生了变化(hash & (length-1))。

安全性和性能

  • HashMap是线程不安全的,所以效率高;
  • HashTable是基于synchronized实现的线程安全的Map,效率较低;
  • ConCurrentHashMap线程安全,但是锁定的只是一部分代码,所以效率比HashTable高。

五、ConcurrentHashMap底层实现

  • jdk8以前
    ConcurrentHashMap使用一个Segment 数组Segment< K,V >[] segments,Segment继承自ReenTrantLock,所以每个Segment就是个可重入锁,每个Segment 有一个HashEntry< K,V >数组用来存放数据,put操作时,先确定往哪个Segment放数据,只需要锁定这个Segment,执行put,其它的Segment不会被锁定;所以数组中有多少个Segment就允许同一时刻多少个线程存放数据,这样增加了并发能力。

  • jdk8之后
    去掉了分段锁,采用CAS + synchronized 控制并发操作,在某些方面提升了性能。对于节点Node中的value和next都用volatile修饰,保证并发的可见性。

六、WeakHashMap

会在某个key不再被引用的时候,remove掉这个key

   Map wmap = new WeakHashMap<>();
   // 添加键值对
   wmap.put(w1, "w1");
   wmap.put(w2, "w2");
   wmap.put(w3, "w3");

   // 打印出wmap
   System.out.println("原始Map" + wmap);

   // ---- 测试 WeakHashMap 的自动回收特性 ----

   // 将w1设置null。
   // 这意味着“弱键”w1再没有被其它对象引用,调用gc时会回收WeakHashMap中与“w1”对应的键值对
   w1 = null;
   // 内存回收。这里,会回收WeakHashMap中与“w1”对应的键值对
   System.gc();  

七、为什么hashmap的长度为2的n次方?

其实我觉得这个问题没有啥意义, 人家jdk底层要这么定义,关我毛事啊,但是如果面试官非得问,你可以如下回答:
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀。每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率远远不如位移运算,源码中做了优化hash&(length-1),hash%length==hash&(length-1)的前提是length是2的n次方;
这个算法在putVal里面获取下标的地方是能看到的

八、为什么hashtable的长度为2n+1

hashtable计算下标的算法为:int index = (hash & 0x7FFFFFFF) % tab.length;

九、为什么concurrenthashmap读操作不需要加锁?

因为get操作可以无锁是由于Node的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。

十、HashSet底层实现

HashSet底层其实存放了一个HashMap,往set里面添加的元素放在了HashMap的key里面,这也是HashSet元素无顺以及不能重复的原因,因为hashmap结构,就注定了key不能重复,同时也不会有顺序。

十一、数组转集合的几种方式

  • for循环实现
    static  List arrayToList(final T[] array) {
      final List l = new ArrayList(array.length);
    
      for (final T s : array) {
        l.add(s);
      }
      return (l);
    
  • 利用Arrays.asList方法
  • 使用 Java8 的Stream(推荐):Arrays.stream(myArray).collect(Collectors.toList());
  • 使用Apache Commons Collections
    List list = new ArrayList();
    CollectionUtils.addAll(list, str);
    
  • 使用Guava
    对于不可变集合,你可以使用ImmutableList类及其of()与copyOf()工厂方法:(参数不能为空)
    
    List il = ImmutableList.of("string", "elements");  // from varargs
    List il = ImmutableList.copyOf(aStringArray);      // from array
    对于可变集合,你可以使用Lists类及其newArrayList()工厂方法:
    
    List l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
    List l2 = Lists.newArrayList(aStringArray);               // from array
    List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
    

十二、哈希碰撞

什么是Hash碰撞

在HashMap中插入数据的时候,当我们对某一个元素进行hash函数计算以及干扰函数得到一个位置之后,发现这个位置已经被其他元素占用了,这种情况就是我们说的哈希碰撞,也叫哈希冲突。

解决办法

  1. 开放定址法:发生冲突,继续寻找下一块未被占用的存储地址。如:线性探测、平方探测、随机探测。
  2. 再hash:也就是有多个hash函数,当一个函数计算产生冲突之后,再用另外一个函数计算。
  3. 公共溢出区:把产生冲突的元素放在一个公共的溢出区里面;
  4. 链地址法:采用数组+链表的结构。HashMap采用的就是这种方式

HashMap产生冲突的时候元素是插在链表的头部还是尾部?为什么?

Java7以前会插在链的头部,因为这样的话,不需要把指针移动到最后面一个元素进行插入操作,效率更高。Java8以后链表长度超过8的时候就会转变成红黑树,插入的位置是根据大小调整的。

十三、HashMap的key有什么要求?

要求重写hashcode和equals方法。

十四、List集合里面的元素怎么排序

  1. 让集合中的对象类实现排序接口
    让定义的类实现Comparable接口,重写compareTo方法;然后调用java.util.Collections.sort(List)方法。这种方法的缺点在于要修改被排序的类;
  2. 编写一个排序比较器,实现Comparator
	static class MyComparator implements Comparator{
		@Override
		public int compare(User o1, User o2) {
			return o1.getId() - o2.getId();
		}
	}
然后调用
Collections.sort(List,Comparator),如:
	public static void main(String[] args) {
		List uList = new ArrayList<>();
		Collections.sort(uList, new MyComparator());
		
	}

当然,在jdk8以后,我们可以通过传入Lamda表达式来简化代码,我这里就不多赘述了
3. 使用jdk8提供的排序功能

List asks = .....
 asks.sort(Comparator.comparing(DepthItem::getPrice)); //按照价格升序
 asks.sort(Comparator.comparing(DepthItem::getPrice).reversed()); //按照价格降序

对于Integer类型的,可以在comparing里面传入:Integer::intValue

回到目录

异常

回到目录

如有错误,欢迎大家多多批评,持续待续中…

你可能感兴趣的:(面试)