1、为什么要使用集合
在没有学习集合之前,保存多个数据,一般都使用数组,那么数组有哪些不足的地方呢?集合相对于数组又有哪些好处呢?
数组的不足之处:
- 数组的长度在一开始就必须指定,而且一旦指定,就不能修改
- 保存的必须是同一类型的元素
- 使用数组进行增删元素是比较麻烦
集合相较于数组的好处:
- 可以动态保存任意多个对象,使用比较方便
- 提供了一系列方便操作对象的方法:add、remove、set、get等,后续细说。
- 使用集合添加、删除新的元素比较简洁
2、集合的框架体系
Java的集合类有很多,主要分为两大类 :如图
Collection主要有两个子接口 List 和 Set 也就是所谓的单列集合
Map接口的实现子类是双列集合,也就是存放的键值对(K - V) 双列集合
3、Collection接口及其实现类
Collection的子接口主要是 List 和 Set
3.1、List接口的和其常用方法
List实现子类的特点:
- List集合类中元素有序(即添加顺序和取出顺序一致)、 且可重复
- List集合中的每个元素都有相对应的顺序索引,支持索引
- List中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据其序号存取容器中对应的元素。
List接口实现类ArrayList和LinkedList
linkedList的用法和ArrayList的用法基本一致。在后续区别见LinkedList的特有方法
- ArrayList的特点
//List集合类中元素有序(即添加顺序和取出顺序一致)、 且可重复
List list = new ArrayList();
list.add(0);
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(4);
list.add(null);
//输出为:list= [0, 1, 2, 3, 4, 4, null]
System.out.println("list= " + list);
//List集合中的每个元素都有相对应的顺序索引,支持索引,索引从零开始
System.out.println("第三号元素为: " + list.get(3));//输出 3
- ArrayList实现类的常用方法
List list = new ArrayList();
list.add("红楼梦");
list.add("水浒传");
list.add("三国演义");
list.add("西游记");
//list= [红楼梦, 水浒传, 三国演义, 西游记]
System.out.println("list= " + list);
//1.add(int index , Object ele) 在index位置插入ele元素,原来位置及后续元素依次后移一位
list.add(1,"曹雪芹");
//list= [红楼梦, 曹雪芹, 水浒传, 三国演义, 西游记]
System.out.println("list= " + list);
//2.addAll(int index , Collection eles);从index位置开始,把eles全部添加到集合,原来位置及后续元素依次后移
List list1= new ArrayList();
list1.add(9);
list1.add(8);
list1.add(7);
list.addAll(1,list1);
//list=[红楼梦, 9, 8, 7, 曹雪芹, 水浒传, 三国演义, 西游记]
System.out.println("list=" + list);
//3.get(int index) 获取指定index位置的元素
System.out.println(list.get(6));//输出 三国演义
//4.indexOf(Object obj)返回obj在集合中首次出现的位置
System.out.println(list.indexOf("三国演义"));//输出 6
// lastIndexOf() 返回元素在集合中最后出现的位置
//5.remove(int index) 移除指定位置的元素,并返回此元素
System.out.println(list.remove(0));//红楼梦
//list= [9, 8, 7, 曹雪芹, 水浒传, 三国演义, 西游记]
System.out.println("list= " + list);
//6.set(int index , Object ele) 设置指定位置的元素为ele,相当于替换
list.set(1,"三国演义");
//list= [9, 三国演义, 7, 曹雪芹, 水浒传, 三国演义, 西游记]
System.out.println("list= " + list);
//7.subList() 截取子列表 左闭右开
List list1 = list.subList(1, 3);
//8.toArray() 将集合转换为数组
Object[] objects = list.toArray();
System.out.println("数组的长度为:" + objects.length);
//Arrays.asList() 将数组转换为集合
List obj = Arrays.asList(objects);
//9.clear() 清除列表所有数据
list.clear();
//10.isEmpty() 判断列表是否为空(没有元素) 空返回True 否则返回false
System.out.println("是否为空:"+list.isEmpty());
//11.contains() 判断集合中是否存在某个字符串
System.out.println("判断是否包含abc:" + list.contains("三国演义"));
//12.size()获取集合中存了几个数据。
System.out.println(list.size());
ArrayList和LinkedList的区别
ArrayList在于底层用的实现方法是数组,而LinkedList使用的是双向链表
这就使得LinkedList在增删上快于ArrayList ,ArrayList的读写快于LinkedList
且其使用双向链表就就使得其有部分的方法不同于Arraylist的方法
- ArrayList的底层逻辑(不追源码,只浅谈)
-
ArrayList中维护了一个Object类型的数组elementData.
-
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1
次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1。5倍。 -
如果使用的是指定大小的构造器,则初始elementData容量为指定大小, 如果需要扩容
则直接扩容elementData为1.5倍。
- LinkedList’的底层逻辑
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点每个节点(Node对象) ,里面又维护了prev、next、 item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
- 所以LinkedList的元素的添加和删除, 不是通过数组完成的,相对来说效率较高。
- LinkedList特殊方法
LinkedList list = new LinkedList();
list.add(5);
list.add(7);
list.add(9);
//在第一个位置和最后一个位置插入数据
list.addFirst(1);
list.addLast(9);
//获取第一个位置和最后一个位子的数据
System.out.println(list.getFirst());
System.out.println(list.getLast());
//删除第一个节点 和最后一个节点
list.removeFirst();
list.removeLast();
//pop() 出栈 弹出第一个节点(会从列表中删除)
System.out.println("弹出元素:" + list.pop());
//push() 入栈(压栈) 在第一的位子插入一个数
list.push(3);
3.2、Set接口的实现类及其常用方法
Set实现类的特点:
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null
Set set = new HashSet();
set.add("三国演义");
set.add("水浒传");
set.add("红楼梦");
set.add("西游记");
set.add("红楼梦");//重复
set.add("三国演义");//重复
set.add(null);
set.add(null);//再次添加 null
//set=[null, 水浒传, 三国演义, 红楼梦, 西游记]
System.out.println("set=" + set);
Set接口实现类TreeSet和HashSet
和 List 接口一样, Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样.
只有部分方法在Set中没有,如:Set没有下标,所以没有get()方法。
- Set实现类的方法演示
//创建HashSet,默认长度为16 加载因子为0.75
Set<String> set = new HashSet<>();
//往set添加元素
set.add("Java");
set.add("Html");
set.add("SQL");
set.add("Java");
System.out.println(set.size());
//contains() 判断是否包含某个对象
// 自己写的类,需要去重写hashcode和equals
set.contains("java");
//remove()删除集合的内容
set.remove("Java");
//clear() 清空set集合
set.clear();
//读取set的元素 只能遍历
for (String str: set) {
System.out.println(str);
}
}
- TreeSet的部分方法
TreeSet<Integer> set = new TreeSet<>();
set.add(20);
set.add(25);
set.add(8);
set.add(10);
set.add(16);
set.add(18);
//first() 返回第一个元素(排序之后)
set.first();
//last() 返回最后一个元素 排序之后的
set.last();
//ceiling() 返回比指定元素大的最小元素
System.out.println(set.ceiling(19));//20
//floor() 返回比指定元素小的最大元素
System.out.println(set.floor(9));//8
//size()返回元素个数
System.out.println("元素的个数:" + set.size());
//remove() 删除元素,根据类容来删
set.remove(18);
//clear() 清空所有元素
set.clear();
//isEmpty() 判断集合是否为空
set.isEmpty();
//遍历是按照元素排序显示
for (Integer i : set) {
System.out.println(i);
}
TreeSet和HashSet的区别
TreeSet采用红黑树数据结构实现的。
不能添加null ,元素有序(添加的元素会自动排序),遍历出来是按照从小到大的顺序排列的。不能添加重复元素
TreeSet的元素必须要实现Comparable 可以比较两个对象的大小
HashSet使用的是哈希表来存储数据。
- TreeSet实现Comparable来比较大小的步骤
- 实现Comparable接口,重写方法 compare方法
- 方法对象的参数大于参数对象返回 1 ,如果要降序,就返回-1
- 当前对象与参数对象小,就返回 -1,降序返回 1
- 当前对象与参数对象相等,返回0。
4、List和Set的遍历方式
- List的三种遍历方式[ArrayList ,LinkedList] 和 Set[TreeSet,HashSet]的两种遍历方式。
- 迭代器遍历(iterator) ---- 可用于List和Set
//list左迭代遍历
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(15);
list.add(66);
list.add(88);
//返回迭代器,迭代器里面存有数据
Iterator<Integer> iterator = list.iterator();
//hashNext()方法判断里面是否还有数据
//快捷生成下面遍历 itit
while (iterator.hasNext()){
//next()从迭代器中取出一个数据,去一个就减少一个
Integer next = iterator.next();
System.out.println(next);
- 增强for遍历(foreach) -----可用于List和Set
List list = new ArrayList();
list.add(0);
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//Object 集合内存储的类型 ,o 代表遍历出来的对象
// list是取出数据的位置,循环一次就取出一个数据放入o内
for (Object o : list) {
System.out.println(o);
}
- 普通for循环 —可用于List 因为Set接口没有get方法,不能使用普通for遍历
List list = new ArrayList();
list.add(0);
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
5、双列集合Map
1.Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
2.Map中的key和value 可以是任何引用类型的数据,
3.Map中的key不允许重复 Map中的value可以重复
4.Map的key可以为null, value也可以为null,注意key为null,只能有一个,
value为null ,可以多个。常用String类作为Map的key
5.key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
语法: 以HashMap为例
//<String,String> 两个String表示键和值的类型,可以是其他数据类型
Map<String,String> map = new HashMap<>();
Map的实现类主要有两个 HashMap 和 TreeMap,使用方法基本一致,以HashMap举例
Map map = new HashMap();
map.put("no2", "张无忌");//k-v
map.put("no1", "张三丰");//当有相同的 k , 就等价于替换. map.put("no3", "张三丰");//k-v
map.put(null, null); //k-v
map.put(null, "abc"); //等价替换
map.put("no4", null); //k-v
map.put("no5", null); //k-v
map.put(1, "赵敏");//k-v
map.put(new Object(), "金毛狮王");//k-v
// 通过 get 方法,传入 key ,会返回对应的 value
System.out.println(map.get("no2"));//张无忌
System.out.println("map=" + map)
- Map接口的方法
Map map = new HashMap();
//put() 往Map中添加数据,第一个参数为键 第二个参数为值
map.put("中国移动","10086");
map.put("中国联通", "10010");
map.put("中国电信", "10001");
map.put("中国移动", "100866");//替换
map.put("红楼梦", "曹雪芹");
map.put("西游记", "吴承恩");
map.put("三国演义", "罗贯中");
//map={中国移动=100866, 中国电信=10001, 红楼梦=曹雪芹, 三国演义=罗贯中, 西游记=吴承恩, 中国联通=10010}
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove("三国演义");
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("中国移动");
System.out.println("val=" + val);//val = 100866
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("中国移动"));//T
Map的遍历方式
Map map = new HashMap();
map.put("中国移动","10086");
map.put("中国联通", "10010");
map.put("中国电信", "10001");
map.put("中国移动", "100866");//替换
map.put("红楼梦", "曹雪芹");
map.put("西游记", "吴承恩");
map.put("三国演义", "罗贯中");
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet()
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
//获取迭代器
Iterator iterator = keyset.iterator();
//使用迭代器方法进行遍历,当数据取完时结束
while (iterator.hasNext()) {
//获取下一个数据
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
开发中如何选择使用那种集合
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行
选择,分析如下:
1先判断存储的类型(- 组对象[单列]或一-组键值对[双列])
2一组对象[单列]: Collection接口
允许重复: List
增删多: LinkedList [底层维护了一个双向链表]
改查多: ArrayList [底层维护Object类型的可变数组]
不允许重复: Set
无序HashSet [底层是HashMap ,维护了一个哈希表即(数组+链表+红黑树)]
排序: TreeSet
插入和取出顺序一致: LinkedHashSet ,维护数组+双向链表
3一组键值对[双列]: Map
键无序HashMap [底层是:哈希表jdk7: 数组+链表,jdk8: 数组+链表+红黑树]
键排序: TreeMap
键插入和取出顺序一致: LinkedHashMap
读取文件Properties
有部分集合未在本文中提及,但用法差不多一致
6、Collections 工具类
1.Collections是一个操作Set、 List和Map等集合的工具类
2.Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
Collections的常用方法
- 排序方法
-
reverse(List): 反转List中元素的顺序
-
shuffle(List):对List集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定List集合元素按升序排序
-
sort(List, Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
-
swap(List, int, int): 将指定list 集合中的i处元素和j处元素进行交换
- 查找、替换方法
-
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
-
Object max(Collection, Comparator): 根据Comparator指定的顺序
-
返回给定集合中的最大元素Object min(Collection)
Object min(Collection, Comparator) --最小元素
-
int frequency(Collection, Object): 返回指定集合中指定元素的出现次数
-
void copy(List dest,List src):将src中的内容复制到dest中
-
boolean replaceAll(List list, object oldVal, Object newVal):使用新值
替换List对象的所有旧值
以上方法均为静态方法,直接使用Collections.方法名() 即可调用
7、泛型集合
在集合的定义时,指定集合内存放的数据格式,可以是自己写的类,如果编译器发现添加的类型,不满足要求,就会报错。而在遍历时,可以直接取出该类型(不添加泛型时,取出的是Object)
-
使用泛型的好处:
1.编译时,检查添加的元素类型,提高了安全性
2.减少了类型的转换次数,提高效率
3.不在提示编译警告
语法:
//此处指定了String,在存放时就必须存放字符串,否则会报错误
List<String> strList = new ArrayList<> ();