思维导图
1. HashMap排序题, 上机题
已知一个HashMap<Integer, User> 集合,User 有name (String) 和age (int)属性。请写一个方法实现对HashMap的排序功能,该方法接收HashMap <Integer, User>为形参,返回类型为HashMap
<Integer, User>,要求对HashMap中的User的age倒序进行排序。排序时key=value键值对不得拆散。.
注意: 要做出这道题必须对集合的体系结构非常的熟悉HashMap 本身就是不可排序的,但是该道题偏偏让给HashMap排序,那我们就得想在API中有没有这样的Map结构是有序的,
LinkedHashMap, 对的,就是它,它是Map结构,也是链表结构,有序的,更可喜的是他是HashMap的子类,我们返回LinkedHashMap <Integer,User>即可,还符合面向接口(父类编程的思想)。但凡是对集合的操作,我们应该保持一个原则就是能用JDK中的API就有JDK中的API,比如排序算法我们不应该去用冒泡或者选择,而是首先想到用Collections集合工具类。
public class HashMapTest { public static void main(String[] args) { HashMap<Integer, User> users = new HashMap<>() ; users.put(1, new User ("张三",25) ) ; users.put(3, new User ("李四",22) ); users.put(2, new User ("王五",28) ) ; System.out.println(users) ; HashMap<Integer, User> sortHashMap = sortHas hMap (users) ; System. out . print ln (sortHashMap) ; /** *控制台输出内容 * {1=User [name=张三, age=25],2=User [name=王五, age=28], 3=User [name=李四, age=22] } {2=User [name=王五, age=28], 1=User [name=张三, age=25], 3-User [name=李四, age=22]} */ } public static HashMap<Integer, User> sortHashMap (HashMap<Integer, User> map) { //首先拿到map的键值对集合 Set<Entry<Integer, User>> entrySet = map.entrySet() ; // 将set 集合转为List集合,为什么,为了使用工具类的排序方法 List<Entry<Integer, User>> list = new ArrayList<Entry<Integer, User>> (entrySet); //使用Collections集合工具类对list进行排序,排序规则使用匿名内部类来实现 Collections.sort (list, new Comparator<Entry<Integer, User>>() { @Override public int compare (Entry<Integer, User> o1, Entry<Integer, User> o2) { //按照要求根据User的age的倒序进行排 return o2.getValue().getAge()-o1.getValue().getAge() ; }) ; //创建-一个新的有序的HashMap子类的集合 Linke dHashMap<Integer, User> linkedHashMap = new LinkedHashMap<Integer, User> () ; //将List中的数据存储在LinkedHashMap中 for (Entry<Integer, User> entry : list) { linkedHashMap.put (entry.getKey(), entry.getValue()) ; } //返回结果 return linkedHashMap; } }
2.集合的安全性问题
请问ArrayList、HashSet、 HashMap 是线程安全的吗?如果不是我想要线程安全的集合怎么办?
我们都看过上面那些集合的源码(如果没有那就看看吧) ,每个方法都没有加锁,显然都是线程不安全的。话又说过来如果他们安全了也就没第二问了。在集合中Vector和HashTable 倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized关键字。Collections工具类提供了相关的API,可以让上面那3个不安全的集合变为安全的。
//Collections. synch ronizedCollection (c ) //Collections. synch ronizedList (list) //Collec tions. synch roni ze dMap (m) //Collections. synchronizedSet (s)
上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实实现原理非常简单,就是将集合的核心方法添加了synchronized关键字。
3. ArrayList 内部用什么实现的?
(回答这样的问题,不要只回答个皮毛,可以再介绍一下 ArrayList内部是如何实现数组的增加和删除的,因为数组在创建的时候长度是固定的,那么就有个问题我们往ArrayList 中不断的添加对象,它是如何管理这些数组呢? )
ArrayList内部是用Object[]实现的。接下来我们分别分析ArrayList的构造、add、remove、 clear 方法的实现原理。
一、构造函数
1)空参构造
/** * Constructs a new {@code ArrayList} instance with zero initial capacity. * / public ArrayList() { array = EmptyArray.OBJECT;
array是一个Object[]类型。当我们new -个空参构造时系统调用了EmptyArray.OBJECT 属性, EmptyArray仅
仅是一个系统的类库,该类源码如下:
public final class EmptyArray { private EmptyArray() {} public static final boolean[] BOOLEAN = new boolean[0] ; public static final byte[] BYTE = new byte[0] ; public static final char[] CHAR = new char[0] ; public static final double[] DOUBLE = new double[0] ; public static final int[] INT = new int[0] ; public static final Class<?>[] CLASS = new Class[0] ; public static final Object[] OBJECT = new Object[0] ; public static final String[] STRING = new String[0] ; public static final Throwable[] THROWABLE = new Throwable[0] ; public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0]; }
也就是说当我们new -个空参ArrayList 的时候,系统内部使用了-个new Object[0]数组。
2)带参构造1
/** * Constructs a new instance of {@code ArrayList} with the specified * initial capacity. * @param capacity * the initial capacity of this {@code ArrayList} . */ public ArrayList(int capacity) { if (capacity < 0) { throw new IllegalArgumentException ("capacity < 0: " + capacity) ; } array = (capacity == 0 ? EmptyArray.OBJECT:new object [capacity]); }
该构造函数传入一个int值,该值作为数组的长度值。如果该值小于0,则抛出一个运行时异常。如果等于0,则使用一个空数组,如果大于0,则创建一个长度为该值的新数组。
3)带参构造2
/** * Constructs a new instance of {@code ArrayList} containing the elements of * the specified collection. * @param collection * the collection of elements to add. */ public ArrayList (Collection<? extends E> collection) { if (collection == null) { throw new Nul1PointerException("collection == null"); } object[] a = collection.toArray() ; if(a.getClass()!= object[].class) { object[] newArray = new object[a.length]; System.arraycopy(a,0,newArray,0, a.length) ; a = newArray; } array = a; size = a.length; }
如果调用构造函数的时候传入了一个Collection的子类,那么先判断该集合是否为null,为null则抛出空指针异常。如果不是则将该集合转换为数组a,然后将该数组赋值为成员变量array,将该数组的长度作为成员变量size.这里面它先判断a.getClass是否等于Object[].class,toArray方法是Collection接口义的因此其所有的子类都有这样的方法,list集合的toArray和Set集合的toArray
返回的都是Object[]数组。
其实在看Java源码的时候,作者的很多意图都很费人心思,我能知道他的目标是啥,但是不知道他为何这样写。比如对于ArrayList,array 是他的成员变量,但是每次在方法中使用该成员变量的时候作者都会重新在方法中开辟一个局部变量,然后给局部变量赋值为array,然后再使用,有人可能说这是为了防止并发修改array,毕竟array是成员变量,大家都可以使用因此需要将array变为局部变量,然后再使用,这样的说法并不是都成立的,也许有时候就是老外们写代码的一个习惯而已。