前言:
目前姐妹们对“多个重载的方法参数个数可能是一样的”可能比较注重,兄弟们都需要学习一些“多个重载的方法参数个数可能是一样的”的相关知识。那么小编在网摘上收集了一些有关“多个重载的方法参数个数可能是一样的””的相关内容,希望你们能喜欢,各位老铁们一起来了解一下吧!1.为什么使用ConcurrentHashMap而不是HashMap或Hashtable?
HashMap的缺点:主要是多线程同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环,CPU达到100%,所以在并发情况下不能使用HashMap。让HashMap同步:Map m = Collections.synchronizeMap(hashMap);而Hashtable虽然是同步的,使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的原理:
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因在于所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap的结构:
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入互斥锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,当对某个HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
ConcurrentHashMap的构造、get、put操作:
构造函数:传入参数分别为 1、初始容量,默认16 2、装载因子 装载因子用于rehash的判定,就是当ConcurrentHashMap中的元素大于装载因子*最大容量时进行扩容,默认0.75 3、并发级别 这个值用来确定Segment的个数,Segment的个数是大于等于concurrencyLevel的第一个2的n次方的数。比如,如果concurrencyLevel为12,13,14,15,16这些数,则Segment的数目为16(2的4次方)。默认值为static final int DEFAULT_CONCURRENCY_LEVEL = 16;。理想情况下ConcurrentHashMap的真正的并发访问量能够达到concurrencyLevel,因为有concurrencyLevel个Segment,假如有concurrencyLevel个线程需要访问Map,并且需要访问的数据都恰好分别落在不同的Segment中,则这些线程能够无竞争地自由访问(因为他们不需要竞争同一把锁),达到同时访问的效果。这也是为什么这个参数起名为“并发级别”的原因。默认16.
初始化的一些动作:
初始化segments数组(根据并发级别得到数组大小ssize),默认16
初始化segmentShift和segmentMask(这两个全局变量在定位segment时的哈希算法里需要使用),默认情况下segmentShift为28,segmentMask为15
初始化每个Segment,这一步会确定Segment里HashEntry数组的长度.
put操作:
1).判断value是否为null,如果为null,直接抛出异常。
2).key通过一次hash运算得到一个hash值。将得到hash值向右按位移动segmentShift位,然后再与segmentMask做&运算得到segment的索引j。即segmentFor方法
3、使用Unsafe的方式从Segment数组中获取该索引对应的Segment对象。向这个Segment对象中put值,这个put操作也基本是一样的步骤(通过&运算获取HashEntry的索引,然后set)。
get操作:
1).和put操作一样,先通过key进行hash确定应该去哪个Segment中取数据。
2).使用Unsafe获取对应的Segment,然后再进行一次&运算得到HashEntry链表的位置,然后从链表头开始遍历整个链表(因为Hash可能会有碰撞,所以用一个链表保存),如果找到对应的key,则返回对应的value值,如果链表遍历完都没有找到对应的key,则说明Map中不包含该key,返回null。
定位Segment的hash算法:(hash >>> segmentShift) & segmentMask
定位HashEntry所使用的hash算法:int index = hash & (tab.length - 1);
注:
1).tab为HashEntry数组
2).ConcurrentHashMap既不允许null key也不允许null value。
2.LinkedList 和 PriorityQueue 的区别?
它们均是Queue接口的实现。拥有FIFO的特点,它们的区别在于排序行为。LinkedList 支持双向列表操作, PriorityQueue 按优先级组织的队列,元素的出队次序由元素的自然排序或者由Comparator比较器指定。
3.如何对一组对象进行排序?
如果需要对一个对象数组进行排序,我们可以使用Arrays.sort()方法。如果我们需要排序一个对象列表,我们可以使用Collections.sort()方法。排序时是默认根据元素的自然排序(使用Comparable)或使用Comparator外部比较器。Collections内部使用数组排序方法,所有它们两者都有相同的性能,只是Collections需要花时间将列表转换为数组。
4.Comparable和Comparator接口区别?
Comparator位于包java.util下,而Comparable位于包java.lang下
如果我们需要使用Arrays或Collections的排序方法对对象进行排序时,我们需要在自定义类中实现Comparable接口并重写compareTo方法,compareTo方法接收一个参数,如果this对象比传递的参数小,相等或大时分别返回负整数、0、正整数。Comparable被用来提供对象的自然排序。String、Integer实现了该接口。
Comparator比较器的compare方法接收2个参数,根据参数的比较大小分别返回负整数、0和正整数。 Comparator 是一个外部的比较器,当这个对象自然排序不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
5.什么是不可变对象(immutable object)?Java 中怎么创建一个不可变对象?
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。
如何在Java中写出Immutable的类?
要写出这样的类,需要遵循以下几个原则:
1)immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。
2)Immutable类的所有的属性都应该是final的。
3)对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)。
4)对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。
5)如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)
6.final、finalize 和 finally 的不同之处?
final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。
7.遍历一个List有哪些不同的方式?
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//for循环
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//增强for循环
for(String str : list){
System.out.println(str);
}
//迭代器迭代
Iterator it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//lamba 表达式 (java8)
list.forEach(n -> System.out.println(n));
8.说出几条 Java 中方法重载的最佳实践?
下面有几条可以遵循的方法重载的最佳实践来避免造成自动装箱的混乱。
a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参数。
b)不要重载参数数量一致,而只是参数顺序不同的方法。
c)如果重载的方法参数个数多于 5 个,采用可变参数。
9.接口是什么?为什么要使用接口而不是直接使用具体类?
接口用于定义 API。它定义了类必须得遵循的规则。同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口中不允许普通方法,以此来保证抽象,但是 Java 8 中你可以在接口声明静态方法和默认普通方法。
10.说出 JDK 1.7,JDK1.8的几个新特性?
虽然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性,如 try-with-resource 语句,这样你在使用流或者资源的时候,就不需要手动关闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。允许 Switch 中有 String 变量和文本。菱形操作符(<>)用于泛型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。另一个值得一提的特性是改善异常处理,如允许在同一个 catch 块中捕获多个异常。
Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性: Lambda 表达式,允许像对象一样传递匿名函数 Stream API,充分利用现代多核 CPU,可以写出很简洁的代码 Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用 扩展方法,现在,接口中可以有静态、默认方法。 重复注解,现在你可以将相同的注解在同一类型上使用多次。