Day 6
集合
1. 集合结构
2. 概念
-
简称集,用来存储多个元素的容器,每一种集合底层都封装了对应的数据结构
-
数据结构:数组,链表,队列,哈希表,二叉树
-
集合和数组区别
- 元素类型
- 集合:引用类型(存储基本类型时自动装箱)
- 数组:基本类型,引用类型
- 元素个数
- 集合:不固定,可任意扩容
- 数组:固定,不能改变容量
- 元素类型
-
不受容器大小限制,可以随时添加、删除元素
-
提供了大量操作元素的方法(判断、获取等)
-
Java的集合体系
-
单列集合(Collection)
List:ArrayList
Set: HashSet
-
双列集合(Map)
Map:HashMap
-
public class Test6 {
public static void main(String[] args) {
Collection c=new ArrayList();
//往集合中添加元素
c.add(100); //100原来是int类型,但是这里的参数对象是Object类,则进行了自动装箱,使int变为引用数据类型Integer
c.add(3.14);
c.add(100.5f);
c.add(true);
c.add(100l);
c.add(new Object());
//获取当前集合的长度
int size = c.size();
System.out.println("当前集合的长度为 "+size);
//遍历集合中的元素
Iterator iterator = c.iterator();//获取迭代器对象
/*
iterator.hasNext() 判断容器中是否存在下一个元素,如果存在返回true,同时对应的指针往下前进一步,不存在返回false
*/
while (iterator.hasNext()){
//获取元素中的数据
Object object = iterator.next();
System.out.println(object);
}
//清空集合中的元素
c.clear();
System.out.println("--------清空集合后--------");
System.out.println("当前集合的长度为 "+c.size());
}
}
/**
当前集合的长度为 6
100
3.14
100.5
true
100
java.lang.Object@4554617c
--------清空集合后--------
当前集合的长度为 0
*/
2.1 案例1
- **需求:**创建一个集合对象
—集合中存入猫、狗、兔子、鸟的实例,遍历集合
—控制台打印
—猫吃鱼
—狗吃骨头
—兔子吃胡萝卜
—鸟吃虫子
/**
Animal父类
定义成员方法eat()
*/
public class Animal {
public void eat(){};
}
/**
Cat子类
定义成员方法
重写父类eat()
*/
public class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
}
/**
Test测试类
*/
public class Test {
public static void main(String[] args) {
Animal c=new Cat();
Animal d=new Dog();
Animal r=new Rabbit();
Animal b=new Bird();
Collection co=new ArrayList(); //创建集合实例对象
co.add(c);
co.add(d);
co.add(r);
co.add(b);
Iterator it=co.iterator(); //创建迭代器,方便遍历
while(it.hasNext()){
Object o=it.next(); //元素是子类,要在下面进行转换成父类
/*
判断o是否是Animal类型
*/
if(o instanceof Animal){
Animal a=(Animal)o; //将集合中的子类元素强转成父类
a.eat(); //接着父类的引用指向子类对象(多态)
}
}
}
}
/**
猫吃鱼
狗吃骨头
兔吃萝卜
鸟吃虫
*/
3. List集合
- 特点:可重复,有序(存取数据顺序相同)
List l=new ArrayList();
- 是线程不安全的
List l=new LinkedList()
3.1 案例
-
分析
- 向集合中添加元素的方法为:
add()
- 遍历集合的方式:
for 循环
- 获取集合中元素个数的方法:
size()
- 向集合中添加元素的方法为:
-
步骤
- 创建集合对象
- 分别创建三个Student对象
- 使用
add()
方法将Student对象添加到集合中 - 使用
for()
循环遍历集合并打印
public class Student {
private String name;
private int age;
public Student(){};
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String toString(){
return "Student{ name:"+name+", age:"+age+" }";
}
}
public class Test {
public static void main(String[] args) {
List l1=new ArrayList();
Student s1=new Student("张三",18);
Student s2=new Student("李四",20);
Student s3=new Student("王五",21);
System.out.println("--------是否添加成功--------");
boolean b1=l1.add(s1);
System.out.println(b1);
l1.add(s2);
l1.add(s3);
System.out.println("--------打印集合--------");
System.out.println(l1);
System.out.println("--------返回索引为1的元素--------");
System.out.println(l1.get(1));
System.out.println("--------返回集合长度--------");
System.out.println(l1.size());
System.out.println("--------遍历集合并输出--------");
for(int i=0;i<l1.size();i++){
System.out.println(l1.get(i));
}
System.out.println("--------增强for循环,简化遍历输出--------");
for(Object o:l1){
System.out.println(o);
}
}
}
/**
--------是否添加成功--------
true
--------打印集合--------
[Student{name='张三', age=18}, Student{name='李四', age=20}, Student{name='王五', age=21}]
--------返回索引为1的元素--------
Student{name='李四', age=20}
--------返回集合长度--------
3
--------遍历集合并输出--------
Student{name='张三', age=18}
Student{name='李四', age=20}
Student{name='王五', age=21}
--------增强for循环,简化遍历输出--------
Student{name='张三', age=18}
Student{name='李四', age=20}
Student{name='王五', age=21}
*/
4. 迭代器
-
对过程的重复,称为迭代
-
==迭代器是遍历Collection集合的通用方法。==可以在对集合遍历的同时进行添加,删除操作
-
常用方法
- next():返回迭代的下一个元素对象
- hasNext():如果仍有元素可以迭代,则返回true
-
列表迭代器是List体系独有的遍历方式,可以在对集合遍历的同时进行添加,删除等操作。
- 但是必须通过调用列表迭代器来实现
public class Test1 {
public static void main(String[] args) {
List l1=new ArrayList();
l1.add("a");
l1.add("b");
l1.add("c");
Iterator it=l1.iterator();
while(it.hasNext()){ //如果迭代器有元素,就一直迭代
String s1=(String) it.next();
System.out.println(s1); //迭代遍历,并转换成字符串输出
}
/**
* 需求:判断集合中如果有字符“b”,就在其后添加一个新字符:java
*/
ListIterator lit=l1.listIterator(); //创建列表迭代器
while(lit.hasNext()){ //是否存在下一个元素,存在返回true
String s2=(String) lit.next();
if("b".equals(s2)){
lit.add("java"); //只有列表迭代器可以添加值
}
System.out.println(s2);
}
System.out.println(l1); //添加元素后输出列表
}
}
/**
a
b
c
a
b
c
[a, b, java, c]
*/
5. 泛型
- 即泛指任意类型,又叫参数化类型 (ParameterizedType),对具体类型的使用起到辅助作用,类似于方法的参数
- 集合类泛型表示该集合中存放指定类型的元素
- 格式:
List<String> list2 = new ArrayList<>();
public class Test2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add(10); //可编译,但会运行报错,类型转换异常 ClassCastException
for (Object o : list) {
String s = (String)o;
System.out.println(s);
}
List<String> list2 = new ArrayList<>(); //使用泛型限制集合的元素类型为String,JDK7以后的版本,new的泛型可以写成<>
list2.add("a");
list2.add("b");
list2.add("c");
//list2.add(10); //泛型后,list2只能添加String类型,此句会编译报错
}
}
5.1 案例2
-
**需求:**创建一个arrayList集合(要使用泛型),里面存放10件商品实例
—过滤出价格大于100元的商品
—打印出所有的商品名称
/**
定义Good类
其中有名称,价格属性
以及成员方法print(),用来打印所有商品名称
*/
public class Good {
private String name;
private double price;
public Good(){};
public Good(String name, double price) {
this.name = name;
this.price = price;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(double price) {
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Good{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
public void print(){
System.out.println(this.name);
}
}
/**
测试类
*/
public class Test7 {
public static void main(String[] args) {
Good g1 = new Good("牛奶", 49.99);
Good g2 = new Good("饼干", 26.00);
Good g3 = new Good("奶粉", 108.88);
Good g4 = new Good("婴儿车", 500.00);
Good g5 = new Good("电视", 2000.00);
Good g6 = new Good("电脑", 6000.00);
Good g7 = new Good("鼠标", 30.8);
Good g8 = new Good("风扇", 299.99);
Good g9 = new Good("空调", 3500.00);
Good g0 = new Good("键盘", 60.00);
List<Good> l1 = new ArrayList<>(); //泛型,将list元素限制为Good类型
l1.add(g0);
l1.add(g1);
l1.add(g2);
l1.add(g3);
l1.add(g4);
l1.add(g5);
l1.add(g6);
l1.add(g7);
l1.add(g8);
l1.add(g9);
System.out.println("--------商品列表--------"); //JDK8中新增的foreach遍历方法
for (Object o : l1) {
Good g = (Good) o;
g.print();
}
System.out.println("--------超过100的商品--------");
for (Object o : l1) {
Good g = (Good) o;
if (g.getPrice() > 100) { //过滤价值超过100的商品
System.out.println(g.toString());
}
}
}
}
/**
--------商品列表--------
键盘
牛奶
饼干
奶粉
婴儿车
电视
电脑
鼠标
风扇
空调
--------超过100的商品--------
Good{name='奶粉', price=108.88}
Good{name='婴儿车', price=500.0}
Good{name='电视', price=2000.0}
Good{name='电脑', price=6000.0}
Good{name='风扇', price=299.99}
Good{name='空调', price=3500.0}
*/
6. Collections工具类
- 针对集合进行操作的工具类
- 成员方法
sort(List)
根据元素的自然顺序,将指定列表按升序排序max(Collection)
返回集合的最大元素reverse(List)
反转List集合元素shuffle(List)
使用默认的随机源随机置换指定的列表
public class Test3 {
public static void main(String[] args) {
List<Integer> l1=new ArrayList<>();
l1.add(1);
l1.add(7);
l1.add(4);
l1.add(6);
l1.add(2);
l1.add(4);
l1.add(5);
l1.add(9);
l1.add(0);
System.out.println(l1);
System.out.println("集合中最大元素为:"+ Collections.max(l1));
Collections.sort(l1);
System.out.println("升序后的集合为:"+l1);
Collections.reverse(l1);
System.out.println("反转后的集合为:"+l1);
Collections.shuffle((l1));
System.out.println("打乱之后的集合为:"+l1);
}
}
/**
[1, 7, 4, 6, 2, 4, 5, 9, 0]
集合中最大元素为:9
升序后的集合为:[0, 1, 2, 4, 4, 5, 6, 7, 9]
反转后的集合为:[9, 7, 6, 5, 4, 4, 2, 1, 0]
打乱之后的集合为:[1, 6, 4, 0, 5, 2, 4, 7, 9]
*/
7. Set集合
- 不可重复,无序
public class Test4 {
public static void main(String[] args) {
Set<Student> set=new HashSet<>();
Student s1=new Student("张三",18);
Student s2=new Student("李四",19);
Student s3=new Student("王五",20);
Student s4=new Student("赵六",21);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4); //多次添加会自动依据equals()和hashCode()两种方法去重
Iterator<Student> it=set.iterator(); //使用迭代器遍历并输出
while(it.hasNext()){
Student s=it.next();
System.out.println(s);
}
System.out.println("--------打印集合--------");
System.out.println(set);
System.out.println("--------遍历集合并输出--------");
for(Student student:set){
System.out.println(student); //打印顺序与添加顺序不同
}
}
}
/**
Student{name='李四', age=19}
Student{name='赵六', age=21}
Student{name='张三', age=18}
Student{name='王五', age=20}
--------打印集合--------
[Student{name='李四', age=19}, Student{name='赵六', age=21}, Student{name='张三', age=18}, Student{name='王五', age=20}]
--------遍历集合并输出--------
Student{name='李四', age=19}
Student{name='赵六', age=21}
Student{name='张三', age=18}
Student{name='王五', age=20}
*/
8. Map集合
-
双列集合,元素由键值对(
Entry
)构成 -
key不可以重复,value可重复
-
向map集合中添加㢝方法为:put()
-
遍历集合的方式
- 获取所有的key :
keySet()
- 遍历 keySet ,通过 key
- 根据key获取 value :
get()
- 获取所有的key :
-
遍历keySet的方法:
iterator()
-
map常用方法
- 将一个集合所有元素复制过来:
putAll()
- 判断是否有此key:
boolean b=map.containsKey();
- 判断是否有此value:
boolean b=map.containsValue();
- 清空map:
clear()
- 根据key删除映射关系:
remove(Object:Key);
- 将一个集合所有元素复制过来:
-
集合常识
- 集合的扩容机制
- 当初始容量满了,就会触发扩容机制
- 集合的初始容量
- 每个集合初始容量不同,JDK版本不同,集合容量不同
- 集合的加载因子
- 触发扩容机制后,具体怎么扩容,例如:扩容为原来的1.5倍,则加载因子为1.
- 集合的扩容机制
-
hashMap和hashTable的区别
- 前者线程不安全,后者线程安全(线程同步)
- 前者元素可为空,后者元素不允许为空(key和value都不能)
-
treeMap可根据key进行排序,因为底层封装的是二叉树
- 注意:如果key元素类型为自定义类,则要在自定义类中实现比较接口Comparable,重写比较的方法
-
treeSet也可根据元素进行排序,但是如果泛型是自定义类,则也要重写比较方法
-
properties
-
public class Test5 {
public static void main(String[] args) {
Map<Integer,Student> map=new HashMap<>(); //创建一个map泛型——指定key为Integer类型,value为Student类型
Student s1=new Student("张三",18);
Student s2=new Student("李四",19);
Student s3=new Student("王五",20);
System.out.println(map.put(1,s2)); //利用键覆盖数值时会返回被覆盖的值
System.out.println(map.put(1,s1));
map.put(2,s1);
map.put(3,s2);
System.out.println("--------打印map集合--------");
System.out.println(map);
System.out.println("--------将map的key装成一个集合输出--------");
Set<Integer> keys=map.keySet(); //获取map中的key,组成一个set集合
System.out.println(keys);
System.out.println("--------遍历key组成的迭代器并输出--------");
Iterator<Integer> it=keys.iterator(); //将set集合变为一个迭代器
while(it.hasNext()){ //遍历key组成的迭代器
Integer key=it.next();
System.out.println("key:"+key+"\tvalue:"+map.get(key)); //打印结果
}
System.out.println("--------遍历所有key并输出--------"); //key类型为Integer
for(Integer integer:keys){ //使用for遍历所有key
//System.out.println(map.get(integer)); //打印索引为keys的map元素
System.out.println(integer);
}
System.out.println("--------遍历所有value并输出--------"); //value类型为Student
for(Student value:map.values()){
System.out.println(value); //使用for遍历所有value
}
}
}
/**
null
Student{name='李四', age=19}
--------打印map集合--------
{1=Student{name='张三', age=18}, 2=Student{name='张三', age=18}, 3=Student{name='李四', age=19}}
--------将map的key装成一个集合输出--------
[1, 2, 3]
--------遍历key组成的迭代器并输出--------
key:1 value:Student{name='张三', age=18}
key:2 value:Student{name='张三', age=18}
key:3 value:Student{name='李四', age=19}
--------遍历所有key并输出--------
1
2
3
--------遍历所有value并输出--------
Student{name='张三', age=18}
Student{name='张三', age=18}
Student{name='李四', age=19}
*/
案例1:hashMap——统计字符出现次数
/*
法一
*/
public class HashChacter {
public static void main(String[] args) {
//键盘录入字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.next();
//HashMap泛型,Key为Character(字符),value为Interger(次数)
HashMap<Character, Integer> hashMap = new HashMap<>();
for (int i = 0; i < line.length(); i++) {
char key = line.charAt(i); //返回指定位置的字符
//此时hashmap为空
//获取每一个字符在HashMap集合中对应的值,看其返回值
Integer value = hashMap.get(key);
//如果返回值为空,说明集合内没有该值,把该值存入进去,计次数为1
if (value == null) {
hashMap.put(key, 1);
} else {
//如果返回值不为空,说明字符已存在,计次数加1,并重新存入map
value++;
hashMap.put(key, value);
}
}
//存放结果的字符序列
StringBuilder result = new StringBuilder();
Set<Character> ks = hashMap.keySet();
for (Character key : ks) {
Integer value = hashMap.get(key);
result.append(key).append("(").append(value).append(") ");
}
System.out.println(result);
}
}
/*
请输入一个字符串:
abbcihcieiceicieciecietrbhtyjntymoj
a(1) b(3) c(6) e(5) h(2) i(7) j(2) m(1) n(1) o(1) r(1) t(3) y(2)
*/
/*
法二
*/
public class HashCharacter {
public static void main(String[] args) {
String str="abbcihcieiceicieciecietrbhtyjntymoj";
//将字符串转换成字符数组
char[] c=str.toCharArray();
//创建hashmap
HashMap<Character,Integer> map=new HashMap<>();
//遍历数组c,并装入map
for(char ch:c){
if(!map.containsKey(ch)){
map.put(ch,1);
}else{
map.put(ch,map.get(ch)+1);
}
}
System.out.println("=======统计次数======");
Set<Map.Entry<Character,Integer>> result=map.entrySet();
for (Map.Entry<Character,Integer> entry:result) {
System.out.print(entry.getKey()+"("+entry.getValue()+") ");
}
}
}
/*
=======统计次数======
a(1) b(3) c(6) e(5) h(2) i(7) j(2) m(1) n(1) o(1) r(1) t(3) y(2)
*/
案例2——统计字符、字母,单词个数(正则匹配)
- 需求
- 1、自定义一个结构,利用 hashMap 统计字符出现的次数。
- 2、至少使用两种遍历方式打印每个字母出现的次数。
- 3、统计每个单词出现的次数。并过滤超过3次的单词。
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Test1 {
public static void main(String[] args) {
//1、自定义一个结构,利用hashMap 统计字符出现的次数。
getHashMapByStr();
//2、至少使用两种遍历方式打印每个字母出现的次数。
getForEach();
//3、统计每个单词出现的次数。并过滤超过3次的单词。
getWordNum();
}
/**
* 3、统计每个单词出现的次数。并且过滤出现次数超过3次的单词
*/
public static void getWordNum(){
System.out.println("--------------------统计单词出现次数-----------------------");
HashMap<String, Integer> map = new HashMap<>();
String[] splits = getString().split("\\W+");
for (String str:splits){
//把所有大写字母转成小写字母
String str2 = str.toLowerCase();
if (map.containsKey(str2)){
map.put(str2,map.get(str2)+1);
}else {
map.put(str2,1);
}
}
System.out.println(map);
//遍历map集合
// Set<Map.Entry<String, Integer>> entries = map.entrySet();
//
// for (Map.Entry<String, Integer> entry:entries){
//
// System.out.println(entry.getKey()+" 出现次数:"+entry.getValue());
// }
System.out.println("-------------打印出现次数超过3次的单词(大小写不敏感)-------------------------");
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry:entries){
if (entry.getValue()>3){
System.out.println(entry.getKey()+" 出现次数:"+entry.getValue());
}
}
}
/**
* 2、至少使用两种遍历方式打印每个字母出现的次数。
*/
public static void getForEach(){
HashMap<String, Integer> map = new HashMap<>();
String[] splits = getString().split("");
for (String str: splits){
if (map.containsKey(str) && str.matches("\\w")){
map.put(str,map.get(str)+1);
}else if (str.matches("\\w")){
map.put(str,1);
}
}
System.out.println("------------------至少使用两种遍历方式打印字母出现次数---------------------");
Set<String> strings = map.keySet();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()){
String next = iterator.next();
Integer integer = map.get(next);
System.out.println("字母 "+next+" 出现次数:"+integer);
}
}
/**
* 1、自定义一个结构,利用hashMap 统计字符出现的次数。
*/
public static void getHashMapByStr(){
System.out.println("-------------------------统计字符出现次数-----------------------------");
HashMap<String, Integer> map = new HashMap<>();
String[] splits = getString().split("");
for (String s:splits){
//把空格或者换行符筛选出去 !s.matches(" +|\n")
if (map.containsKey(s) && !s.matches(" +|\n")){
map.put(s,map.get(s)+1);//如果有这个字符的存在 在当前基础上加一
}else if (!s.matches(" +|\n")){
map.put(s,1);
}
}
System.out.println(map);
// //将map集合转成set集合
// Set<Map.Entry<String, Integer>> entries = map.entrySet();
// //遍历set集合
// Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
// while (iterator.hasNext()){
// Map.Entry<String, Integer> next = iterator.next();
// System.out.println(next.getKey() + "的个数是" +next.getValue());
// }
}
public static String getString(){
String getSring = "Tree bins (i.e., bins whose elements are all TreeNodes) are\n" +
" * ordered primarily by hashCode, but in the case of ties, if two\n" +
" * elements are of the same \"class C implements Comparable<C>\",\n" +
" * type then their compareTo method is used for ordering. (We\n" +
" * conservatively check generic types via reflection to validate\n" +
" * this -- see method comparableClassFor). The added complexity\n" +
" * of tree bins is worthwhile in providing worst-case O(log n)\n" +
" * operations when keys either have distinct hashes or are\n" +
" * orderable, Thus, performance degrades gracefully under\n" +
" * accidental or malicious usages in which hashCode() methods\n" +
" * return values that are poorly distributed, as well as those in\n" +
" * which many keys share a hashCode, so long as they are also\n" +
" * Comparable. (If neither of these apply, we may waste about a\n" +
" * factor of two in time and space compared to taking no\n" +
" * precautions. But the only known cases stem from poor user\n" +
" * programming practices that are already so slow that this makes\n" +
" * little difference.)";
return getSring;
}
}