目录
7.5 StringBuffer和StringBuilder的区别
19.5 Comparable接口和Comparator比较器接口的选择
05 集合及常用类库
01、集合的定义
1)集合实际上是一个容器,可以容纳其他类型的数据。集合不能直接存放基本数据类型,另外集合也不能直接存储java对象,而存储的是java对象的内存地址(引用)
2)为什么集合在开发中使用较多?
集合是一个容器,是一个载体,可以一次容纳多个对象,实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询将10个数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来
list.add(100);//自动装箱
注意:
集合再java中本身就是一个容器,是一个对象,集合在任何时候存储的但是"引用"
3)java中每一个不同的集合,底层对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构(数据存储结构)当中。其中:数组、二叉树、链表、哈希表都是常见的数据结构
4)new ArrayList(); 创建一个集合,底层是数组
new LinkedList();创建一个集合对,底层是链表
new TreeSet(); 创建一个集合对象,底层是二叉树
5)集合类和集合接口都在在java.utill包下
02、集合的继承结构
在java中集合分为两大类:
一类是单个方式存储元素,这一类集合中超级父接口:java.utill.Collection;
一类是以键值对的方式存储元素,这一类元素中超级父类接口是:java.utill.Map;
2.1 超级父接口Collection
2.2 超级父接口Map
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dD9uIId3-1657728924925)(C:\Users\86177\Pictures\Saved Pictures\微信图片_20220117200816.png)]
2.3 总结
ArrayList:底层是数组
LinkedList:底层是双向链表
Vector:底层是数组,线程安全的,效率较低,使用较少
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了
TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到HashMap集合key部分了
HashMap:底层是哈希表
Hashtable:底层也是哈希表,只不过线程安全的,效率较低,使用较少
Properties:是线程安全的,并且key和value只能存储字符串String
TreeMap:底层是二叉树,可以自动按照大小顺序排序
List集合存储元素的特点:
有序:存进去和取出来的顺序相同,每一个元素都有下标
可重复:存进去1,还可以存储1
Set集合存储元素的特点:
无序:存进去和取出来的顺序不一定相同,元素没有下标
不可重复:存进去1,不可以再存储1
SortedSet集合存储元素特点:
无序不可重复的,
元素可排序:可以按照大小顺序排列
Map集合的特点
Map集合的key,就是一个Set集合
往Set集合中放数据,实际上放到了Map集合的key部分
03、Collection接口
3.1 Collection存放的元素类型
没有使用“泛型”之前Collection可以存储Object的所有子类型
使用“泛型”之后,Collection只能存储某个具体的类型
(集合中不能直接存储基本数据类型,也不能存储java对象,存储的是java对象的内存地址)
3.2 Collection的常用方法
1)boolean add(Object e)向集合中添加元素
//多态机制,接口是抽象的,无法实例化
Collection c = new ArrayList();
//测试Collection接口中的常用方法
c.add(1200);//自动装箱,实际上是放进去一个对象的内存地址。Integer c = new Integer(1200);
c.add(3.14);//自动装箱
c.add(new Object());
c.add(new Student());
c.dd(true);//自动装箱
System.out.println("集合中的元素个数是:" + c.size());//5
2)int size()获取集合中的元素的个数
3)void clear()清理集合
4)boolean contains(Object o)判断当前集合中是否包含元素o,包含返回ture,不包含返回false;
c.add("绿巨人");
System.out.println(c.contains("绿巨人"));
5)boolean remove(Object o)删除集合中的某个元素
c.remove("绿巨人");
6)boolean isEmpty()判断该集合中元素中的个数是否为0
c.clear();
System.out.println(c.isEmpty());//true
7)*Object[ ] toArray()集合转换成数组
c.add(1200);
c.add(3.14);
c.add("12345");
//转换成数组
Object[] objs = c.toArray();
for(int i = 0 ; i<objs.length ; i++){
//遍历数组
object o = objs[i];
System.out.println(o);//调用toString方法
}
04、集合遍历/迭代
4.1 如何遍历集合
迭代方法是Collection通用的一种方法,在Map集合中不能用。在所有的Collection以及子类当中使用
//创建集合对象
Collection c = new ArrayList();
//添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
//对集合Collection进行遍历/迭代
//第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();//调用iterator()拿到迭代器,迭代器是个对象,其中对象有两个方法
//第二步:通过以上获取的迭代器对象开始迭代/遍历集合
/*
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext()如果仍有元素可以迭代,则返回ture。
Object next() 返回迭代的下一个元素。
*/
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
Collection c = new ArrayList();
//添加元素
c.add(1);//自动装箱成Integer类型
c.add(2);
c.add(3);
c.add(4);
//迭代集合
Iterator it = c.Iterator();
while(it.hasNext()){
//存进去什么类型,取出来还是什么类型
Object obj = it.next();
if(obj instanceof Integer){
System.out.println("Integer类型");//这里调用toString方法转换成了字符串
}
}
4.2 迭代器是通用的
HashSet集合:无序不可重复
无序:存进去和取出的顺序不一定相同;
不可重复:存了100不可能再存100;
Collection c2 = new HashSet();
//添加元素
c2.add(100);//自动装箱成Integer类型
c2.add(2);
c2.add(33);
c2.add(43245);
c2.add(100);
//迭代集合
Iterator it2 = c2.Iterator();
while(it2.hasNext()){
System.out.println(it2.next());//这里调用toString方法转换成了字符串
}
注意:集合结构发生改变,迭代器必须重新获取
迭代过程中不能调用remove方法等改变集合的结构;如果需要在迭代过程中必须使用迭代器的remove方法删除元素
Collection c2 = new HashSet();
//添加元素
c2.add(100);//自动装箱成Integer类型
c2.add(2);
c2.add(33);
c2.add(43245);
c2.add(100);
//迭代集合
Iterator it2 = c2.Iterator();
while(it2.hasNext()){
//删除的一定是迭代器指向的当前元素
it2.remove();//如果需要在迭代过程中删除元素,必须使用迭代器的remove方法
//调用迭代器删除元素会更新迭代器,而使用集合删元素结构发生改变需要重新获取迭代器
System.out.println(it2.next());//这里调用toString方法转换成了字符串
}
Collection c = new ArrayList();
//此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器
Iterator it = c.iterator();
//添加元素
c.add(1);
c.add(2);
//此时再添加元素,无法迭代,必须重新获取迭代器
05、深入Collection的contains方法
contains方法是判断集合中是否包含某个元素的方法
底层调用了equals方法进行比较,并且String重写了equals方法,如果不重写equals方法则会比较内存地址
放在集合中类型一定要重写equals方法,八大基本类型的包装类都重写了equals方法
public class asdf{
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
//向集合中存储元素
String s1 = new String("abc");
((ArrayList) c).add(s1);
String s2 = new String("def");
((ArrayList) c).add(s2);
//新建的对象String
//集合中是否包含x?包含返回ture
String x =new String("abc");
System.out.println(c.contains(x));//true
//contains底层调用了equals方法,与字符串常量池无关,与开辟在堆区的内存地址也无关
}
}
import java.util.ArrayList;
import java.util.Collection;
public class asdf{
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
//向集合中存储元素
String s1 = new String("abc");
((ArrayList) c).add(s1);
String s2 = new String("abc");
//((ArrayList) c).add(s2);
c.remove(s2);//remove调用了equals方法
//这里s2和s1是相同的,所以删s2就是删s1
//如果s2也创建出来了,那么删s2就是删s2,
//所以只是删除集合中找到的第一个元素,就算后面还有相同的,也不会删除
System.out.println(c.size());
}
}
06、String类
6.1 String字符串的存储原理
关于Java JDK中内置的一个类:java.alng.String
1)String表示字符串类型,属于引用数据类型,不属于基本数据类型
2)在java中随随便便用双引号括起来的都是String对象
3)java中规定,双引号括起来的字符串是不可变的
4)JDK当中双引号括起来的字符串存储在方法区的"字符串常量池"当中的【字符串使用过于频繁,提高效率】
5)String类当中已经重写了equals()方法,直接调用即可
public class Text{
public static void main(String[] args){
//下面程序一共创建了三个对象,字符串常量池当中"xyz"
堆内存当中两个对象,因为new两次
String x = new String("xyz");
String y = new String("xyz");
System.out.println(x == y);//false
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);//true
//所以直接直接赋值和创建对象的字符串比较结果是不同的,所以要通过equals()方法进行比较
System.out.println("hello".quals(s1));//建议采用这种方法,避免了空指针异常
System.out.println(s1.equals("hello"));//存在空指针异常的风险
//i保存的是100
int i = 100;
//s保存的是字符串对象在字符串常量池当中的内存地址
String s = "abc";
}
}
2.2 String类常用的构造方法
1)String s = new String(" ");
2)String s = " ";(最常用的方法)
3)String(byte数组);【将byte数组全部转换为字符串】
byte[] bytes = {97,98,99};//97是a,98是b,99是c
String s2 = new String(bytes);
System.out.println(s2);//abc
4)String(byte数组,起始下标,长度);【将byte数组一部分转换为字符串】
byte[] bytes = {97,98,99};//97是a,98是b,99是c
String s3 = new String(bytes,1,2);
System.out.println(s3);//bc
5)String(char数组);【将char数组全部转换成字符串】
Char[] chars = {'我','是','中','国','人'};
String s4 = new String(chars);
s4的输出结果为"我是中国人"
6)String(char数组,起始下标,长度);【将char数组一部分转换为字符串】
Char[] chars = {'我','是','中','国','人'};
String s5 = new String(chars,2,3);
s5的输出结果为"中国人"
2.3 String常用的方法
char charAt(int index)
char c = "中国人".charAt(1);
System.out.println(c);//国
int compareTo(String anotherString)
逐位比较: 前后一致(0)、前小后大(-1)、前大后小(1)
int result = "abc".compareTo("abc");
System.out.println(result);//0
boolean contains(CharSequence s)
System.out.println("HelloWorld.java".contains(".java"));//true
boolean endWith(String suffix)
System.out.println("text.java".endWith("text"));//true
boolean startWith(String suffix)
System.out.println("HelloWorld.java".startWith(".java"));//true
boolean equals(Object anObject)
equals()方法只能看出是否相等,
compareTo()方法可以看出谁大谁小
boolean equalsIgnoreCase(String anotherString)
System.out.println("ABC".equalsIgnoreCase(".Abc"));//ture
byte [] getBytes()
byte[] bytes = "abcdef".getBytes();
for(int i=0;i<bytes.length;i++){
System.out.println(bytes[i]);//
}
2.3.9 【子字符串在当前字符串第一次出现处的索引(下标)】
int indexOf(String str)
System.out.println("oraclejavac++htmlc#phpython".indexOf("java"));//6
2.3.10 【子字符串在当前字符串最后一次出现处的索引(下标)】
int lastIndexOf(String str)
System.out.println("oraclejavac++htmlc#phpjavapython".indexOf("java"));//
boolean isEmpty()
底层调用的是length()方法
String s = "";
System.out.println(s.isEmpty);//ture
int length()
判断数组长度是length属性,判断字符串长度是length()方法
System.out.println("abc".length());
String replace(CharSequence target,CharSequence replacement)
String的父接口就是:CharSequence
String newString = "http://www.baidu.com".replace("http://","heetps://");
System.out.println(newString);//"https://www.baidu.com"
String[ ] split(String regex)
String[] ymd = "1980-10-11".split("-");//以"-"分隔符进行拆分
for(int i=0;i<ymd.length;i++){
System.out.println(ymd[i]);
}//1980 10 11
String substring(int beginIndex)
参数是起始下标
System.out.println("http://www.baidu.com".substring(7));//www.baidu.com
String substring(int beginIndex,int endIndex)
System.out.println("http://www.baidu.com".substring(7,10));//www
左闭右开,起始下标【包含】,终止下标【不包含】
char[] toCharArray()
char[] chars = "我是中国人".toCharArray();
for(int i = 0;i<chars.length;i++){
System.out.println(chars[i]);
}//我 是 中 国 人
String toLowerCase()
System.out.println("ASDFG".toLowerCase());//asdfg
String toUpperCase()
System.out.println("asdfg".toLowerCase());//ASDFG
String trim()
System.out.println(" asd wd ".trim());//asd wd
valueOf
1)String中唯一的静态方法,不需要new对象
System.out.println(String.valueOf(100));//100(字符串)
2)参数是一个对象的时候,会调用该对象的toString()方法,但没有重写,输出的是对象的内存地址
System.out.println(String.valOf(new Customer());//是对象的内存地址
System.out.println()这方法在输出的时候都是先转成字符串再输出
07、StringBuffer
7.1 在字符串拼接时的资源浪费
若采用下列代码,则会占有大量的内存区空间,造成内存空间的浪费
String s = "abc";
s += "hello";
就以上两行代码,就导致在方法区字符串内存池当中创建了三个对象:
“abc”,“hello”,“abchello”
7.2 优化程序引入StringBuffer
public class String1 {
public static void main(String1[] args) {
//创建一个初始化容量为16个byte[]数组(字符串缓冲区对象)
StringBuffer stringBuffer = new StringBUffer();
//拼接字符串,以后拼接字符串统一调用append()方法
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("d");
stringBuffer.append(3.14);
//append方法底层在进行追加的时候,如果byte数组满了,会自动扩容
stringBuffer.append(100L);
System.out.println(stringBuffer.toString());
//System.out.println(stringBuffer);
}
}
7.3 如何优化StringBuffer的性能
1)在创建StringBuffer的时候尽可能给定一个初始化容量
2)最好减少底层数组的扩容次数。预估计一下,给一个合适的较大的初始化容量,提高程序的执行效率
StringBuffer sb = new StringBUffer(100);
7.4 优化大量字符串的拼接的程序
建议使用JDK自带的:java.lang.StringBuffer和java.lang.StringBuilder
7.5 StringBuffer和StringBuilder的区别
StringBuffer方法都有:sychronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的
StringBuilder方法都没有:sychronized关键字修饰。表示StringBuilder在多线程环境下运行是不安全的
08、List接口
1)List集合存储元素特点:有序可重复
有序:List集合中的元素有下标,从0开始,以1递增;
可重复:存储一个1,还可以再存储1;
2)List接口中特有的常用方法:
添加元素到指定位置 void add(int index,E element)
返回列表中指定位置的元素 Object get(int index)
返回列表中第一个出现的指定元素的索引,如果不包含返回-1 int intdexOf(Object c)
返回列表中最后出现的指定元素的索引,如果不包含返回-1 int lastIndexOf(Object c)
移除列表中指定位置的元素(可选操作) Object move(Object o)
用指定元素替换列表中指定位置的元素(可选操作) Object set(int index,E element)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class asdf{
public static void main(String[] args) {
//调用List特有的方法需要创建List引用
List myList = new ArrayList();
//添加元素
myList.add("A");//默认都向集合末尾添加元素
myList.add("B");
myList.add("C");
myList.add("D");
myList.add("E");
//在列表的指定位置插入元素(第一个参数是下标)
myList.add(1,"KING");//这个方法使用的不多,因为效率太低
//迭代
Iterator it = myList.iterator();
while(it.hasNext()){
Object ect = it.next();
System.out.println(ect);
}
//根据下标获取元素
Object firstObj = myList.get(0);
System.out.println(firstObj);
//因为有下标,所以List集合有自己比较独特的遍历方式【通过下标】
for(int i = 0;i<mylist.size();i++){
Object obj = myList.get(i);
System.out.println(obj);
}
}
}
09、ArrayList集合
9.1 Arrays工具类的使用
判断两个数组是否相等。 boolean equals(int[] a,int[] b)
输出数组信息。 String toString(int[] a)
将指定值填充到数组之中。 void fill(int[] a,int val)
对数组进行排序。 void sort(int[l a)
对排序后的数组进行二分法检索指定的值。 int binarySearch(int[] a,int key)
9.2 初始化容量
1)默认初始化容量是10(JDK13新特性:底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)
注意:size方法测的是集合中元素的个数,不是集合的容量
2)集合底层是一个Object [ ] 数组
3)构造方法:
new ArrayList();
new ArrayList(20);
//指定初始化容量100
List myList2 = new ArrayList(100);
//创建一个HashSet集合
Collection c = new HashSet();
//添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
//通过这个构造方法就可以将HashSet集合转换成List集合
List myList3 = new ArrayList(c);
for(int i =0;i<myList3.size();i++){
System.out.println(myList3.get(i));
}
9.3 扩容
1)默认初始化容量是10,容量满了之后如果再添加元素,自动扩容,扩容的大小是原来的1.5倍
2)ArrayList的底层是数组,尽可能少的扩容,因为数组扩容效率比较低。建议在使用ArrayList的是初始化容量给顶一个预估计的初始化容量,减少扩容
9.4 关于数组
优点:检索效率比较高;
缺点:随机增删元素的效率比较低;
向数组元素末尾添加元素,效率很高,不受影响;
10、位运算
// 5
// >> 1 二进制右移1位
// >> 2 二进制右移2位
// 10的二进制:00001010 【10】
// 10的二进制右移1位是:00000101 【5】
左移是乘以,右移是除以 2的n次方倍,n为移动的位数
11、LinkedList集合
11.1 链表的优缺点
链表的优点:空间存储内存地址不连续,随机增删元素效率较高(因为增删元素不涉及到大量的元素位移)
链表的缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历
ArrayList之所以检索效率高,不是单纯因为下标的原因,是因为底层数组发挥的作用
LinkedList集合照样有下标,但是检索某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历
11.2 Java实现单链表
public class asdf{
public static void main(String[] args) {
Link link = new Link();
link.add(100);
link.add(200);
link.add(300);
System.out.println(link.size);
}
}
class Node{
//存储的数据
Object data;
//下一个结点的内存地址
Node next;
public Node(){
}
public Node(Object data,Node next){
this.data = data;
this.next = next;
}
}
class Link {
//头指针
Node header = null;
int size = 0;
public int size(){
return size;
}
//向链表中添加元素的方法
public void add(Object data) {
//创建一个新的节点对象
//让之前单链表的末尾节点next指向新节点对象
if(header == null) {
//说明还没有节点,new一个新的节点对象,作为头节点对象
header = new Node(data,null);
}else{
//说明头结点已经有了
//找出当前末尾节点,让当前末尾节点的next是新节点
Node currentLastNode = findLast(header);
currentLastNode.next = new Node(data,null);
}
size++;
}
//专门查找末尾结点的方法
private Node findLast(Node node) {
if(node.next == null) {
//如果节点的next是空则为末尾节点
return node;
}
return findLast(node.next);//递归
}
//删除链表中的某个数据的方法
public void remove(Object obj){
}
//修改链表中的某个数据元素的方法
public void modify(Object newobj){
}
//查找链表中某个元素的方法
public int find(Object obj){
return 1;
}
}
11.3 关于LinkedList
1)LinkedList集合没有初始化容量
2)最初这个链表中没有任何元素。first和last引用都是null
3)不管是LinkedList还是ArrayList,以后写代码不需要关系是哪个集合,我们需要面向接口编程,调用的方法都是接口中的方法
List list2 = new ArrayList();//这样写表示底层用的是数组
List list2 = new LinkedList();//这样写表示底层用的是双向链表
List2.add("123");
List2.add("456");
List2.add("789");
//这些方法都是面向的是接口编程
12、Vector集合
1)底层是一个数组
2)初始化容量是10
3)超过10后自动扩容,每次扩容是原容量的2倍
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
public class java{
public static void main(String[] args) {
List l = new Vector();
l.add("123");
l.add("456");
l.add("789");
l.add("122");
Iterator as = l.iterator();
while(as.hasNext()){
Object object = as.next();
System.out.println(object);
}
}
}
4)Vector中的所有方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率较低,使用较少
12.2 将不安全的ArrayList集合转换成线程安全
使用集合工具类 java.utill.Collections;
注意:java.utill.Collection 是集合接口
java.utill.Collections 是集合工具类
List myList = new ArrayList();//非线程安全的
//变成线程安全的
Collection.synchronizedList(myList);
//现在mylist就是线程安全的了
13、泛型
13.1 关于泛型
jdk1.5后的新特性
泛型这种语法机制只能在程序编译阶段起作用,只是给编译器参考的【运行阶段泛型意义不大】
优点:
1)集合中存储的元素类型统一了
2)从集合中取出的元素类型是泛型指定的类型,不需要进行大量的"向下转型"
缺点:
1)导致集合中元素缺乏多样性
2)调用父类中特有的方法不需要向下转型,但调用子类中特有的方法还是需要向下转型
13.2 泛型的具体使用
public class java{
public static void main(String[] args) {
/*List l = new Vector();
Cat cat = new Cat();
Dog dog = new Dog();
l.add(cat);
l.add(dog);
Iterator it = l.iterator();
while(it.hasNext()){
Object object = it.next();
// 使用泛型前需要向下转型
if(object instanceof Animal){
Animal a =(Animal)object;
a.move();
}
}*/
//使用泛型后
List <Animal> l = new Vector<Animal>();
//指定list集合只能存储Animal,那么存储String就编译报错了
//这样使用泛型之后,集合中元素的数据类型更加统一
Cat cat = new Cat();
Dog dog = new Dog();
l.add(cat);
l.add(dog);
//获取迭代器
//表示迭代器迭代的是Animal类型
Iterator <Animal>it = l.iterator();
while(it.hasNext()){
//使用迭代之后返回的数据都是Animal类型的,不需要强制类型转换
Animal a= it.next();
a.move();
}
}
}
class Animal{
public void move(){
System.out.println("动物在移动");
}
}
class Cat extends Animal{
public void move(){
System.out.println("猫在吃鱼");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗在吃肉");
}
}
13.3 ArrayList<>()
ArrayList<这里的类型会自动推断>()
前提是jdk8之后才允许
List myList = new ArrayList <> ();
13.4 自定义泛型
<>尖括号里面的是一个标识符,随便写
一般是,
E是Element单词首字母
T是Type单词首字母
public class asdf<E>{
public void Do(E o){
System.out.println(o);
}
public static void main(String[] args) {
asdf<String> gt = new asdf<>();
gt.Do("ssd");
}
}
14、foreach【增强for循环】
jdk5.0之后的新特性
14.1 语法格式
for(元素类型 变量名:数组或集合){
System.out.println(变量名);
}
14.2 增强for(foreach)
int[] arr = {43,34,3241,5463,23,1};
for(int data:arr){
System.out.println(data);
}
data代表数组中的每一个元素,可以改变
缺点:没有下标
14.3 集合使用foreach
//创建List集合
List<String>strList = new ArrayList<>();
//添加元素
strList.add("hello");
strList.add("world");
strList.add("kitty");
//遍历使用选代器方式
Iterator<String> it = strlist.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
//使用下标方式(只针对于有下标的集合)
for(int i = B; i < strList.size(); i++){
System.out.println(strList.get(i));
}
//使用foreach
for(String s:strlist){ // 因为泛型使用的是String关星,所以是:String s
System.out.println(s);
}
15、Map接口
15.1 关于Map接口
1)MapCollection没有继承关系。
2)Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的一个附属品
15.2 Map接口中常用的方法
V put(K key,V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为e
Set keySet() 获取Map集合所有的key(所有的键是一个set集合)
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection values() 获取Map集合中所有的value,返回一个Collection
Set<Map.Entry <K,V> >entrySet() 将Map集合转换成set集合
注意:关于将Map集合转换成set集合Set<Map.Entry<K,V>>entrySet()
假设现在有一个Map集合,如下所示:.
map1集合对象
key value
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu
Set set = map1.entrySet();
set集合对象
1=zhangsan【转换成的这个Set集合,Set集合的类型是Map.Entry(一种类型的名字)】
2=lisi
3=wangwu
4=zhaoliu
Entry是Map中的静态内部类
// 创建Map集合对象
Map<Integer, String> map = new HashMap<>();
// 向Nap集合中添加键值对
map.put(1,"zhangsan");// 1在这里进行了自动装箱。
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4, "zhaoliu");
// 通过key获取value
String value = map.get(2);
System.out.println(value);
//获取键值对的数量
System.out.println("键值对的数量:"+map.size());|
// 通过key删除key-value
map.remove(key: 2);
System.out.println("键值对的数量:"+map.size());
// 判断是否包含某个key
//contoins方法屈层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
System.out.println(imap.containsKey(new Integer( value: 4))); // true
//判断是否包含某个value
System.out.println(map.containsValue(new String( original: "wangwu"))); // trud
//获取所有的value
Collection<String> values = map.value();
for(String s:values){
System.out.println(s);
}
//清空Map集合
map.clear();
System.out.println("键值对的数量:"+map.size());
15.3 遍历Map集合
第一种方式:获取所有的key,通过遍历key,来遍历value
Map<Integer, String> map = new MashMap<>();
map.put(1,"zhangsan");
map.put(2,"1isi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//迎历Nap集合
//获取所有的key,所有的key是一个5et集合
Set<Integer> keys = map.keySet();
//遍历key,遗过key获取val ue
//选代器可以
Iterator<Integer> it = keys.iterator();
//迭代器遍历
while(it.hasNext())(
// 取出其中一个key
Integer key = it.next();
// 通过key欢取value
String value = map.get(key);
System.out.println(key + "=" + value);
}
//foreach循环遍历
for(Integer key:keys){
System.out.println(key + "=" +map.get(key));
}
第二种方式:Set<Map.Entry<K,V>>entrySet() 【效率高】
//把Map集合直接全部转换成Set集合。
// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历set集合,每一次取出一个Node
//迭代器遍历
Iterator<Map.Entry<Integer,String>> it2 = set.iterator(>;
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getkey();
String value = node.getValue();
System.out.println(key + "=" + value);
}
// foreach
for(Map.Entry<Integer,String> node : set){
System.out.println(node.getkey() + "---" + node.getValue());
}
16、HashMap集合
16.1 关于HashMap集合
1)HashMap集合底层是哈希表/散列表的数据结构。
2)哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
4)HashMap集合底层的源代码:
public class HashMap{
//HashMap底层实际上就是一个数组。(一维数组)
Node<K,V>[] table;
//静态的内部类HashMap.Node
static class Node<K,V> {
final int hash;// 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法可以转换成数组的下标
final K key;// 存储到Map集合中的那个key
v value;//存储到Map集合中的那个value
Node<K,V> next;//下一个节点的内存地址。
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
5)在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。
当红黑树上的节点数量小于6时,会重新把红思树变成单向链表数据结构。
【提高效率,二叉树的检索会再次缩小扫描范围】
6)对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
7)HashMap允许key部分为空,但null值只能为一个
16.2 HashMap集合的key部分特点
无序,不可重复。
为什么无序?因为不一定挂到哪个单向链表上。
为什么不可重复?通过equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
16.3 哈希表HashMap使用不当无法发挥性能
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。
这种情况我们成为:散列分布不均匀。
1)什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。
所以散列分布均匀需要重写hashCode()方法时有一定的技巧。
2)为什么哈希表的随机增删,以及查询效率都很高?
增删是在链表上完成。查询也不需要都扫描,只需要部分扫描。
重点:HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写。
16.4 map.put(k,v)实现原理
第一步:先将k,v封装到Node对象当中。
第二步:底层会调用k的hashCode()方法得出hash值,
通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。
如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals,
如果所有的equals方法返回都是false,那么这个新节点将果被添加到链表的末尾。
如果其中有一个equals返回了会true,那么这个节点的value将会被覆盖。
16.5 v = map.get(k)实现原理
先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,
如果这个位置上什么也没有,返回null;
如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals,
如果所有equals方法返false,那么get方法返回null,只要其中有一个节点的k和参数k equals的时候返回true,
那么此时这个节点的value就是我们要找的value,get方法最终返回这个要找的value。
//测试HashMap集合key部分的元素特点
//Integer是key。他的euquals和hashCode都重写了
import java.util.*;
public class java{
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1111,"zahngsan");
map.put(2222,"wusan");
map.put(3333,"lisi");
map.put(1111,"sadasd");//key重复的时候value会自动覆盖
System.out.println(map.size());
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历Map集合
for(Map.Entry<Integer,String> entry : set){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
16.6 HashMap集合的初始化容量和扩容
HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容为原来的2倍。
重点:
HashMap 集合初始化容量必须是2的倍数,这也是官方推荐的,
这是因为达到散列均匀,为了提高HashMap集合的存取效率所必须的。
16.7 euquals和hashCode的重写
public class HashMapTest02 {
public static void main(String[] args){
Student s1 = new Student( name: "zhangsan");
Student s2 = new Student( name: "zhangsan");
//重写equals方法之前是false
//System.out.println(s1.equals(s2)); // false
//重写equals方法之后是true
System.out.println(s1.equals(s2));//true(s1和s2表示相等)
System.out.println("s1的hashCode=" + s1.hashCode());//284720968
System.out.println("s2的hashCode="+s2.hashCode());//122883338
//s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,
//按说只能放进去1个。(HashSet集合特点:无序不可重复)
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size());//这个结果按说应该是1.但是结果是2.显然不符合HashSet集合存储特点,所以要重写equals和HashCode方法
}
}
1)注意:
hashCode如果不重写,相同的对象内容信息通过哈希算法得到的哈希值不相同,会存到不同的数组下标下,不符合预期
所以需要重写hashCode,得到相同的哈希值,然后存到同一个数组下标下,进行equals比较,才会符合预期
如果一个类的equals方法重写了,那么hashCode()方法必须重写。并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以hashCode()方法的返回值也应该相同。
2)如何重写?
直接使用IDEA工具生成,但是这两个方法需要同时生成。
3)终极结论:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
17、Hashtable集合
Hashtable不允许key和value部分为空,会出现空指针异常
Hashtable方法都带有synchronized:线程安全的。线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。
HashMap和Hashtable底层都是哈希表
Hashtable的初始化容量为11,默认加载因子是0.75扩容为2倍再加一
18、Properties类
目前只需要掌properties属性类对象的相关方法即可。
Properties是一Map集合,继承Hashtable,Properties 的key和value 都是string类型。
Properties被称为属性类对象。
Properties是模程安全的。
public class PropertiesTeste1 {
public static void main(String[] args){
// 创建一小properties对象
Properties pro = new Properties();
//properties的存
pro.setProperty("ur1", "jdbc:mysq1://localhost:3306/bjpovernode");
pro.setProperty("driver","com.mysq1.jdbc.Driver");
pro.setProperty("username","root");
pro.setProperty("password", "123");
// 通过key获取value
String url = pro.getProperty("ur1");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.printIn(password);l
}
}
19、TreeSet集合
19.1 关于TreeSet集合
1)TreeSet集合底层实际上是一个TreeMap
2)TreeMap集合底层是一个二叉树。
3)放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4)TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。
19.2 排序方式:实现Comparable接口
无法对自定义类型进行排序,因为没有指定对象之间的比较原则。谁大谁小并没有说明啊。
程序运行的时候出现了这个异常:
java.long.ClossCastException:
class com.bjpowernode.javase.collection.Person
cannot be cast to class java.Lang.Comparable
出现这个异常的原因是:自定义类没有实现java.Lang.Comparable接口。
import java.util.TreeSet;
public class TreeSetTest04 {
public static void main(String[] args){
Customer c1 = new Customer( age: 32);
Customer c2 = new Customer( age: 20);
Customer c3 = new Customer( age: 30);
Customer c4 = new Customer( age: 25);
// 创建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
//添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
customers.add(c4);
//遍历
for (Customer c : customers){
System.out.println(c);
}
}
}
//放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
//并且实现compareTo方法equals可以不写。
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
//需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
// k.compareTo(t.key)
//拿着参数k和集合中的每一个k进行比较,返回值可能是>e<e=e
//比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
@Override
public int compareTo(Customer c) { // cl.compareTo(c2);
// this是c1
//c是c2
//c1和c2比较的时候,就是this和c比较。
/*int agel = this.age;
int age2 = c.age;
if(agel == age2){
return 0;
} else if(agel > age2){
return 1;
} else.{
return -1;
}*/
//return this.age - c.age; // =0 >0 <0
return c.age - this.age;
//compareTo方法的返回值很重要:
//返回e表示相同,value会覆盖。
//返回>0,会继续在右子树上找。【10-9=1,1>0的说明左边这个数字比较大。所以在右子树上找。
//返回<0,会继续在左子树上找。
}
public String toString(){
return "Customer[age="+age+"]";
}
}
19.3 二叉树数据结构
1)TreeSet/TreeMap是自平衡二叉树。遵循左小右大原则存放。
2)遍历二叉树的时候有三种方式:
存放是要依靠左小右大原则,所以这个存放的时候要进行比较。
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
注意:
前中后说的是“根”的位置。根在前面是前序,根在中间是中序,根在后面是后序。
3)TreeSet集合/TreeMap集合采用的是:中序遍历方式。
Iterator迭代器采用的是中序遍历方式。左根右。
4) 100 200 50 60 80 120 140 130 135 180 666 .40 55
5)采用中序遍历取出:
40 50 55 60 80 100 120 130 135 140 180 200 666
存放的过程就是排序的过程。取出来就是自动按照大小顺序排列的。
19.4 排序方式:实现Comparator比较器接口
public class TreeSetTest06 {
public static void main(String[] args) {
//创建TreeSet集合的时候,需要使用这个比较器。
//TreeSet<wuGui>wuGuis=new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。
//给构造方法传递一个比较器。
TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
wuGuis.add(new WuGui( age: 1000));
wuGuis.add(new WuGui( age: 800));
wuGuis.add(new WuGui( age: 810));
for(WuGui wuGui : wuGuis){
System.out.println(wuGui);
}
}
}
//乌龟
class WuGui {
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return"小乌龟["+
"age=1" + age +
']';
}
}
//单独在这里编写一个比较器
//比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
//指定比较规则
//按照年龄排序
return o1.age - o2.age;
}
}
19.5 Comparable接口和Comparator比较器接口的选择
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
Comparator接口的设计符合OCP原则。
20、Collections工具类
java.util.Collection 集合接口
java.util.Collections集合工具类,方便集合的操作。
public class CollectionsTest {
public static void main(String[] args){
// ArrayList集合不是线程安全的。
List<String> list = new ArrayList<>();
//变成线程安全的
Collections.synchronizedlist(list);
//排序
list.add("abf");
list.add("abx");
list.add("abc");
list.add("abe");
Collections.sort(list);
for(String s : list){
System.out.println(s);
}
List<WuGui> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui( age: 1000));
wuGuis.add(new WuGui( age: 8000));
Collections.sort(wuGuis);//对List集合中元素排序,需要保证List集合中的元素实现了Comparable接口
for(WuGui wg : whGuis){
System.out.println(wg);
}
}
class WuGui implements Comparable<WuGui>{
int age;
public WuGui(int age){ this.age = age; }
@Override
public int compareTo(WuGui o){
return this.age - o.age;
}
@Override
public String toString() {
return "WuGui2{" +
"age=" + age +
'}';
}
}
//对Set集合怎么排序呢?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
//将set集合转换成List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s :myList){
System.out.println(s);
//Collections.sort(l正st集合,比较器对象);
}
}
21、关于异常、接口、集合的练习【战争】
题目要求:
开放性题目,随意发挥;
建立一个武器集合,包括添加武器,遍历武器使得各武器能充分发挥功能,其中功能包括可移动、可攻击。最好不要面向具体编程,降低程序的耦合度,提高扩展力,再添加过程中若出现武器满的情况在控制台作出反馈,但不停止程序
测试类
package zhandou; import java.util.ArrayList; import java.util.List; public class text{ public static void main(String[] args) { List<Weapon> weaponList = new ArrayList<>(); Weapon GaoShePao = new GaoShePao("C"); Weapon TanKe = new TanKe("A"); Weapon YunShuJi = new YunShuJi("B"); Weapon ZhanDouJi = new ZhanDouJi("D"); weaponList.add(GaoShePao); weaponList.add(TanKe); weaponList.add(YunShuJi); weaponList.add(ZhanDouJi); try { if (weaponList.size() > 4) throw new WeaponException("越界"); } catch (WeaponException e) { System.out.println(e.getMessage()); } for(Weapon data:weaponList){ if (data instanceof Moveable) { Moveable m = (Moveable) data; m.move(); System.out.println("$$$$$$$$$$$"); }if (data instanceof Shootable) { Shootable b = (Shootable) data; b.shoot(); System.out.println("############"); } } } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-or5jhvpP-1657728924927)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
武器越界异常
package zhandou; public class WeaponException extends Exception{ public WeaponException() { } public WeaponException(String message) { super(message); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZVjnXld-1657728924927)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
移动接口
package zhandou; public interface Moveable { void move(); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzTSax6k-1657728924928)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
射击接口
package zhandou; public interface Shootable { void shoot(); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i5Yt80s1-1657728924928)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
父类:武器
package zhandou; public class Weapon { }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fu89hCgY-1657728924928)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
子类: 战斗机
package zhandou; public class ZhanDouJi extends Weapon implements Shootable,Moveable{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public ZhanDouJi(String name) { this.name = name; } public ZhanDouJi() { } public void shoot() { System.out.println("战斗机射击!"); } public void move(){ System.out.println("战斗机起飞!"); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R959DyDu-1657728924929)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
子类:高射炮
package zhandou; public class GaoShePao extends Weapon implements Shootable{ private String name; public GaoShePao(String name) { this.name = name; } public GaoShePao() { } public void setName(String name) { this.name = name; } public String getName() { return name; } public void shoot() { System.out.println("高射炮射击!"); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tnwcI01r-1657728924929)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
子类:坦克
package zhandou; public class TanKe extends Weapon implements Moveable,Shootable{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public TanKe() { } public TanKe(String name) { this.name = name; } public void move() { System.out.println("坦克启动!"); } public void shoot(){ System.out.println("坦克射击!"); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KOlj1dhu-1657728924929)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
子类:运输机
package zhandou; public class YunShuJi extends Weapon implements Moveable{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public YunShuJi() { } public YunShuJi(String name) { this.name = name; } public void move() { System.out.println("运输机起飞!"); } }
题后总结 :
1)抛出异常方面代码思路不够明确,其中若自定义集合中的异常,不知道如何写,只能出现自抛自抓的情况
2)父类型能转型称为接口类型,没有想到真的能实现,是在测试中发现的;
3)以后写程序要把接口、类、放在不同的Java class/interface,思路更加清晰