【JAVA面试题整理】JAVA基础(三)

JAVA的集合类

一、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即可,还符合面向接口。

单发是对集合的操作,我们应该保持一个原则就是能用JDK中的API就用,比如排序算法我们不应该用冒泡或者选择,而是首先想到Collections集合工具类。

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 = sortHashMap(users);
    System.out.println(sortHashMap);
}


public static HashMap<Integer, User> sortHashMap(HashMap<Integer, User> map) {
    // 首先拿到map键值对集合
    Set<Map.Entry<Integer, User>> entrySet = map.entrySet();
    // 将set集合转为List集合,为了使用工具类的排序方法
    List<Map.Entry<Integer, User>> list = new ArrayList<>(entrySet);
    // 使用Collections集合工具类对list进行排序,排序规则使用匿名内部类来实现
    Collections.sort(list, new Comparator<Map.Entry<Integer, User>>() {
        @Override
        public int compare(Map.Entry<Integer, User> o1, Map.Entry<Integer, User> o2) {
            // 按照规则根据User的age的倒叙进行排序
            return o2.getValue().getAge() - o1.getValue().getAge();
        }
    });
    // 创建一个新的有序的HashMap子类的集合
    LinkedHashMap<Integer, User> linkedHashMap = new LinkedHashMap<>();
    // 将List中数据存储在LinkedHashMap中
    for (Map.Entry<Integer, User> entry : list) {
        linkedHashMap.put(entry.getKey(), entry.getValue());
    }
    // 返回结果
    return linkedHashMap;
}

二、集合的安全性问题

集合的安全性指的就是多线程模式中,集合是否还安全,众所周知,Vector是线程安全的,ArrayList线程不安全,HashTable线程安全,HashMap线程不安全。打开源码就会发现,大部分线程安全的操作,就是在核心方法中加上synchronized关键字。

Collections工具类中提供了相关的API,可以让部分集合变为线程安全

三、ArrayList内部是怎么实现的?

ArrayList使用Object[]数组实现的。

1、构造函数

a.空参构造函数

private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

当空参的时候,直接创建了一个空的不可修改的数组

b.构造函数1

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

当构造函数传入参数是整型时,代表创建了一个自定义长度的数组

c.构造函数2

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

当构造函数为集合时,第二行会将集合转为数组的形式,至于为什么下面要对elementData.getClass() != Object[].class进行判断,我也说不好,因为在第二行转换过后,一般该条件都不成立。

2、add方法

a.构造函数1

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

当构造函数的参数是单独一个泛型时,构造函数中ensureCapacityInternal方法要计算add之后需要的空间

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

这个方法里嵌套了两个函数,外层的函数 ensureExplicitCapacity要给ArrayList进行扩容操作,里层函数calculateCapacity是为了计算当前ArrayList需要的空间大小是多少

private static final int DEFAULT_CAPACITY = 10;
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

这里可以看出,add方法执行的是时候,默认最小的ArrayList空间大小为DEFAULT_CAPACITY,也就是10。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;


    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

在这里可以看出,当add方法执行时,如果数组空间大小不够,会根据grow方法中的条件,进行扩容

b.构造方法2

public void add(int index, E element) {
    rangeCheckForAdd(index);


    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

另一个add方法是要在指定的位置插入指定的元素,实现方法哈上一个构造方法大体相同。

3、remove方法

remove方法有两种方式,第一种是传入int,第二种是传入Object,这里我们只讲第一种,感兴趣的小伙伴可以自行查阅源代码了解另一种实现方式。

public E remove(int index) {
    rangeCheck(index);


    modCount++;
    E oldValue = elementData(index);


    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work


    return oldValue;
}

这里我们看到, E oldValue = elementData(index)这句会通过传入的int参数获取到对应的数组元素,然后通过      System.arraycopy方法对数组进行复制,同时对原数组进行remove操作

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

操作之后改变了原数组的内容,进而实现了对ArrayList集合的remove操作。

4、clear方法

public void clear() {
    modCount++;


    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;


    size = 0;
}

这个方法的目的很清晰,就是要将ArrayList集合置空,具体的2实现就是先把原数组中所有的元素都修改成null,然后将集合的的size修改成0,达到集合置空的效果。

四、List三个子类的特点

ArrayList:底层实现是数组,查询快,增删慢;

LinkedList:底层实现是链表,查询慢,增删快;

vector:底层是现实数组,线程安全,查询慢,增删慢;

五、List、Set、Map的特点、实现类和区别

特点:

  1. List和Set是单列存储数据的集合,Map是双列(键值对)存储数据的集合;

  2. List有序且允许重复;

  3. Map无序,key不允许重复,value可以重复;

  4. Set是无序的,且不允许重复;

实现类:

List:LinkedList、ArrayList、vector

Map:HashMap、HashTable、LinkedHashMap、TreeMap

Set:HashSet、LinkedHashSet

区别:

同特点;

六、HashTable和HashMap有什么区别?

  1. HashMap线程不安全,不允许key重复,允许key为空值;

  2. HashTable线程安全,不允许key或者value为空值;

  3. 由于非线程安全,HashMap比HashTable访问效率要高一些;

七、List a=new ArrayList()和ArrayList a = new ArrayList()有什么区别?

前者创建了一个ArrayList对象之后把它上溯到List,因此他是一个List对象,有些ArrayList中特有的方法便不能再用了。

八、Collection和Map的继承体系

Collection:

Map:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值