在总结集合前,先对比一下数组和集合这两个概念:
A:长度区别:数组的长度是固定的,集合的长度是可变的。
B:内容区别:数组存储的是同一个数据类型的元素,而集合可以存储不同类型的元素。
C:元素的数据类型不同:数组可以存储基本数据类型,也可以存储引用数据类型。集合只能存储引用数据类型。
由上可知:就大概明白了集合的出现是有一定原因的:,其长度可变,可存储不同类型的元素。举例:
ArrayList<Object>list=new ArrayList<>();
list.add("Is-Me-HL");//字符串
list.add(18);//整型
list.add(new Integer(18));//引用
list.add(3.1);//浮点类型
list.add(true);//布尔类型
System.out.println(list.toString());
//执行结果
[Is-Me-HL, 18, 18, 3.1, true]
可以看出来集合是可以存储不同类型的引用数据的。但是有人又会疑问道:上面不是说集合只能放的是引用类型的数据吗,你这个上面放的都是double,boolean类型什么的,这都是基本类型啊,上面是不是说错了?那么请看下面一段反编译之后代码:
ArrayList<Object> list = new ArrayList();
list.add("Is-Me-HL");
list.add(Integer.valueOf(18));
list.add(new Integer(18));
list.add(Double.valueOf(3.1D));
list.add(Boolean.valueOf(true));
System.out.println(list.toString());
实际上又编译器完成了自动装箱这一步,将基本类型转化为了对应的对象引用。JDK5开始就能够自动拆装箱。自动装箱和自动拆箱实际上是Java编译器提供的一颗语法糖(语法糖是指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通过可提高开发效率,增加代码可读性,增加代码的安全性)。
Collection:是单列集合的顶层接口。该集合是List,Set集合的父接口。JDK不提供此接口的任何直接实现:它提供了更具体的子接口的实现,如Set和List 。 该界面通常用于传递集合,并在需要最大的通用性的情况下对其进行操作。所有通用的Collection实现类(通常通过其子接口间接实现)应提供两个“标准”构造函数:一个void(无参数)构造函数,它创建一个空集合,以及一个构造函数, Collection ,它创建一个与其参数相同的元素的新集合。 实际上,后一个构造函数允许用户复制任何集合,生成所需实现类型的等效集合。
ArrayList<Object> list = new ArrayList<>();
list.add("Is-Me-HL");// 字符串
list.add(18);// 整型
list.add(new Integer(18));// 引用
list.add(3.1);// 浮点类型
list.add(true);// 布尔类型
ArrayList<Object> list2 = new ArrayList<>(list);
System.out.println(list2.get(0));
System.out.println(list2.toString());
集合的使用步骤:
A:创建集合对象
B:创建元素对象
C:把元素添加到集合
D:遍历集合:其中遍历集合又分为三步:
a:通过集合对象获取迭代器对象;
b:通过迭代器对象的hashNext()方法判断是否有元素;
c:通过迭代器对象的next方法获取元素并移动到下一个。
ArrayList<Object> list = new ArrayList<>();
list.add("Is-Me-HL");// 字符串
list.add(18);// 整型
list.add(new Integer(18));// 引用
list.add(3.1);// 浮点类型
list.add(true);// 布尔类型
// 获取迭代器对象
Iterator<Object> it = list.iterator();
while (it.hasNext()) {// 判断是否有笑一个元素
Object value = it.next();// 获取下一个元素
System.out.println(value);
}
//执行结果
Is-Me-HL
18
18
3.1
true
List集合:一个可包含重复元素有序的Collection集合,允许有null元素
有序的Collection集合(也称为序列)此接口的用户可以对列表中的插入位置进行精确地控制,可以根据索引访问元素,与set不同,列表通常允许重复的元素,底层是一个数组,返回值始终为true,所以重复的元素是可以存在的。底层是一个数组,返回值始终为true,所以重复元素是可以存在的。
//ArrayList集合add()方法JDK源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
List相对于Collection特有的功能方法:
(1)add(int index,Object element)在指定位置上添加元素。
(2)remove(int index)根据索引删除元素,返回删除的该元素。
(3)set(int index,object element)根据索引修改元素。
(4)ListIterator listIterator():List集合特有的迭代器.
(5)get(int index)方法,得到指定位置的元素
List常见子类的区别:
ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高。List list=new ArrayList();
Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低。List list2=new Vector();
LinkedList:底层数据结构式链表,查询慢,增删快,线程不安全,效率高。List list3=new LinkedList();
List子类遍历集合的三种方式:迭代器、增强for、普通for:举例如下:
List<Object> list = new ArrayList<>();
list.add("Is-Me-HL");// 字符串
list.add(18);// 整型
list.add(new Integer(18));// 引用
list.add(3.1);// 浮点类型
list.add(true);// 布尔类型
// iterator遍历
Iterator<Object> it = list.iterator();
while (it.hasNext()) {// 判断是否有笑一个元素
Object value = it.next();// 获取下一个元素
System.out.println(value);
}
System.out.println("------------------");
// 增强for遍历
for (Object s : list) {
System.out.println(s);
}
System.out.println("------------------");
// 普通for遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
此处再补充几点说明:
(1)当一个函数的参数为Object类型(引用类型)时,你也可以填写基本类型,JDK5之后提供了自动装箱功能,如ArratList.add(10)等价于ArrayList.add(new Integer(18));
(2)泛型:是一种把类型明确的工作推迟到创建对象或者调用方法的时候采取明确的特殊类型,参数化类型:把类型当做参数一样的传递。格式:<数据类型>此处的数据类型只能是引用类型,不可以是基本类型。好处:A:把运行时期的问题提前到了编译时期,B;避免了强制类型转换,C:又花了程序设计,解决了黄色警告线。
(3)ListIterator由名字可知,这是List集合特有的迭代器,好玩的地方在哪呢》之前我们讲的Iterator迭代器只能是正序遍历,即从头到尾遍历,而这个ListIterator则可以逆向遍历:
// ListIterator遍历
ListIterator<Object> it = list.listIterator();
while (it.hasNext()) {// 判断是否有笑一个元素
Object value = it.next();// 获取下一个元素
System.out.println(value);
}
System.out.println("------------------");
while (it.hasPrevious()) {// 判断是否有笑一个元素
Object value = it.previous();// 获取下一个元素
System.out.println(value);
}
//执行结果
Is-Me-HL
18
18
3.1
true
------------------
true
3.1
18
18
Is-Me-HL
(4)Java获取数据的长度大小:
针对数组提供了length属性来获取数组的长度;
针对字符串提供了length()方法来获取字符串的长度;
针对泛型集合类提供了size()方法来获取元素的个数。
Set集合:一个不包含重复元素的无序Collection集合,允许有null元素
无序:(存储顺序和取出顺序不一致),只能使用迭代器和增强for循环遍历集合,普通循环是List数组特有的。
Set<Object> set = new HashSet<>();
set.add("Is-Me-HL");// 字符串
set.add(18);// 整型
set.add(new Integer(18));// 引用
set.add(true);// 布尔类型
set.add(3.1);// 浮点类型
// 迭代器遍历
Iterator<Object> it = set.iterator();
while (it.hasNext()) {
Object value = it.next();
System.out.println(value);
}
System.out.println("-------------");
// 增强for循环
for (Object s : set) {
System.out.println(s);
}
System.out.println("-------------");
// 普通for循环(无法使用)
for (int i = 0; i < set.size(); i++) {
System.out.println();// 此处set集合没有get(int index)方法,所以普通for无法使用
}
//执行结果
18
3.1
Is-Me-HL
true
-------------
18
3.1
Is-Me-HL
true
-------------
从上面的代码中可以看出刚开始讲Set集合的两个特点,一个是去重,添加了五个元素,实际上只有四个被加入到集合中,一个呗当做重复去掉。另一个特点是无序,根据添加的顺序和遍历出元素的数据可以发现,Set集合无序的特点,它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。最后还要注意的一点是:虽然Set集合的元素是无序的,但是作为集合来说,他肯定有他自己的存储顺序,而你添加元素的顺序如果正好和它遍历出来的顺序一致,这也不代表它有序!
HashSet:为什么存储字符串的时候,内容相同的只存储了一个呢?
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();。HashSet跟HashMap一样,都是一个存放链表的数组。HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。更确切的说,HashSet中的元素,只是存放在了底层HashMap的key上, 而value使用一个static final的Object对象标识。
关于Set集合存放对象:
通过查看add方法的源码,我们知道这个方法的底层是依赖两个方法hashCode()和equals()。
首先比较hashCode,如果相同,就继续走,比较地址值或者走equals(),如果不同就直接添加到里面。
hashSet记得需要在pojo中重写hashCode()和equals(),这两个重写是通过自动生成完成的,这样才能保证元素的唯一性。
Set<Object> set = new HashSet<>();
User user1=new User("zhangsan","123");
User user2=new User("lisi","456");
User user3=new User("zhangsan","123");
set.add(user1);
set.add(user2);
set.add(user3);
// 迭代器遍历
Iterator<Object> it = set.iterator();
while (it.hasNext()) {
Object value = it.next();
System.out.println(value.toString());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
在User类中重写equals()和hashCode()方法,自动生成:右键--Source--Generate hashCode() and equals()...
linkedHashSet:底层 数据结构是有哈希表和表表组成。哈希表保证元素的唯一性。链表保证元素有序(存储和取出是一致的)
Set<Object> set = new LinkedHashSet<>();
User user1=new User("zhangsan","123");
User user2=new User("lisi","456");
User user3=new User("zhangsan","123");
User user4=new User("wangwu","123");
User user5=new User("zhaoliu","123");
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
set.add(user5);
// 迭代器遍历
Iterator<Object> it = set.iterator();
while (it.hasNext()) {
Object value = it.next();
System.out.println(value.toString());
}
//执行结果
User [username=zhangsan, password=123]
User [username=lisi, password=456]
User [username=wangwu, password=123]
User [username=zhaoliu, password=123]
TreeSet:能够对元素按照某种规则进行排序。有序不可重复集,具有以下特点:
1、数据会按自然排序(可以理解为从小到大排序)
2、不可存储null
3、数据不可重复
4、非线程安全
自然排序(元素具备比较性):new TreeSet<>(),对基本类型自然排序能够去掉重复元素保证唯一性,也能排好顺序。但如果自然排序的是复合类型,就要pojo类实现comparable接口。
TreeSet存放基本类型:
Set<Object> set = new TreeSet<>();
set.add(12);
set.add(18);
set.add(14);
set.add(12);
// 迭代器遍历
Iterator<Object> it = set.iterator();
while (it.hasNext()) {
Object value = it.next();
System.out.println(value.toString());
}
//执行结果
12
14
18
TreeSet存放复合类型:
方案一:通过pojo类实现Comparable接口:
Set<Object> set = new TreeSet<>();
User user1=new User("zhangsan","123");
User user2=new User("lisi","456");
User user3=new User("zhangsan","456");
User user4=new User("wangwu","123");
User user5=new User("zhaoliu","123");
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
set.add(user5);
Iterator<Object> it = set.iterator();
while (it.hasNext()) {
Object value = it.next();
System.out.println(value.toString());
}
//执行结果
User [username=lisi, password=456]
User [username=wangwu, password=123]
User [username=zhaoliu, password=123]
User [username=zhangsan, password=123]
User [username=zhangsan, password=456]
public class User implements Comparable<User> {
private String username;
private String password;
......
@Override
public int compareTo(User o) {
int num1=this.username.length()-o.username.length();
int num2=num1==0?this.password.compareTo(o.password):num1;
return num2;
}
方案二:比较器排序(集合具备比较性),让集合的构造方法接收一个比较器的子类对象Comparator。
Set<User> set = new TreeSet<User>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
int num1=o1.getUsername().length()-o2.getUsername().length();
int num2=num1==0?o1.getPassword().compareTo(o2.getPassword()):num1;
return num2;
}
});
User user1=new User("zhangsan","123");
User user2=new User("lisi","456");
User user3=new User("zhangsan","456");
User user4=new User("wangwu","123");
User user5=new User("zhaoliu","123");
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
set.add(user5);
Iterator<User> it = set.iterator();
while (it.hasNext()) {
Object value = it.next();
System.out.println(value.toString());
}
//执行结果
User [username=lisi, password=456]
User [username=wangwu, password=123]
User [username=zhaoliu, password=123]
User [username=zhangsan, password=123]
User [username=zhangsan, password=456]
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
最后还有注意的一点是TreeSet是基于TreeMap的,底层是红黑树(一种自平衡的二叉树)。(ps:这个度娘一大堆讲解)
Map:Map子集合的顶层父接口。
常见的Map子集合有:HashMap、TreeMap。
HashMap:基于哈希表的实现的Map接口。 此实现提供了所有可选的地图操作,并允许null的值和null键。
Map<String,String>map=new HashMap<>();
map.put("12", "321");
map.put(null, "2243");
map.put("31", "123");
Set<Entry<String, String>>set=map.entrySet();
//map集合遍历方式一
for(Entry<String, String>entry:set) {
System.out.println(entry.getKey()+"\t"+entry.getValue());
}
System.out.println("------------");
//map集合遍历方式二
Set<String> set2=map.keySet();
for(String s:set2) {
String value=map.get(s);
System.out.println(s+"\t"+value);
}
//执行结果
null 2243
12 321
31 123
------------
null 2243
12 321
31 123
Hashtable和HashMap的区别:
Hashtable:线程安全的,效率低。不允许null键和null值
HashMap:线程不安全的,效率高,允许null键和null值
TreeMap:一个红黑树基于NavigableMap
实现。(使用同HashMap类似,关于红黑树的原理自行百度。)
总结:
集合名称 | 顶层接口 | 底层实现 | 默认初始容量 | 加载因子 | 扩容增量 | 是否存在重复元素 | 能否存放null值 | 是否同步 |
ArrayList | Collection | 数组 | 10 | 1 | 0.5倍+1 | 是 | 能 | 不同步 |
Vector | Collection | 数组 | 10 | 1 | 1倍 | 是 | 能 | 同步 |
LinkedList | Collection | |||||||
HashSet | Collection | HashMap | 16 | 0.75 | 1倍 | 否 | 能 | 不同步 |
LinkedHashSet | Collection | |||||||
TreeSet | Collection | |||||||
HashMap | Map | 16 | 0.75 | 1倍 | 否 | 能 | 不同步 | |
TreeMap | Map | |||||||
Hashtable | Map | 16 | 0.75 | 1倍+1 | 否 | 否 | 同步 |
注:以上文章仅是个人学习过程总结,若有不当之处,望不吝赐教。