一文搞懂java中List、Set各种集合实现类的区别

以下是java中常见的容器简单的关系图:

List,也就是我们前面介绍最多的有序集合,它提供了方便的访问、插入、删除等操作。
Set,Set 是不允许重复元素的,这是和 List 最明显的区别,也就是不存在两个对象 equals 返回 true。我们在日常开发中有很多需要保证元素唯一性的场合。
Queue/Deque,则是 Java 提供的标准队列结构的实现,除了集合的基本功能,它还支持类似先入先出(FIFO, First-in-First-Out)或者后入先出(LIFO,Last-In-First-Out)等特定行为。这里不包括 BlockingQueue,因为通常是并发编程场合,所以被放置在并发包里。
 

 1. List接口实现类:

List接口的实现类包括:ArrayList、LinkedList、Vector、Stack。都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。

ArrayList和LinkedList都是按照被存进去的顺序保存的。存储的数据允许有重复的,可以根据下标获取对应的元素。

ArrayList使用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好很多。ArrayList 是可以根据需要调整容量,增加之前容量的50%。

LinkedList是 Java 提供的双向链表保存数据,所以它不需要像上面两种那样调整容量,它也不是线程安全的。

Vector是 Java 早期提供的线程安全的动态数组,如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector 内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。vector扩容时会提高一倍。

Vector 和 ArrayList 作为动态数组,其内部元素以数组形式顺序存储的,所以非常适合随机访问的场合。除了尾部插入和尾部删除元素,往往性能会相对较差,比如我们在中间位置插入一个元素,需要移动后续所有元素。

而 LinkedList 进行节点插入、删除却要高效得多,但是随机访问性能则要比动态数组慢。

 2. Set接口实现类:

HashSet、LinkedHashSet、SortedSet、TreeSet都是set类型,保存的对象元素都是不重复的。并且是不能通过下标来获取元素的。因为Set实现了Iterable,所以可以通过迭代获取每个元素。比如:

HashSet<String> hashSet = new HashSet<>();
hashSet.add("aaa");
hashSet.add("bbb");
hashSet.add("ccc");
hashSet.add("222");
hashSet.add("111");
hashSet.add("aaa");
 
Iterator<String> iterator = hashSet.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next() + " ");
}
// 输出结果:
// aaa 111 ccc 222 bbb

HashSet是利用哈希算法,理想情况下,如果哈希散列正常,可以提供常数时间的添加、删除、包含等操作,但是它不保证有序。专为快速查找而设计的Set,存入的HashSet的元素必须定义HashCode()。通常默认使用HashSet。

TreeSet集合中的元素存储在红-黑树数据结构中,支持自然顺序访问,但是添加、删除、包含等操作要相对低效(log(n) 时间)。按数字字母的顺序排序的(数字优于字母),如果是汉字则按字符串的hashCode大小排序。里面的元素必须有实现Comparable接口,重写compareTo()方法,因为TreeSet的排序是通过compareTo()方法实现的。TreeSet类实现了NavigableSet接口,NavigableSet接口继承了SortedSet接口,因此TreeSet比其他Set多了些方法。比如:

TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("abc");
treeSet.add("集合");
treeSet.add("aaa");
treeSet.add("上海");
treeSet.add("123");
treeSet.add("bbb");
System.out.println(String.format("%-15s", "TreeSet:") + treeSet);
for (String string : treeSet) {
    System.out.println(string + " 的hashCode是:" + string.hashCode());
}
// TreeSet:       [123, aaa, abc, bbb, 上海, 集合]
123 的hashCode是:48690
aaa 的hashCode是:96321
abc 的hashCode是:96354
bbb 的hashCode是:97314
上海 的hashCode是:647341
集合 的hashCode是:1218050
 
TreeSet<String> sortedSet = new TreeSet<String>();
Collections.addAll(sortedSet, "one tow three four five six seven eight nine ten".split(" "));
System.out.println(String.format("%-15s", "SortedSet:") + sortedSet);
System.out.println("sortedSet中第一个元素:" + sortedSet.first() + "  sortedSet中最后一个元素:" + sortedSet.last());
System.out.println("根据元素截取,前闭后开:" + sortedSet.subSet("five", "one"));
System.out.println("截取此元素前面的所有元素(不包含此元素):" + sortedSet.headSet("six"));
System.out.println("截取此元素后面所有的元素(包含此元素):" + sortedSet.tailSet("six"));
// SortedSet:     [eight, five, four, nine, one, seven, six, ten, three, tow]
sortedSet中第一个元素:eight  sortedSet中最后一个元素:tow
根据元素截取,前闭后开:[five, four, nine]
截取此元素前面的所有元素(不包含此元素):[eight, five, four, nine, one, seven]
截取此元素后面所有的元素(包含此元素):[six, ten, three, tow]
 
// 测试对象
public class TestObject implements Comparable<TestObject> {
    private String name;
    private String address;
    private int age;
 
    public TestObject(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestObject that = (TestObject) o;
        return age == that.age &&
                Objects.equals(name, that.name) &&
                Objects.equals(address, that.address);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, address, age);
    }
 
    @Override
    public String toString() {
        return "TestObject{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                '}';
    }
 
    @Override
    public int compareTo(TestObject o) {
        return Integer.compare(this.hashCode(), o.hashCode());
    }
}
//测试
TreeSet<String> set = new TreeSet<String>();
set.add(new TestObject("andy", "beijing", 10));
set.add(new TestObject("andy", " beijing", 10));
set.add(new TestObject("tommy", "shanghai", 3));
set.add(new TestObject("john", "shanghai", 1));
set.add(new TestObject("a", "b", 1));
set.add(new TestObject("a", "b", 1));
System.out.println(String.format("%-15s", "将对象存进TreeSet:") + testTreeSet);
for (TestObject obj : testTreeSet) {
    System.out.println(obj + " 的hashCode是:" + obj.hashCode());
}
//将对象存进TreeSet:  [TestObject{name='andy', address=' beijing', age=10}, TestObject{name='a', address='b', age=1}, TestObject{name='andy', address='beijing', age=10}, TestObject{name='john', address='shanghai', age=1}, TestObject{name='tommy', address='shanghai', age=3}]
TestObject{name='andy', address=' beijing', age=10} 的hashCode是:-1869724531
TestObject{name='a', address='b', age=1} 的hashCode是:126047
TestObject{name='andy', address='beijing', age=10} 的hashCode是:134243437
TestObject{name='john', address='shanghai', age=1} 的hashCode是:1446317824
TestObject{name='tommy', address='shanghai', age=3} 的hashCode是:1458987957

LinkedHashSet也使用了散列函数,内部构建了一个记录插入顺序的双向链表,因此提供了按照插入顺序遍历的能力。保留了HashSet的快速查询的优点。于是在使用迭代遍历Set的时候,结果会按照元素插入的次序显示。里面的元素也必须定义hashCode()。

 在遍历元素时,HashSet 性能受自身容量影响,所以初始化时,除非有必要,不然不要将其背后的 HashMap 容量设置过大。而对于 LinkedHashSet,由于其内部链表提供的方便,遍历性能只和元素多少有关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值