day07-Properties+TreeMap+泛型+Collections+集合小结
学习任务:
1 掌握Properties读取属性文件
2 记住TreeMap特点
3 了解Map键值数据类型的问题
4 集合小结
5 了解自定义泛型
文章目录
- day07-Properties+TreeMap+泛型+Collections+集合小结
- Properties
- TreeMap
- Map键值数据类型问题
- 集合小结
- 集合是用于存储引用数据的容器,只能存储引用数据, 如果保存整数,小数,字符,布尔数据时,需要保存相应的包装类对象集合分为Collection集合与Map集合
- Collection集合单个数据的存储,也称为单列集合, Collection集合的基本操作Collection collection = newArrayList<();collection.add("hello");collection.contains("hello");collection.size();collection.remove("world");for( Iteratorit = collection.iterator() ; it.hasNext(); ){ String s = it.next();}foreach遍历collection集合中的元素collection.forEach(Consumer)collection.removeIf(Predicate)
- Map集合是按对的形式存储 ,也称为双列集合, Map的基本操作Mapmap=newHashMap<();map.put("lisi",80);map.get("lisi")map.containsKey("lisi")map.containsValue(80)map.remove("chenqi")map.replace("lisi",90)map.keySet()map.values()map.entrySet()map.forEach(BiConsumer)
- 虽然Vector, HashTable是线程安全的, 但在多线程程序中,很少使用它们建议使用java.util.concurrent包(juc包)中线程安全的集合类CopyOnWriteArrayList , 线程安全的List集合CopyOnWriteArraySet, 线程安全的Set集合ConcurrentSkipListSet, 线程安全的可以排序的Set集合ConcurrentHashMap, 线程安全的Map集合ConcurrentSkipListMap, 线程安全的可以根据键排序的Map集合
- 泛型
- Collections类
- 练习
Properties
- Properties继承了HashTable, 也是一种线程安全的Map集合
- 键值都是固定的String字符串, 通常用于设置/读取程序的属性
- 在实际开发中, 通常把程序的属性保存到属性文件中,可以使用Properties或者ResourceBundle读取属性文件中的属性值
Properties的基本使用
public class Test01Properties {
public static void main(String[] args) {
//创建Properties对象
Properties properties = new Properties();
//设置属性
properties.setProperty("username", "feifei");
properties.setProperty("password", "123");
properties.put("country", "CN");
//读取 属性
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
System.out.println(properties.getProperty("country"));
System.out.println(properties.get("username"));
//读取系统属性
System.out.println(System.getProperty("user.dir")); //用户目录
//返回所有的系统属性
Properties allProperties = System.getProperties();
allProperties.forEach((k, v) -> System.out.println(k + " : " + v));
}
}
resources资源包添加属性文件+Properties读取属性值
- 在实际开发中,通常会把属性保存到属性文件中
会在src源文件夹下创建一个独立的resources资源包, 在这个包中添加属性文件
右键resources包, 新建File, 输入名称: config.properties
右键resourced包, 新建Resource Bundle, 在弹出的对话框中输入文件名config, 默认文件扩展名就是.properties
- 在属性文件中,以键值对的形式保存属性: 属性名=属性值
一般情况下,属性名与属性值都 是英文,如果需要使用中文,则需要设置属性文件编码与当前环境编码一致
-
3 可以使用Properties或者ResourceBundle读取属性文件中的属性
username=feifei password=456 国籍=中国
public class Test02Properties {
public static void main(String[] args) throws IOException {
//1 创建Properties对象
Properties properties = new Properties();
//2 通过当前类的字节码的类加载器与属性文件之间建立输入流通道
InputStream in = Test02Properties.class.getClassLoader().getResourceAsStream("resources/config.properties");
//3 调用Properties的load(InputStream)加载属性文件 , 该方法有IOException异常需要预处理,当前选择抛出处理,Alt + Enter, 选择 Add exception to method signature. 运行程序后,如果这一行出现了异常,说明第2)步中属性文件路径不正确
properties.load(in);
//4)读取属性
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
System.out.println(properties.getProperty("国籍"));
System.out.println(properties.get("username"));
System.out.println(properties.getProperty("country")); //null, 属性名不正确
//5)关闭流
in.close();
}
}
resources资源包添加属性文件+使用ResourceBundle读取属性文件
public class Test03ResourceBundle {
public static void main(String[] args) {
//创建ResourceBundle对象, ResourceBundle是抽象类, 调用它的静态方法getBundle( 属性文件基本路径) 创建对象. 注意属性文件基本路径不需要扩展名
ResourceBundle resourceBundle = ResourceBundle.getBundle("resources/config");//如果这一行出现异常,说明属性文件路径不正确
//读取 属性
System.out.println( resourceBundle.getString("username"));
System.out.println( resourceBundle.getString("password"));
System.out.println( resourceBundle.getString("国籍"));
//System.out.println( resourceBundle.getString("country")); //属性名不正确会抛出异常
}
}
TreeMap
TreeMap实现了SortedMap接口,可以根据键排序, 键排序原理是红黑树原理红黑树采用中序遍历可以实现由小到大排序
- TreeMap可以根据键排序
public class Test04TreeMap {
public static void main(String[] args) {
//定义TreeMap集合保存<学生姓名,成绩> , 通过构造方法的Comparator比较器指定根据姓名降序排序
/* TreeMap<String,Integer> treeMap = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});*/
//TreeMap<String, Integer> treeMap = new TreeMap<>((s1, s2) -> s2.compareTo(s1));
//Comparator接口提供静态方法naturalOrder()自然排序, reverseOrder()逆序排序
TreeMap<String, Integer> treeMap = new TreeMap<>(Comparator.reverseOrder());
treeMap.put("hanyi", 80);
treeMap.put("liuer", 90);
treeMap.put("zhangsa", 80);
treeMap.put("chenqi", 60);
treeMap.put("zhuba", 40);
treeMap.put("sunjiu", 70);
treeMap.forEach((k, v) -> System.out.println(k + " : " + v));
//如果调用TreeMap()无参构造方法, 没有在构造方法中指定Comparator比较器,要求键本身具有比较大小 的能力
TreeMap<String, Integer> treeMap2 = new TreeMap<>();
treeMap2.putAll(treeMap); //把参数treeMap集合中的键值对都添加到当前treeMap2集合中
System.out.println("=======================================");
treeMap2.forEach((k, v) -> System.out.println(k + " : " + v));
///注意:TreeMap只能根据键排序, 不能根据值排序
//在TreeMap中增加了一组针对第一个Entry(键) 和最后一个Entry(键)的操作
Map.Entry<String, Integer> firstEntry = treeMap.firstEntry(); //返回第一个Entry
System.out.println(firstEntry);
String firstKey = treeMap.firstKey(); //返回第一个Key
System.out.println(firstKey);
treeMap.pollLastEntry(); //删除最后一个
System.out.println(treeMap);
treeMap.pollFirstEntry(); //删除第一个
System.out.println(treeMap);
}
}
Map键值数据类型问题
Map的键一般使用String字符串或者Integer整数, 很少使用自定义类型HashMap集合中键值对的存储位置是由键的哈希码计算出来的, 如果修改了键对象的属性值,导致键的哈希码发生变化,在HashMap中可能找不到这个键了
- HashMap集合中的键是自定义类型对象
存在的问题:
在put添加键值对时, 根据键对象的哈希码计算出来的数组下标i, 把键值对保存到table[i]链表中
修改了键对象的属性值,导致键对象的哈希码变了
不管是containsKey(k), 还是put()是根据car对象现在的哈希码计算数组的下标 x, 现在计算出来的x与之前put计算出来的下标i不相等, 现在来到 table[x]链表中查看不存在equals相等的键
public class Test05KeyCar {
public static void main(String[] args) {
//定义HashMap保存<Car小汽车, 车主姓名>
HashMap<Car, String> hashMap = new HashMap<>();
hashMap.put(new Car("G63", 200), "nie");
hashMap.put(new Car("BMW", 150), "xie");
hashMap.put(new Car("Benz", 100), "long");
hashMap.put(new Car("GTR", 20), "yu");
hashMap.put(new Car("BYD", 30), "li");
Car car = new Car("TSL", 28);
hashMap.put(car, "lisi");
hashMap.forEach((k, v) -> System.out.println(k + " : " + v));
System.out.println(hashMap.containsKey(car)); //true
car.price = 26;
System.out.println(hashMap.containsKey(car)); //false
System.out.println("-------------------------------------");
hashMap.put(car, "zhangsan");
hashMap.forEach((k, v) -> System.out.println(k + " : " + v));
}
//以静态内部类形式定义Car小汽车类, 重写equals/hashcode方法
static class Car {
String brand;
int price;
public Car(String brand, int price) {
this.brand = brand;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return price == car.price &&
Objects.equals(brand, car.brand);
}
@Override
public int hashCode() {
return Objects.hash(brand, price);
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
}
TreeMap集合中键值对的存储位置是由键的大小决定的,如果修改了键对象的排序字段值,也可能导致在TreeMap中找不到这键
- TreeMap集合中的键是自定义类型对象
存在的问题
在put添加键值对时, 根据比较的字段值添加到某个结点的左(右)子树上,
修改了键对象的排序字段值,再查找时,可能去这个结点的右(左)子树上查找, 找不到
public class Test06KeyCar {
public static void main(String[] args) {
//定义TreeMap保存<Car小汽车, 车主姓名>
TreeMap<Car, String> treeMap = new TreeMap<>(new Comparator<Car>() {
@Override
public int compare(Car o1, Car o2) {
return o2.price - o1.price;
}
});
treeMap.put(new Car("G63", 40), "nie");
treeMap.put(new Car("BMW", 50), "xie");
treeMap.put(new Car("Benz", 100), "long");
treeMap.put(new Car("GTR", 20), "yu");
treeMap.put(new Car("BYD", 30), "li");
Car car = new Car("TSL", 28);
treeMap.put(car, "lisi");
treeMap.forEach((k, v) -> System.out.println(k + " : " + v));
System.out.println(treeMap.containsKey(car)); //true
car.price = 38;
System.out.println(treeMap.containsKey(car)); //false
}
//以静态内部类形式定义Car小汽车类,
static class Car {
String brand;
int price;
public Car(String brand, int price) {
this.brand = brand;
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
}
Map的value值可以是各种引用类型, 还可以是数组或者其他集合
- Map的value值是List集合
public class Test07ValueList {
public static void main(String[] args) {
/*
姓名 爱好
zhangsan sing, dance
lisi sleep, game, girl
wangwu reading
zhaoliu football, climb, swimming, game
定义Map保存<姓名, 爱好>, 爱好可以有多个,所以爱好需要使用一个容器来保存, 当前选择List集合
*/
Map<String, List<String>> map = new HashMap<>();
//创建存储爱好的List集合
List<String> list = new ArrayList<>();
list.add("sing");
list.add("dance");
//把<姓名, 保存爱好的List集合>添加到Map集合中
map.put("zhangsan", list);
//创建存储爱好的List集合
list = new ArrayList<>();
list.add("sleep");
list.add("game");
list.add("girl");
//把<姓名, 保存爱好的List集合>添加到Map集合中
map.put("lisi", list);
//创建存储爱好的List集合
list = new ArrayList<>();
list.add("football");
list.add("climb");
list.add("swimming");
list.add("game");
//把<姓名, 保存爱好的List集合>添加到Map集合中
map.put("zhaoliu", list);
// map.forEach((k,v) -> System.out.println(k + "的爱好为:" + v));
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
System.out.print(entry.getKey() + "的爱好为:");
for (String s : entry.getValue()) { //entry的value值是List集合
System.out.print(s + " ");
}
System.out.println();
}
}
}
集合小结
-
集合是用于存储引用数据的容器,只能存储引用数据, 如果保存整数,小数,字符,布尔数据时,需要保存相应的包装类对象集合分为Collection集合与Map集合
-
Collection集合单个数据的存储,也称为单列集合, Collection集合的基本操作Collection collection = newArrayList<();collection.add(“hello”);collection.contains(“hello”);collection.size();collection.remove(“world”);for( Iteratorit = collection.iterator() ; it.hasNext(); ){ String s = it.next();}foreach遍历collection集合中的元素collection.forEach(Consumer)collection.removeIf(Predicate)
如果允许数据重复选择List集合List集合为每个元素指定了索引值, 增加一组针对索引值的操作:add( i , o )remove( i )set( i , o)get( i )indexOf( o )/ lastIndexOf( o )subList(from, to)List集合调用listIterator()方法返回ListIterator迭代器, 不仅可以从前向后迭代,还可以从后向前迭代,不仅可以删除,还可以添加与修改List集合在JDK8中新增了sort(Comparator)排序
List集合如果以访问为主选择使用ArrayList ArrayList底层是数组, 通过索引值可以计算出数组元素的地址,通过地址直接访问,效率高
如果频繁的插入/删除 选择LinkedList LinkedList底层是双向链表, 访问慢,插入/删除效率高LinkedList增加一组方法模拟队列: offer(), poll(), peek()模拟栈: push(), pop(), peek() 针对第一个/最后一个元素的操作: addFirst, removeLast
如果不允许数据重复选择Set集合
如果不需要排序就选择HashSet HashSet底层数据结构是 HashMap HashSet集合中元素的存储位置是由元素的哈希码计算出来的判断重复还需要调用equals()方法如果在HashSet集合中存储自定义类型对象, 需要重写Equals/hashCode
如果需要排序就使用TreeSet TreeSet底层数据结构是TreeMap可以对集合中的元素进行排序元素的存储位置跟元素的大小有关 判断元素是否重复只看元素的大小是否相等,即Comparator/comparable的比较结果是否为0
-
Map集合是按对的形式存储 ,也称为双列集合, Map的基本操作Mapmap=newHashMap<();map.put(“lisi”,80);map.get(“lisi”)map.containsKey(“lisi”)map.containsValue(80)map.remove(“chenqi”)map.replace(“lisi”,90)map.keySet()map.values()map.entrySet()map.forEach(BiConsumer)
HashMap底层是哈希表,结合了数组与链表的优点HashMap的工作原理
TreeMap可以根据键排序
Properties读取属性文件ResourceBundle读取属性文件
Map的键一般使用String字符串或者Integer整数,很少使用自定义类型 Map的value值可以是各种引用数据类型,包括数组或者List集合
-
虽然Vector, HashTable是线程安全的, 但在多线程程序中,很少使用它们建议使用java.util.concurrent包(juc包)中线程安全的集合类CopyOnWriteArrayList , 线程安全的List集合CopyOnWriteArraySet, 线程安全的Set集合ConcurrentSkipListSet, 线程安全的可以排序的Set集合ConcurrentHashMap, 线程安全的Map集合ConcurrentSkipListMap, 线程安全的可以根据键排序的Map集合
泛型
泛型就是把数据类型作为参数传递
- 当不管针对哪种类型的数据,都进行相同的操作时, 可以使用泛型接收操作数据的类型Comparable/Comaprator接口通过泛型指定比较对象的类型Collection集合与Map集合通过泛型指定存储元素的类型
- 泛型的好处是可以在编译阶段进行数据类型检查
- 可以自定义方法泛型与类泛型
在方法中,不管针对哪种类型的数据都进行相同的操作,可以使用泛型接收操作数据的类型
在方法返回值类型前面使用声明泛型参数
public class Test01Method {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
//调用方法向list集合中同时添加若干数据, 在调用泛型方法时, 不需要显示给泛型参数传递数据类型,系统可以根据实参自动推断
addAll(stringList, "hh", "aa", "bb", "ww", "mm");
System.out.println(stringList);
List<Integer> integerList = new ArrayList<>();
addAll(integerList, 3, 5, 7, 8, 921, 43, 45, 76, 78, 43, 56, 87);
System.out.println(integerList);
}
//定义方法, 向List集合中同时添加若干数据. 需要通过参数接收一个List集合, 通过参数接收若干数据,第二个参数若干数据通过变长参数, 变长参数的类型要与List集合存储元素的类型一致 . List可以存储各种引用 类型的数据, 那么变长参数的类型要也 不确定, 可以使用泛型
//在方法返回值类型前面使用<T>声明泛型参数, List集合就存储T类型的数据, 变长参数就是T类型
public static <T> void addAll(List<T> list, T... data) {
//把变长参数接收的数据添加到list集合中
for (T datum : data) {
list.add(datum);
}
}
}
在类名/接口名后面使用声明泛型参数, 在类体中可以使用T类型
public class Test02 {
public static void main(String[] args) {
//在使用泛型类时, 需要显示的给泛型参数传递数据类型
GenericClass<String> obj1 = new GenericClass<>();
obj1.setData("helo");
String s = obj1.getData();
GenericClass<Integer> obj2 = new GenericClass<>();
obj2.setData(456);
Integer ii = obj2.getData();
//如果没有给类的泛型参数传递数据类型,则系统默认为Object类型
GenericClass obj3 = new GenericClass();
Object data = obj3.getData();
}
}
//在类名/接口名后面使用<T>声明泛型参数
class GenericClass<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
Collections类
在java.util包中提供了Collections类, 该类有一组操作集合的方法addAll()sort(List)shuffle(List)synchronizedXXX(xx)
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//同时向集合中添加若干数据
Collections.addAll(list, "hello", "world", "boy", "girl", "good", "nice");
System.out.println(list);
//排序
Collections.sort(list); //要求集合中的元素本身具有比较大小的功能
System.out.println(list); //[boy, girl, good, hello, nice, world]
//乱序
Collections.shuffle(list);
System.out.println(list);
//逆序排序
/*Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});*/
Collections.sort(list, Comparator.reverseOrder());
System.out.println(list); //[world, nice, hello, good, girl, boy]
//把不是线程安全的集合转换为线程安全的
List<String> stringList = Collections.synchronizedList(list);
//stringList集合是线程安全的
}
}
练习
模拟斗地主游戏中的发牌
1 构建一副扑克牌, 以字符串的形式表示扑克牌: ♥3, ♦4, ♣5, ♠6, 小王, 大王 除了大小王外,其他扑克有四种花色, 每种花色有13张牌
2 洗牌
3 发牌, 有三个玩家: 地主,农民1, 农民2 每人一张依次发牌,最后三张底牌给地主
4 看牌前先排序 看牌就是把玩家的牌打印出来
}
});*/
Collections.sort(list, Comparator.reverseOrder());
System.out.println(list); //[world, nice, hello, good, girl, boy]
//把不是线程安全的集合转换为线程安全的
List<String> stringList = Collections.synchronizedList(list);
//stringList集合是线程安全的
}
}
### 练习
##### 模拟斗地主游戏中的发牌
1 构建一副扑克牌, 以字符串的形式表示扑克牌: ♥3, ♦4, ♣5, ♠6, 小王, 大王 除了大小王外,其他扑克有四种花色, 每种花色有13张牌
2 洗牌
3 发牌, 有三个玩家: 地主,农民1, 农民2 每人一张依次发牌,最后三张底牌给地主
4 看牌前先排序 看牌就是把玩家的牌打印出来