学习总览:


一、集合

1.Collection

1.1 Collection接口

  • 集合: 存储数据的容器(数据结构)

  • Collection:是一个接口,定义了操作集合相关方法

  • Collection下有两个子接口 :List和 Set

1.2 常用方法

​
/*** Collection 接口中的方法总结:**/
public class TestCollection {
​public static void main(String[] args) {/*泛型机制:规定集合中存储的元素类型*///创建对象,接口类型指向实现类对象——————向上造型Collection<String> list1 = new ArrayList<String>();/*直接打印元素内容的原因是:在其继承关系AbstractCollection中重写了toString*/System.out.println(list1);//[]
​//常用方法:// 1.add(Object o):向集合中添加元素,返回值为booleanlist1.add("java");list1.add("c");System.out.println("添加后的集合为:"+list1);
​// 2.addAll(Object o):向集合中添加元素,返回值为booleanCollection<String>  list2 =   new LinkedList<String>();list2.add("c++");list2.add("php");System.out.println("list2的元素为:"+list2);list1.addAll(list2);System.out.println("list添加list2的元素后为:"+list1);
​//3.contains(Oject o):检测colletion是否包含指定元素,是返回trueSystem.out.println("list2集合是否包含c++:"+list2.contains("c++"));  //true
​//4.containsAll(Collection<?> list):检测当前colletion是否包含colletion所有元素,是返回trueSystem.out.println("list1集合是否包含list2的所有元素:"+list1.containsAll(list2));  //true
​//5.equals(Oject o):比较集合是否与指定对象相等,Collection<String>  list3 =   new LinkedList<String>();Collection<String>  list5 =   new LinkedList<String>();
​list3.add("python");list5.add("python");System.out.println(list3);String str = "[python]";System.out.println("list3与str是否相等:"+list3.equals(str));  //falseSystem.out.println("list3与str是否相等:"+list3.equals("[puthon]"));  //falseSystem.out.println("list3与list5是否相等:"+list3.equals(list5));//true
​//6.hashCode():返回collection的哈希码System.out.println("list3的哈希码为:"+list3.hashCode());
​//7.isEmpty():检测colletion不包含元素(空),返回trueSystem.out.println(list3.isEmpty());   //falseCollection<String>  list4 =   new LinkedList<String>();System.out.println(list4.isEmpty());   //true
​//8.remove(Object o):移除指定元素,存在的话list4.addAll(list1);System.out.println(list4);   //[java, c, c++, php]list4.remove("c");System.out.println(list4);   //[java, c++, php]
​//9.removeAll(Collection<?> e):移除指定Collection中的所有元素,存在的话list4.removeAll(list2);System.out.println(list4);  //[java]
​//10.retainAll(Collection<?> e):  删除该list4中除了list1的元素list4.addAll(list1);list4.retainAll(list2);System.out.println(list4); // [c++, php]
​//11.size()System.out.println("list4中的元素个数为:"+list4.size());   //2
​//12.toArray()Object[] objects = list4.toArray();String[]  srings = new String[list4.size()];/*向下造型,类型转换有问题String[]  Strings = (String[]) objects;   无法转型,不符合规范,数组有多个元素,多个元素的类型都为Object,主要原因:我们需要将存储在Object类型数组中的每一个Object对象都转换为String类型*/for (int i = 0; i < objects.length; i++) {String s = (String) objects[i];srings[i] = s;}System.out.println("list4中所有元素的数组:"+Arrays.toString(srings));
​System.out.println("list4中所有元素的数组:"+ Arrays.toString(list4.toArray()));
​//13.toArray(T[] a):String[] strings2 = list4.toArray(new String[list4.size()]);System.out.println(Arrays.toString(strings2));
​//14.clear():清除所有元素list4.clear();System.out.println(list4);}
}
​

2.List

2.1 List接口

  • List: 有序有重复的集合

  • List常见实现类:

  1. ArrayList: 数组实现 线程非安全效率高

  2. LinkedList: 双向链表 不存在扩容问题

  3. Vector: 数组实现 线程安全,效率低不建议使用

  4. Stack: Vector的子类 数组实现 遵循先进后出的原则

2.2 List使用

  • 测试有序有重复

public static void main(String[] args) {
List<Integer> lists = new ArrayList<Integer>();
lists.add(23);
lists.add(15);
lists.add(64);
System.out.println(lists);//23 15 64
/*
有序:指自然顺序 ;存储元素的先后顺序 不是排序
*/
lists.add(64);
//可以有重复元素
System.out.println(lists);
}

  • ArrayList的初始容量和扩容机制

   初始容量:10

private static final int DEFAULT_CAPACITY = 10;

扩容机制: 原来容量的1.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);
   **ArrayList底层是数组实现,线性结构,更适合查询

2.3 ArrayList和LinkedList区别(重点)

1、数据结构不同
ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
2、效率不同
当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的
数据存储方式,所以需要移动指针从前往后依次查找。
当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为
ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数
据的移动。
3、自由性不同
ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后
添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它
不便于使用。
4、主要控件开销不同
ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点
信息以及结点指针信息。

2.4 截取子串subList

subList(int fromIndex, int toIndex): 索引为前包后不包

/*5.subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分。---sublist截取子串,和原来的list共用一块资源(SubList是ArrayList的一个内部类,对他操作也是对原list操作)---sublist截取的子串如果修改来了,则原list也会改变*/List<Integer> sublist = list1.subList(2,6);for (int i = 0; i < sublist.size(); i++) {sublist.set(i,sublist.get(i)*10);}System.out.println("list1中索引为2-5元素为:"+sublist);System.out.println(list1);

2.5  list与数组之间的转换

  • 集合和数组的转化:

  • Object[] toArray()

  • T[] toArray(T[] a)

        //集合转数组Object[] objects = list1.toArray();System.out.println(objects);Integer[] integers = new Integer[list1.size()];System.out.println(Arrays.toString(integers));//数组转集合Integer[] integers1 = new Integer[list1.size()];for (int i = 0; i < integers1.length; i++) {integers1[i] = (int) (Math.random()*100);}System.out.println("Interger数组的元素为:"+Arrays.toString(integers));List<Integer> aslist = Arrays.asList(integers1);System.out.println("转换后的aslist"+aslist);/*注意:数组转换后的list不能直接改变,如果改变就会出现UnsupportedOperationExceptionasList.add(12);*//*解决方法;可以通过定义一个新的list,然后转换后的aslist添加进去,对新的list操作*/ArrayList<Integer> list3 = new ArrayList<>();list3.addAll(aslist);System.out.println("新的list添加aslist后:"+list3);list3.add(12);System.out.println("操作list后:"+list3);

2.6 常用方法


public class TestList1 {//作业:测试list接口中定义的其他方法(除了collection中定义的方法及Iterationator相关的方法public static void main(String[] args) {List<Integer> list1 = new ArrayList<>();list1.add(1);list1.add(2);list1.add(4);list1.add(3);//0.add(int index, E element):在列表的指定位置插入指定元素(可选操作)。System.out.println("list1为:"+list1);list1.add(4,5);System.out.println("list1中在索引为4的位置添加元素后为:"+list1);//addAll():添加集合,集合长度是+nList<Integer> list2= new ArrayList<>();list2.add(12);list1.addAll(list2);System.out.println("list1中添加list2是:"+list1);//1.get(int index):返回列表中指定位置的元素。System.out.println("list1中索引为2的元素是:"+list1.get(2));//2.indexOf(Object o):返回列表中首次出现指定元素的索引,如果列表不包含此元素,则返回 -1。System.out.println("list1中java首先出现的索引为:"+list1.indexOf(4));//3.lastIndexOf(Object o):返回列表中最后出现指定元素的索引,如果列表不包含此元素,则返回 -1。list1.add(3);list1.add(1);System.out.println("list1中java最后出现的索引为:"+list1.lastIndexOf(3));//4.set(int index, E element):用指定元素替换列表中指定位置的元素(可选操作)。System.out.println("list1替换前为:"+list1);list1.set(2,6);System.out.println("list1替换前后:"+list1);/*5.subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分。---sublist截取子串,和原来的list共用一块资源(SubList是ArrayList的一个内部类,对他操作也是对原list操作)---sublist截取的子串如果修改来了,则原list也会改变*/List<Integer> sublist = list1.subList(2,6);for (int i = 0; i < sublist.size(); i++) {sublist.set(i,sublist.get(i)*10);}System.out.println("list1中索引为2-5元素为:"+sublist);System.out.println(list1);//6.集合转数组Object[] objects = list1.toArray();System.out.println(objects);Integer[] integers = new Integer[list1.size()];System.out.println(Arrays.toString(integers));//7.数组转集合Integer[] integers1 = new Integer[list1.size()];for (int i = 0; i < integers1.length; i++) {integers1[i] = (int) (Math.random()*100);}System.out.println("Interger数组的元素为:"+Arrays.toString(integers));List<Integer> aslist = Arrays.asList(integers1);System.out.println("转换后的aslist"+aslist);/*注意:数组转换后的list不能直接改变,如果改变就会出现UnsupportedOperationExceptionasList.add(12);*//*解决方法;可以通过定义一个新的list,然后转换后的aslist添加进去,对新的list操作*/ArrayList<Integer> list3 = new ArrayList<>();list3.addAll(aslist);System.out.println("新的list添加aslist后:"+list3);list3.add(12);System.out.println("操作list后:"+list3);}}

2.7 总结

/**
 * list:  有序(自然顺序)又重复的集合
 *     有序:自然顺序-->元素添加先后顺序,而不是排序
 *     有重复:可以重复多个相同的元素
 *     4个实现类:
 *          ArrayList:  底层封装了一个Object的数组,非安全线程 ---Object[]  elementData
 *          LinkedList:底层是一个链表(Node)
 *          Vector:底层也是数组,是线程安全,效率慢一点
 *          Stack:底层也是数组,只不过遵循了先进后出的原则
 *
 * ArrayList的扩容机制:
 *      1.当添加第一个元素时,会执行grow方法,给Object[]给一个默认容量 是10
 *      2.当添加后续元素<10时,不会再执行grow方法进行扩容,Object[] 容量为10
 *      3.当添加的元素超出了容量10时,会执行grow方法,Object[] 的容量会进行扩容,扩容机制为1.5倍

*/


3.set

  • Set: 无序无重复的集合

  • Set的常见实现类:

  1. HashSet:底层使用HashMap操作数据
  2. TreeSet:底层封装了TreeMap,对元素可以进行排序,树结构 实现 LinkedHashSet:具有自然顺序的操作

3.1  HashSet:

底层就是HashMap

//创建Set对象。测试HashSet
Set<Integer> set1 = new HashSet<>();
for (int i = 0; i < 10; i++) {set1.add((int)(Math.random()*100));
}
System.out.println(set1);   //无序无重复
//[97, 98, 5, 38, 72, 89, 25, 58, 63]

3.2  TreeSet:

底层是TreeMap,排序

//测试TreeSet
Set<Integer> set2 = new TreeSet<>();
for (int i = 0; i < 10; i++) {set2.add((int)(Math.random()*100));
}
System.out.println(set2);   //排序,字符串不能排序
//[20, 22, 29, 42, 60, 62, 64, 72, 74]

3.3  LinkedHashSet:

底层是LinkedHashMap,会按照自然顺序(添加元素的顺序)进行排列

//测试LinkedHashSet:
Set<Integer> set3 = new LinkedHashSet<>();
set3.add(7);
set3.add(4);
set3.add(1);
set3.add(9);
set3.add(3);
System.out.println(set3);   //自然顺序
//[7, 4, 1, 9, 3]

4.集合排序问题

4.1  Collections

  • Collections是为集合提供的工具类

  • 集合排序的方法为sort(List list),作用是对集合中的元素排序

//包装类数值的排序
//list转换成数组,进行排序
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {list.add((int)(Math.random()*100));
}
System.out.println("排序前:"+list);Integer[] integers = list.toArray(new Integer[list.size()]);
Arrays.sort(integers);
System.out.println("排序后:"+ Arrays.toString(integers));Collections.sort(list);   //与上面等价
System.out.println("排序后:"+list);

4.2  对象排序:

对属性值将进行排序

  • 使用Collections方法排序的集合元素必须实现Comparable。

  • 如果元素为对象,那么可以通过实现Comparable的compareTo方法为对象添加比较逻辑

  • 案例-根据学生分数进行排序

编写实体类Student:

package com.hqyj.qpi.collections;public class Student  implements  Comparable{private  String name;private Integer score;public Student(String name, Integer score) {this.name = name;this.score = score;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getScore() {return score;}public void setScore(Integer score) {this.score = score;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", score=" + score +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;if (name != null ? !name.equals(student.name) : student.name != null) return false;return score != null ? score.equals(student.score) : student.score == null;}@Overridepublic int hashCode() {int result = name != null ? name.hashCode() : 0;result = 31 * result + (score != null ? score.hashCode() : 0);return result;}@Overridepublic int compareTo(Object o) {Student s = (Student) o;//向下造型/*从小到大,转换为从大到小从大到小:this.score - s.getScore()从小到大:s.getScore() - this.scorereturn  s.getScore() - this.score ;   //从大到小*/return s.getScore() - this.score;   //从小到大}
}

             编写测试类:常用顺序

List<Student> students = new ArrayList<>();
students.add(new Student("zhangs",90));
students.add(new Student("liu6",70));
students.add(new Student("wang2",89));
students.add(new Student("hus",97));
System.out.println(students.toString());/*
no instance(s) of type variable(s) T exist so that Student conforms to Comparable<? super T>原因:不知道排序排谁解决方法:为Student类提供一套排序规则:Comparable接口为其提供排序规则!源码中解释道:将此对象(Student)与指定对象(T o)进行排序.返回负整数、零、正整数   小于,等于,大于       */
Collections.sort(students);
System.out.println("排序后的Student:"+students);

                临时改变比较规则

/*问题:临时改变比较规则?临时将学生成绩从大到小排序,换成从小到大排列,采用匿名内部类,实现Comparator中的compare方法*/
Collections.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {//从大到小:this.score - s.getScore()//从小到大:s.getScore() - this.score/*o1表示:thiso2表示:T*/return o1.getScore() - o2.getScore();}
});
System.out.println("临时规则比较:"+students);
//临时比较只作用于当前方法,再一次调用时会根据Student中的比较规则

5、Queue

5.1  队列queue

/*
Queue:队列,先进先出*/
public class TestQueue {public static void main(String[] args) {Queue<String> queue = new LinkedList<>();queue.offer("a");queue.offer("b");queue.offer("c");queue.add("d");System.out.println(queue);System.out.println("第一次取出的元素:"+queue.poll());  //取出的是最先进去的元素System.out.println("第一次取出的元素:"+queue.poll());  //取出的是最先进去的元素System.out.println("观察原队列中的元素是否拿出来了:"+queue);   //改变//检索但不删除此队列的头部System.out.println("检索当前队列头部但不删除"+queue.peek());System.out.println("观察原队列中的头元素是否删除了:"+queue);//检索并删除此队列的头部System.out.println("检索当前队列头部并删除"+queue.remove());System.out.println("观察原队列中的头元素是否删除了:"+queue);}}

5.2  双队列Deque

/*** Deque:双端队列*      --offerFirst 从队首进 pollFirst从队首出*      --offerLasst 从队尾进 pollLast从队尾出*      --如果限定只从一端进去一端出,则就是栈!s*/
public class TestDeque {public static void main(String[] args) {//考虑:继承Queue,offer pollDeque<String> deque = new LinkedList<>();deque.offerFirst("a");deque.offerFirst("b");deque.offerFirst("c");deque.offerFirst("d");deque.offerLast("e");deque.offerLast("f");deque.offerLast("g");//注意:如果我们限制从双端队列的一端进取则和栈的存取原理是一样的System.out.println(deque.pollFirst());  //先进后出,同stack(栈)System.out.println(deque.pollLast());   //先进先出,队列Queue}
}

6、Iterator

/*
Iterable:用于遍历接口
iterator:方法的返回值是iterator对象
 */

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10 ; i++) {list.add((int)(Math.random()*100));
}
System.out.println(list);
list.add(20);

6.1  迭代器遍历当前集合

System.out.println("----------------通过Iterator遍历------------------");
//1.获取迭代器对象
Iterator<Integer> iterator = list.iterator();
//2.while循环遍历集合,判断当前集合是否有下一个元素
while (iterator.hasNext()) {
//3.返回需要迭代的对象Integer interge = iterator.next();System.out.print(interge+"  ");if (interge == 20) {//list.remove(interge);  不能使用集合提供的remove方法,否则会报错iterator.remove();}
}
System.out.println();
System.out.println(list);

6.2  增强for循环遍历当前集合

System.out.println("----------------通过for循环遍历------------------");
for (Integer o:list) {System.out.print(o + " ");
}
System.out.println();

6.3 forEach遍历集合

 // 1- 通过匿名内部类
System.out.println("----------------通过forEach方法匿名内部类形式遍历------------------");
list.forEach(new Consumer<Integer>() {@Overridepublic void accept(Integer integer) {System.out.print(integer + " ");}
});
System.out.println();// 2- 通过lambda表达式:重点使用
System.out.println("----------------通过forEach方法lambda形式遍历------------------");
list.forEach(((o)-> System.out.print(o + " ")));// 3- 通过引用方法形式(流式编程)
System.out.println("----------------通过forEach方法引用方法形式遍历------------------");
list.forEach(System.out::print);

7、Map

             Map集合是以(K-V)键值对的形式存储数据,无序,key可以看为Value的索引,且key是唯一的。

7.1  常见实现类

  • - HashMap:底层使用位桶、链表、红黑树实现(红黑树的加入是jdk1.8后,当链表长度超过阈 值(8)时,使用红黑树),大大减少了查找时间
  • - TreeMap: 底层位红黑树实现
  • - LinkedHashMap:按插入的顺序排列。HashMap和双向链表合二为一就是LinkedHashMap
  • - Hashtable:和HashMap存储原理相似,但方法都是synchronized,因此时线程安全的

7.2  map接口方法实现

7.3  常用方法


import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class TestMap {public static void main(String[] args) {//1.创建Map对象Map<String, String> map = new HashMap<>();//2.添加元素:put(k,v)map.put("chongqing","wanzhou");  //键时唯一的map.put("hubei","wuhan");System.out.println(map);//3.containsKey(Object key):如果此映射包含指定键的映射关系,则返回 true。System.out.println("map是否包含hubei这个键:"+map.containsKey("hubei"));//4.isEmpty():是否为空!如果此映射未包含键-值映射关系,则返回 true。System.out.println("map是否为空:"+map.isEmpty());//5.entrySet():返回此映射中包含的映射关系的 set 视图。Set<Map.Entry<String, String>> entries = map.entrySet();for (Map.Entry<String, String> o: entries) {System.out.println(o);}      //遍历System.out.println("映射关系的 set 视图"+entries);//6.get(Object key):返回此映射中映射到指定键的值。System.out.println(map.get("hubei"));//7.hashCode():返回此映射的哈希码值。System.out.println(map.hashCode());//8.keySet():返回此映射中包含的键的 set 视图。System.out.println("包含的键的 set 视图:"+map.keySet());//9.putAll(Map<? extends K,? extends V> t):从指定映射中将所有映射关系复制到此映射中(可选操作)。Map<String, String> map2 = new HashMap<>();map2.putAll(map);System.out.println("map2复制map后为:"+map2);//10.remove(Object key) :删除指定键值map.remove("hubei");System.out.println("检测map是否删除了hubei键值对:"+map);//11.size():返回此映射中的键-值映射关系数。System.out.println("map2中的键值对数量:"+map2.size());//12.values():返回此映射中包含的值的 collection 视图。System.out.println(map2.values());//13.containsValue(Object value):map2.put("china","wanzhou");System.out.println("检测wap中是否包含wanzhou对应的键:"+map2.containsValue("wanzhou"));//14.clear:移除所有元素map.clear();System.out.println(map);}
}

7.4  底层实现原理

/**
 * Map: k-v key是唯一、无序的
 *
 * HashMap:测试这个类的方法
 * 面试:hashmqp的存储原理  put(k,v)   get(k)
 *       1.HashMap实现:数组加链表   jdk1.8后数组+链表------->数组+链表+红黑树
 *          ---底层封装了一个数组:table
 *          ---在实例化HashMap对象时,没有对数组Node初始化,而是给它设置了一个默认的加载因子0.75f
 */

7.5  hashmap的优化

  • 容量(Capacity): 初始容量为16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 最大容量

static final int MAXIMUM_CAPACITY = 1 << 30;
  • 元素个数

size()
  • 默认加载因子:size/capacity的结果值, 如果大于0.75,则重新散列(rehashing)

static final float DEFAULT_LOAD_FACTOR = 0.75f;

优化: 加载因子较小时,查找性能会提高,但是会浪费桶容量 0.75是相对平衡状态 因此通过构造方法指定合理的容量,减少重新散列,提高性能。



二、异常处理机制

1.异常处理

  • 异常的父类为Throwable,有两个派生子类Error和Exception

  1. Error:指Java运行环境出现的错误,一般程序不允许出现Error错误,如:JVM的资源耗尽等
  2. Exception:网络故障、文件损坏、用户输入非法等导致的异常,其子类RuntimeException 尤为重要。
  • 异常又可分为检测型异常和非检测型异常

  1. 检测型异常: 编译代码时,程序指出的必须处理的异常,如:InterruptedException、 IOException等。
  2. 非检测异常:运行时才会出现的异常,如:ArrayIndexOutOfBoundsException、 ClassCastException、NullPointerException等。

2.异常处理方式

  • 异常处理的目的:当异常出现,程序就会终止。交给异常处理代码处理(抛出或抓取),其他代码正 常运行

  • 异常处理的方式有两种,抛出或抓取

**   throws :用于方法参数后,对方法中发生异常的代码进行异常抛出

public void testTryCatch() throws InterruptedException {
Thread.sleep(1000);
}

**  try-catch:抓取异常一一处理或直接处理父类异常

public void testTryCatch() {
try {
Thread.sleep(1000);
InputStream is = new FileInputStream("demo.txt");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
或
public void testTryCatch() {
try {
Thread.sleep(1000);//打断性异常
InputStream is = new FileInputStream("demo.txt");//IO异常
} catch (Exception e) {
e.printStackTrace();
}
}

**   finally块:一般跟在catch之后,表示异常处理块的最终出口,无论有没有抛出异常,都会执行 finally中的代码 通常用来进行资源释放等工作

public void testTryCatch() {
try {
Thread.sleep(1000);
InputStream is = new FileInputStream("demo.txt");
} catch (Exception e) {
e.printStackTrace();
System.out.println("异常出现了,QAQ");
} finally {
System.out.println("资源释放...");
}
}

**  throw关键字:用于程序员主动抛出异常

throw new RuntimeException();//抛出新异常

3.常见的方法

  • printStackTrace():打印异常事件发生时的堆栈信息

  • getMessage():打印有关异常事件的信息。

  • getCause():获取异常出现的原因

4.自定义异常

自定义异常:继承Exception

public class MyException  extends  Exception{
​public MyException(){System.out.println("这是我自己的一个异常");System.out.println("this:"+getMessage());}
​@Overridepublic String toString() {return "this is a new message";}
}
public class TestExceptions {public static void main(String[] args) {
​try {throw  new MyException();} catch (MyException e) {e.printStackTrace();}}
}

5、总结

1.异常的处理方式

--第一种:通过throws在方法后抛出,可以抛出多个不同的异常,可以直接抛出父异常Exception --第二种:通过try—catch—finally,可以抓取所有异常并对其进行处理,finally块中不论是否发送异常都会执行 一般用来关闭资源

2.异常常见类型:

--检测型异常(需要处理):编译不通过 --费检测异常:编译通过,运行时异常

3.处理异常时,可以采用throw关键字自行抛出异常

4.异常的常用方法:

  • e.printStackTrace();打印堆栈信息(错误信息)
  • getCause();获取异常原因
  • getMessage();获取异常原因信息

5.自定义异常:

继承Exception

三、IO流

1、File

  • java.io.File表示文件(目录),操作本地的文件和目录。
  • File对象只能操作文件的基本信息(名称、大小等)。不能对文件内容进行访问。

1.1  创建File对象

相对路劲和绝对路劲

相对路劲:相对于当前书写目录的位置

../demo/a.txt//上级目录的a.txt

绝对路径:指文件在硬盘上真正存在的路径

D:\IdeaProjects\javase

1.2  File构造方法

  •  File:文件路径或文件对象,不描述文件本身!
  •  文件和目录路径名的抽象表示
import java.io.File;public class TestFile {public static void main(String[] args) {//1.构造方法//-- File(String  pathname)File file = new File("D:\\program\\A_hqyj\\ideaprojects\\javase-220302\\java-coreprogramming\\src\\com\\hqyj\\qpi\\io\\TestFile.java");System.out.println(file);      //  抽象表示System.out.println(file.exists());//-- File(String parent,String child)String parent = "D:\\program\\A_hqyj\\ideaprojects\\javase-220302\\java-coreprogramming\\src\\com\\hqyj\\qpi\\io";File file2 = new File(parent, "TestFile.java");System.out.println(file2.exists());//- File(File parent,String child)File parentPath = new File(parent);File file3 = new File(parentPath, "TestFile.java");System.out.println(file3.exists());/*2.问题来了路径如果传入绝对路径很复杂--绝对路径:从盘符开始的路径--相对路径:从Module(子工程)开始的   !!!类路径-".":系统默认路径*/File file4 = new File("java-coreprogramming\\src\\com\\hqyj\\qpi\\io\\TestFile.java");System.out.println(file4.exists());   //相对路径//测试相对路径File file5 = new File("textFile.txt");System.out.println(file5.exists());//系统的默认路径File file6 = new File(".");System.out.println(file6.exists());System.out.println(file6.getAbsolutePath());/*路径分隔符-- 正斜杠 /(建议使用)-- 反斜杠 \\(win独有)*/File file7 = new File("java-coreprogramming/src/com/hqyj/qpi/io/TestFile.java");System.out.println(file7.exists());}}

1.3  常用方法

import java.io.File;
import java.util.Arrays;
import java.util.Date;/**** 常用方法*/
public class TestFile2 {public static void main(String[] args) throws  Exception {//0.准备工作,在io包下创建文件和目录File file = new File("java-coreprogramming/src/com/hqyj/qpi/io/testfile");file.mkdir();System.out.println(file.exists());//创建一个文件名File file2 = new File("java-coreprogramming/src/com/hqyj/qpi/io/testfile/demo.properties");file2.createNewFile();//测试此文件是否可读System.out.println(file2.canRead());//4.测试是不是一个文件或者目录System.out.println(file2.isFile());System.out.println(file2.isDirectory());//5.返回路径或者文件最后一次被修改的时间System.out.println(new Date(file2.lastModified()));//6.获取此抽象文件或者目录的名称System.out.println(file2.getName());//7.判断当前文件是否可写System.out.println(file2.canWrite());//8.获取文件的长度System.out.println(file2.length());//9.创建多级目录File file3 = new File(file, "a");System.out.println(file3.mkdirs());//10.删除一个路径file3.delete();//问题:File对象操作的是文件内容还是文件的基本信息呢? 操作的是文件的基本信息//File 就是该文件路径表示的对象,而不是操作内容本身。//11.打印当前目录下的所有内容File file4 = new File(".");File[] files = file4.listFiles();System.out.println(Arrays.toString(files));String[] list = file4.list();System.out.println(Arrays.toString(list));}
}

2、IO流概述

  • IO:指输入输出

Input:输入,从外界到程序,读取数据

Output:输出,从程序到外界,写出数据

  • 流的分类:

**字节流和字符流

字节流: 传输数据的最小单位是字节

字符流: 传输数据的最小单位是字符

**节点流(低级流)和处理流(高级流或过滤流)

节点流: 可以向一个特定的地方读写数据

处理流: 封装了节点流,通过封装的流的功能调用实现数据读写。

**流的链接: 一个流对象经过其他流的多次包装,成为流的链接。


3、字节流

  • 字节流有两个抽象类: InputStream和OutputStream
  • IS(InputStream)的常用方法
  • OS(OutputStream)的常用方法

3.1 FIS和FOS

         3.1.1 字节流FIS和FOS

  •                FIS: FileInputStream,文件输入流

FileInputStream(File file)

通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中 的 File 对象 file 指定。

FileInputStream(String name)

通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中 的路径名 name 指定。

  •       FOS:FileOutputStream, 文件输出流

FileOutputStream(File file)

创建一个向指定 File 对象表示的文件中写入数据的文件输出流。

FileOutputStream(File file, boolean append)

创建一个向指定 File 对象表示的文件中写入数据的文件输出流。

FileOutputStream(String name)

创建一个向具有指定名称的文件中写入数据的输出文件流。

FileOutputStream(String name, boolean append)

创建一个向具有指定 name 的文件中写入数据的输出文件流。

      3.1.2  FileInputStream的读出与写入

/*** fileInputStream FileOutputSream*** 字节流:凡是以Stream结尾的都是字节流*/
public class TestFISAndFOS2 {public static void main(String[] args) throws  Exception{//读入InputStream inputStream = null;OutputStream outputStream = null;File file = new File("java-coreprogramming/src/com/hqyj/qpi/io/testfile/fis1.txt");try {inputStream= new FileInputStream(file);for (int j= 1; ; j++) {int io = inputStream.read();if (io == -1) {break;}System.out.print((char ) io);}}catch (Exception e){e.printStackTrace();} finally {inputStream.close();}System.out.println();//写出byte[] bytes = new byte[]{'o','u','t'};try {outputStream = new FileOutputStream(file,true);outputStream.write(bytes);} catch (Exception e) {e.printStackTrace();}finally {outputStream.close();}}
}

    3.1.3 文件内容的复制

      此刻需要用到单元测试,以下为单元测试内容

1.测试读写

--单元(每一个方法都是一个单元)测试:

--@Before :同一个java中,执行@Test前必须执行的内容

--@After :同一个java中,执行完@Test后必须执行的内容

--@Test :同一个java中,必须执行@Test的内容

 2.单元测试路径

绝对路径:从盘符开始

相对路径:单元测试编译位置和main函数不同

--main从module开始

--单元测试从Classpath开始(类路径)

-- 可以标记一个普通的文件目录为classpath Make Directory As

测试:


import org.junit.After;
import org.junit.Before;
import org.junit.Test;import java.io.*;/*测试读写--单元(每一个方法都是一个单元)测试:--@Before :同一个java中,执行@Test前必须执行的内容--@After :同一个java中,执行完@Test后必须执行的内容--@Test :同一个java中,必须执行@Test的内容*/
public class TestFISAndFOS3  {//练习通过上面的案例尝试读取文件中所有的字节,并尝试创建OutputStream,向文件中写入内容File file;FileInputStream fis;FileOutputStream fos;//准备资源@Beforepublic void testBefore(){/*单元测试:绝对路径:从盘符开始相对路径:单元测试编译位置和main函数不同--main从module开始--单元测试从Classpath开始(类路径)可以标记一个普通的文件目录为classpathMake Directory As*/String path  = "resource\\fis1.txt";file = new File(path);}//写@Testpublic void testWirte() throws  Exception{/*构造方法中:boolean append参数如果为true表示追加*/fos = new FileOutputStream(file,true);fos.write(65);}//读@Testpublic  void  testRead() throws  Exception{fis = new FileInputStream(file);int i = -1;while ((i = fis.read()) != -1) {System.out.println((char)i);}}//byte[]写@Testpublic void  testWriteByts() throws  Exception{fos = new FileOutputStream(file,true);byte[] bytes1 = new byte[]{65,66,67,68,69};fos.write(bytes1);}//byte[]读@Testpublic void  testReadByts() throws  Exception{fis = new FileInputStream(file);byte[] bytes2 = new byte[100];int i = fis.read(bytes2);System.out.println(new String(bytes2));}//测试使用fis和fos实现文件的复制 将fis1.txt的内容复制到fis1_copy.txt,采用byte实现//byte复制@Testpublic void  testcopy() throws  Exception {//定义文件路径fis = new FileInputStream(file);//如果没有文件,会自动生成文件fos = new FileOutputStream("resource\\fis1_copy.txt");//定义数组byte[] bytes = new byte[1024];//读fis.read(bytes);//写fos.write(bytes);/*int i = -1;while(i = fis.read(bytes) != -1) {fos.write(bytes);}*/}//关闭资源@Afterpublic void testAfter() throws  Exception{if (fos != null){fos.close();}if (fis != null) {fis.close();}}}

3.2 BIS和BOS

  • 缓冲区的优势:减少了读写次数
  • flush() : 清空缓冲区,将缓冲区中的数据全部写出
  • BIS:BufferedInputStream 缓冲输入流

BufferedInputStream(InputStream in)

创建 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 BufferedInputStream(InputStream in, int size)

创建具有指定缓冲区大小的 BufferedInputStream,并保存其参数,即输入流 in,以 便将来使用。

  • BOS:BufferedOutputStream 缓冲输出流

BufferedOutputStream(OutputStream out)

创建一个新的缓冲输出流,以将数据写入指定的基础输出流。 BufferedOutputStream(OutputStream out, int size)

创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的基础输出流。

3.2.1  缓冲输入输出流

public class TestBISAndBOS {File file;FileInputStream fis;FileOutputStream fos;BufferedInputStream bis;BufferedOutputStream bos;@Beforepublic  void  createFile() throws Exception {String s = "resource\\bis1.txt";file = new File(s);}//写出@Testpublic  void write() throws  Exception {//先去创建低级流fos = new FileOutputStream(file);bos = new BufferedOutputStream(fos,10); //以低级流fos作为载体//写出内容bos.write("I am a boy".getBytes());bos.flush();//立即写出}//读入@Testpublic  void read() throws  Exception {fis = new FileInputStream(file);bis = new BufferedInputStream(fis);byte[] bytes = new byte[1024];//读入bis.read(bytes);System.out.println(new String(bytes));}@Afterpublic  void  close() throws  Exception {//先关闭高级流,在关闭低级流if (bos != null)bos.close();if (bis != null)bis.close();if (fos != null)fos.close();if (fis != null)fis.close();}}

3.2.2复制文件的内容

public class TestBISAndBOS {File file;FileInputStream fis;FileOutputStream fos;BufferedInputStream bis;BufferedOutputStream bos;@Beforepublic  void  createFile() throws Exception {String s = "resource\\bis1.txt";file = new File(s);}//写出@Testpublic  void write() throws  Exception {//先去创建低级流fos = new FileOutputStream(file);bos = new BufferedOutputStream(fos,10); //以低级流fos作为载体//写出内容bos.write("I am a boy".getBytes());bos.flush();//立即写出}//读入@Testpublic  void read() throws  Exception {fis = new FileInputStream(file);bis = new BufferedInputStream(fis);byte[] bytes = new byte[1024];//读入bis.read(bytes);System.out.println(new String(bytes));}//复制文件内容//练习:使用bis和bos实现文件的复制,bis_copy.txt,参考fis和fos的方法@Testpublic void testcopy() throws Exception {fis = new FileInputStream(file);bis = new BufferedInputStream(fis);fos = new FileOutputStream("resource\\bis_copy.txt");bos = new BufferedOutputStream(fos);byte[] b = new byte[1024];int i = -1;while (( i = bis.read(b)) != -1) {bos.write(b);}}@Afterpublic  void  close() throws  Exception {//先关闭高级流,在关闭低级流if (bos != null)bos.close();if (bis != null)bis.close();if (fos != null)fos.close();if (fis != null)fis.close();}}

4、对象流

4.1 序列化和反序列化

  • 序列化:对象转化为字节序列
  • 反序列换:字节序列转化为对象

4.2 OIS和OOS

  • OIS:ObjectInputStream,对象输入流

ObjectInputStream(InputStream in)

创建从指定 InputStream 读取的 ObjectInputStream。

  • OOS:ObjectOutputStream,对象输出流

ObjectOutputStream(OutputStream out)

创建写入指定 OutputStream 的 ObjectOutputStream。

4.3常用方法

                序列化

void writeObject(Object o);

                反序列化

Object readObject();

  • 对象序列化的注意事项:
  1. 必须实现Serializable接口,该接口没有方法
  2. 建议为对象添加类版本号,避免版本升级后造成的不兼容问题
  3. transient关键字:修饰属性可以对没必要的属性值忽略,从而对对象序列化后的字节序列”瘦 身“

4.4实现

                实体类:


import java.io.Serializable;public class Person implements Serializable {private static final long serialVersionUID = -516804566657580250L;private String name;private transient int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;if (age != person.age) return false;return name != null ? name.equals(person.name) : person.name == null;}@Overridepublic int hashCode() {int result = name != null ? name.hashCode() : 0;result = 31 * result + age;return result;}
}

                测试:

import org.junit.After;
import org.junit.Before;
import org.junit.Test;import java.io.*;public class TestOisAndOos {/*1.怎么对实体类进行序列化和反序列化2.创建ois和oos*/File file;FileOutputStream fos;FileInputStream fis;BufferedOutputStream bos;BufferedInputStream bis;ObjectInputStream ois;ObjectOutputStream oos;@Beforepublic  void  filepath() throws Exception {String str = "resource//ois.txt";file = new File(str);}@Test   //序列化public  void testWrite() throws  Exception {fos = new  FileOutputStream(file);oos = new ObjectOutputStream(fos);Person person = new Person("zhagns", 16);oos.writeObject(person);}@Test   //反序列化public void testRead() throws  Exception {fis = new FileInputStream(file);bis = new BufferedInputStream(fis);ois = new ObjectInputStream(bis);Object o = ois.readObject();System.out.println(o);}@Afterpublic void close() throws  Exception {if (ois != null) ois.close();if (oos != null) oos.close();if (bis != null) bis.close();if (bos != null) bos.close();if (fis != null) fis.close();if (fos != null) fos.close();}
}

序列化多个对象:

import org.junit.After;
import org.junit.Before;
import org.junit.Test;import java.io.*;public class TestOisAndOos2 {/*序列化多个对象:*/File file;FileOutputStream fos;FileInputStream fis;BufferedOutputStream bos;BufferedInputStream bis;ObjectInputStream ois;ObjectOutputStream oos;@Beforepublic  void  filepath() throws Exception {String str = "resource//ois.txt";file = new File(str);}@Test   //序列化public  void testWrite() throws  Exception {fos = new  FileOutputStream(file);oos = new ObjectOutputStream(fos);Person[] persons = new Person[2];persons[0] = new Person("li4",25);persons[1] = new Person("li5",32);for (int i = 0; i < persons.length; i++) {oos.writeObject(persons[i]);}}@Test   //反序列化public void testRead() throws  Exception {fis = new FileInputStream(file);bis = new BufferedInputStream(fis);ois = new ObjectInputStream(bis);Object[] objects = new Object[2];for (int i = 0; i < objects.length; i++) {Object o = ois.readObject();System.out.println(o);}}@Afterpublic void close() throws  Exception {if (ois != null) ois.close();if (oos != null) oos.close();if (bis != null) bis.close();if (bos != null) bos.close();if (fis != null) fis.close();if (fos != null) fos.close();}
}

总结:

1.对象序列化时,序列化对象类型必须实现Serializable,否则会报错
2.对象的实现序列化和返序列化- 序列化:OOS的writeObject方法- 反序列化:OIS的readObject方法
3.transient关键字:在序列化时,不考虑该关键字Person person = new Person("zhagns", 0);private transient int age;序列化和反序列化都不考虑age属性
4.serialVersionUID(序列化号)-功能:解决不兼容   序列化一个对象到内存中,反序列化时值发生变化当没有UID时,会直接报错有UID时会返回空
5.多个对象的序列化过程,可以采用数组/集合的形式去完成

5、字符流

  • 字符流有两个抽象类 Reader和Writer
  • 字符流的底层是字节流,每次处理一个字符(char)

  • 常见字符集:
  1. UTF-8:是针对Unicode的一种可变长度字符编码。
  2. GBK:汉字编码字符集。
  • Reader常用方法 Writer常用方法

5.1 ISR和OSW

  • ISR:InputStreamReader,字符输入流

InputStreamReader(InputStream in)

创建一个使用默认字符集的 InputStreamReader。 InputStreamReader(InputStream in, String charsetName)

创建使用指定字符集的 InputStreamReader。

  • OSW:OutputStreamWriter,字符输出流

OutputStreamWriter(OutputStream out)

创建使用默认字符编码的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, String charsetName)

创建使用指定字符集的 OutputStreamWriter。

测试:


import org.junit.After;
import org.junit.Before;
import org.junit.Test;import java.io.*;/*** 字符流,指的是按照字符*   以Reader和Writer结尾的*/
public class TestRSRAndOSW {/*通过ISR和OSW实现文件的复制*/File file;FileInputStream fis;FileOutputStream fos;InputStreamReader isr;OutputStreamWriter osw;@Beforepublic void bigin() throws  Exception {file = new File("resource//isr.txt");}@Testpublic void write() throws  Exception {fos = new FileOutputStream(file);osw = new OutputStreamWriter(fos);osw.write("I love java");}@Testpublic void read() throws Exception {fis = new FileInputStream(file);isr = new InputStreamReader(fis);char[] chars = new char[100];isr.read(chars);System.out.println(chars);}@Testpublic void copyFile() throws Exception {fis = new FileInputStream(file);isr = new InputStreamReader(fis);fos = new FileOutputStream("resource//isr_copy.txt");osw = new OutputStreamWriter(fos);char[] chars = new char[100];int i = -1;while ((i = isr.read(chars) ) != -1) {osw.write(chars);}}@Afterpublic void close() throws  Exception {if (isr != null) isr.close();if (osw != null) osw.close();}
}

5.2 PW和BR

  • PW:PrintWriter,输出打印流

--构造方法

PrintWriter(File file)

使用指定文件创建不具有自动行刷新的新 PrintWriter。

PrintWriter(File file, String csn)

创建具有指定文件和字符集且不带自动刷行新的新 PrintWriter。 PrintWriter(OutputStream out)

根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。 PrintWriter(OutputStream out, boolean autoFlush)

通过现有的 OutputStream 创建新的 PrintWriter。

PrintWriter(String fileName)

创建具有指定文件名称且不带自动行刷新的新 PrintWriter。

PrintWriter(String fileName, String csn)

创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter。

PrintWriter(Writer out)

创建不带自动行刷新的新 PrintWriter。

PrintWriter(Writer out, boolean autoFlush)

创建新 PrintWriter。

--常用方法

PW提供了丰富的print方法和println方法的重载方法

自动行刷新: 从本地读取一个文件,再写回去

 String path = "src\\com\\hqyj\\qpi\\io\\pwandbr\\pw.txt";@Testpublic  void write() throws  Exception  {PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(path)), true);pw.write("大家好!");pw.write("23");pw.print("456");pw.println("789");pw.close();}
  • BR:BufferedReader:缓冲字符输入流

-- 构造方法

BufferedReader(Reader in)

创建一个使用默认大小输入缓冲区的缓冲字符输入流。

BufferedReader(Reader in, int sz)

创建一个使用指定大小输入缓冲区的缓冲字符输入流。

-- 常用方法 String readLine()

连续读取一行字符串,直到读取到换行符为止,返回的字符串中不包含该换行符

测试:

String path = "src\\com\\hqyj\\qpi\\io\\pwandbr\\pw.txt";
@Testpublic void read() throws  Exception {BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
//        char[] chars = new char[1024];
//        int i = -1;
//        while ((i = br.read(chars)) != -1) {
//            System.out.println(new String(chars));
//        }String s = br.readLine();   //读取行System.out.println(s);br.close();   //必须关闭最上层的流}

6、练习

6.1输入一个字符串,统计出现的字符个数,并写入文件中,文件名为count.txt


import java.io.*;
import java.util.Scanner;
import java.util.TreeMap;public class homeWork {//测试public static void main(String[] args) throws  Exception {//a.提示信息System.out.println("请输入一个字符串:");//b.输入字符串Scanner scanner = new Scanner(System.in);String str = scanner.nextLine();//c.统计个数homeWork hw = new homeWork();String s = hw.wordCount(str);//d.存储在文件中String path = "D:\\program\\A_hqyj\\ideaprojects\\javase-220302\\java-coreprogramming\\resource\\count.txt";hw.intoFile(path,s);System.out.println("结果已生成,查看count.txt");}//统计字符个数public String wordCount(String inputStr) {//a.对字符串进行处理char[] chars = inputStr.toCharArray();//b.选择数据结构TreeMap<Character, Integer> maps = new TreeMap<>();//c.遍历for (int i = 0; i < chars.length; i++) {//d.将数据存储进map中maps.put(chars[i],maps.containsKey(chars[i])?maps.get(chars[i]) + 1 : 1);}return  maps.toString();}//将结果写进结果中public void intoFile(String path,String s) throws  Exception{OutputStreamWriter osw = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(new File(path))));osw.write(s);   //写入到缓冲区了,没有写入文件——————>1.flush  2.关闭流if (osw != null)osw.close();}}


四、多线程

1.线程和进程

1.1进程和线程概述

进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和 调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基 本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组 织形式的描述,进程是程序的实体。

 线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是 进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个 线程,每条线程并行执行不同的任务。

1.2 并发和并行

        并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多 个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速 交替的执行。

        并行:真正意义的同时发生


2.线程的生命周期

 线程的5种状态

起始:当使用new关键字创建线程对象时

就绪:调用start方法启动线程时,指能运行但还没有运行的过程,会等待CPU为其分配时间 片。

运行:执行run方法时

阻塞:当线程中断、主动出让、等待、睡眠时,会重新进入就绪状态

结束:指run方法执行完毕时


3.线程创建

3.1继承Thread类

    通过继承Thread类重写run()方法实现


/*** 创建方式:继承Thread类*/
public class TestThread1 extends  Thread{//重写父类的run方法,编写自己定义的线程的执行体@Overridepublic void run() {    //3.run 运行状态System.out.println("t1");//打印当前线程的名称for (int i = 0; i < 100; i++) {System.out.println(this.getName()+"->"+i);}/*try {sleep(2);   //4.阻塞状态} catch (InterruptedException e) {e.printStackTrace();}*/}   //5.结束状态public static void main(String[] args) {TestThread1 t1 = new TestThread1();  //1.new 起始状态t1.start();   //2.start  就绪状态(没有运行,等待CPU的时间片)System.out.println(Thread.currentThread().getName());//1.测试创建多条线程new TestThread1().start();new TestThread1().start();new TestThread1().start();}
}

3.2实现Runnable接口

        通过实现Runnable接口重写run()方法

        分为三种方式: 

                1.常规的Runnable接口

                2.匿名内部类

                3.lambda表达式

/*** runnable接口实现*/
public class TestThread3 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " t");}public static void main(String[] args) {//实现Runnable接口,线程创建的三种方法//1.常规写法,构造方法中一般传入实现Runnable接口的实现类Thread t = new Thread(new TestThread3());t.start();//2.匿名内部类形式new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " t1");}}).start();//3.lambda表达式 -----前提:必须为接口,接口中只能有一个方法new Thread(() -> System.out.println(Thread.currentThread().getName() + " t3")).start();}
}

3.3继承Callable接口

       实现Callable接口,可以定义返回指定的泛型,依赖FutuerTask类

       同样也是有三种方式:

                1.常规的Callable接口

                2.匿名内部类

                3.lambda表达式

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;/*** 问题:实现Callable接口,泛型*      --能在方法中返回一个返回值,且这个返回值存储在FutureTask*/
public class TestThread4 implements Callable<Integer> {@Override   //等价于run方法public Integer call() throws Exception {Integer sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println(Thread.currentThread().getName());return sum;}public static void main(String[] args) throws  Exception{FutureTask<Integer> task = new FutureTask<Integer>(new TestThread4());new Thread(task).start();  //绑定System.out.println(task.get());//2.匿名内部类new Thread(new FutureTask<Integer>(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {Integer sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println(Thread.currentThread().getName());return sum;}})).start();//3.lambda表达式FutureTask<Integer> task1 = new FutureTask<>(() -> {Integer sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println(Thread.currentThread().getName());return sum;});new Thread(task1).start();System.out.println(task1.get());}}

3.4线程池的创建

        工厂类Executors

常用方法:

static ExecutorService newCachedThreadPool()

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它 们。 static ExecutorService newFixedThreadPool(int nThreads)

创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。

static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

static ExecutorService newSingleThreadExecutor()

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;/*** 创建方式4:线程池   Executors:工具类,提供了很多的线程**/
public class TestThread5  {public static void main(String[] args) {//创建一个线程池,有3个线程ExecutorService pool = Executors.newFixedThreadPool(3);for (int i = 0; i < 100; i++) {pool.execute( () -> System.out.println(Thread.currentThread().getName()) );//excute:相当于start}pool.shutdown();   //关闭线程池//Callable和Runable有什么关联/*线程池可以把Runnable交给Callable去执行*/}}

4.线程基本API

4.1线程信息获取

/*** 测试线程的常用方法*/
public class TestThreadMethod implements  Runnable{
​public static void main(String[] args) {
​Thread t = new Thread(new TestThreadMethod(),"李逵");   //也可以通过构造方法设置名称t.start();
​t.setName("张飞");   //设置线程的名称
​t.setPriority(10);   //设置线程的优先级
​System.out.println("main线程的优先级" + Thread.currentThread().getPriority());
​}
​
​@Overridepublic void run() {
​Thread thread = Thread.currentThread();//1.获取当前线程的对象System.out.println("当前线程对象:" + thread);
​//2.获取当前线程的对象NameSystem.out.println("当前线程对象Name:" + thread.getName());
​//3.获取当前线程的对象IDSystem.out.println("当前线程对象ID:" + thread.getId());
​//4.获取当前线程组的名称System.out.println("当前线程组的名称:" + thread.getThreadGroup().getName());
​
​//5.获取当前线程的优先级System.out.println("当前线程的优先级:" + thread.getPriority());
​//6.获取当前线程的状态System.out.println("当前线程的状态:" + thread.getState());
​//7.测试当前线程是否活动System.out.println("当前线程活动吗:" + thread.isAlive());
​//8.测试当前线程是否中断System.out.println("当前线程是否中断:" + thread.isInterrupted());}
}

4.2守护线程的运用

/***      守护线程指的是:当前程序中除了守护线程外,没有其他线程了,这时候守护线程会自动停止*          --  常见的守护线程是   gc线程(垃圾回收线程)*/
public class TestfDaemon {
​/*测试守护线程--main线程暂停10秒,10秒后结束程序--守护线程t,让其永久运行,且每0.5秒打印一个字符串--观察当main线程停止时,守护线程会不会自动停止*/public static void main(String[] args) throws InterruptedException {Thread t =  new Thread(() -> {while (true){System.out.println(Thread.currentThread().getName() + "Daemon running!");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}},"张飞");//设置当前t线程为守护线程t.setDaemon(true);t.start();
​//main线程睡眠10秒System.out.println("main线程睡眠10秒");Thread.sleep(10000);}
​
​
}

4.3线程常用方法

  • sleep方法

static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
public static void main(String[] args) throws InterruptedException {Thread t =  new Thread(() -> {while (true){System.out.println(Thread.currentThread().getName() + "Daemon running!");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}},"张飞");
  1. sleep睡眠完成后进去就绪状态
  2. 需要处理InterruptedException
  • yield方法

static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
  1. 使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。 但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选 中。
  2. yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状 态转到可运行状态,但有可能没有效果。
  • join方法

void join() 等待该线程终止。
/*** join等待线程运行完毕** 总结:*      -join可以实现线程间的通信,让某一线程等待*      -join可以给一个等待时间*      -join底层是OBject的wait方法**      --线程A和B*         -如果让A执行完毕后再执行B,则在B中调用A的join*         -如果让B执行完毕后再执行A,则在A中调用B的join*/
public class TestJoin {
​public static void main(String[] args) throws InterruptedException {
​Thread main = Thread.currentThread();
​Thread t  = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5000; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);try {main.join();} catch (InterruptedException e) {e.printStackTrace();}}}},"z3");t.start();for (int i = 0; i < 200 ; i++) {t.join(500);    //等待t线程完毕后才能运行System.out.println(Thread.currentThread().getName() + ":" +i);}}
}

5.线程同步

  • 线程同步:同一个资源,多个人想使用,解决方法:排队

  • 多个需要同时访问 此对象的线程进入这个对象的等待池 形成队列, 等待前面线程使用完毕 , 下一个 线 程再使用

  • 每个对象都有把锁,当获取对象时,独占资源,其他线程必须等待,使用结束后才释放

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

5.1同步方法

synchronized修饰一个方法,表示该方法有某一线程运行时,其他线程进不来

  • 问题:对一个方法加锁,就要承担这个方法的其他不会发生线程问题的代码执行效率问题,因此根据情况选择

  • 可以保证数据的正确性,但当一个线程进入该方法时,该线程就获得了此锁,其他线程必须等 待该线程执行完方法释放锁。

  • 当这个方法比较庞大时,而要加锁的代码(修改)只有一部分,其他代码(只读)也被锁住,而造 成效率问题。

  • 考虑使用同步代码块解决问题

@Override
public synchronized  void run() {/*方法1:同步方法      */while(flag){buy();}}

​5.2.同步代码块

  • Obj:同步监视器(锁)

  • Obj 可以是任何对象 , 但是推荐使用共享资源作为同步监视器

  • 同步方法中无需指定同步监视器 , 因为同步方法的同步监视器就是this , 就是 这个对象本身

  • 括号中需要传入一个对象时,也叫作这个对象可以是任何对象,也叫作同步监视器

  • 就相当一把锁,当一个线程在进入代码块时,获取锁,其他线程获取不到锁,因此进不去

  • 当执行完时,释放锁

  • 同步代码块是指定锁住固定的东西,在方法里其他的内容不受影响。

@Override
public  void run() {*/方法2:同步代码块  */while (flag) {synchronized(this) {buy();   //会发生线程同步问题}}

5.3死锁

  • 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释 放的资源而处于的一种永久等待状态。

  • 死锁的四个必要条件:

  1. 互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。

  2. 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。

  3. 非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。

  4. 循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进 程正占用的资源。

  • 解决死锁:如果打破上述任何一个条件,便可让死锁消失。

package com.hqyj.qpi.threads;
​
public class DeadLock {public static String lockA  = "objectA";public static String lockB  = "objectB";public static String lockC  = "objectB";public static String lockD  = "objectB";
​
​
​public static void main(String[] args) {new Thread(new LockA()).start();new Thread(new LockB()).start();}
​
​
}
class LockA implements Runnable {
​@Overridepublic void run() {System.out.println("线程A开始运行了");try {while (true) {synchronized (DeadLock.lockA) {System.out.println("获取了A锁");Thread.sleep(3000);synchronized (DeadLock.lockB) {System.out.println("获取了B锁");Thread.sleep(3000);   //如果线程A拿到了B锁,则不释放}}}} catch (InterruptedException e) {e.printStackTrace();}}
}
class LockB implements Runnable {
​@Overridepublic void run() {System.out.println("B线程开始运行了");try {while (true) {synchronized (DeadLock.lockB) {System.out.println("获取了B锁");   Thread.sleep(3000);synchronized (DeadLock.lockA) {System.out.println("获取了A锁");Thread.sleep(3000);}}}} catch (InterruptedException e) {e.printStackTrace();}}
}

解决方法:将B中获取线程改为另外的线程:

5.4.lock锁

  • lock接口的实现ReentrantLock,可以获取锁对象

  • 通过显示定义同步锁对象(Lock)来实现同步

  • ReentrantLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,在实现线程 安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

  • 通过定义lock方法和Runlock方法实现加锁和关锁,作用和同步代码块同样

  • 死锁:互斥锁

//第三种方式:lock锁private ReentrantLock lock = new ReentrantLock();
​
​@Overridepublic  void run() {//方法3:lock锁try {lock.lock();buy();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();   //关锁}}}
​
  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了 作用域自 动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展 性(提供 更多的子类)

  • 重点知晓:

  1. 什么是死锁

  2. 同步代码块和同步方法的区别?

6.线程通信

  • 生产者和消费者模型

  • 对于生产者 , 没有生产产品之前 , 要通知消费者等待 。 而生产了产品之后 , 又需要马上通知消费者 消费

  • 对于消费者 , 在消费之后 , 要通知生产者已经结束消费 , 需要生产新的产品 以供消费

6.1 线程通信API

void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的所有线程。 void wait() 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。 void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过 指定的时间量。

注意:

  • 调用wait和notify线程必须拥有相同的对象锁

  • 该API中的方法必须在同步方法或同步代码块中。

/*** wait* notify* 注意:*     1.在使用wait和notify所在的代码必须是同步方法或同步代码块*     2必须使用同一个对象锁*/
public class TestCommunication {//场景:创建两个线程,分别输出3句代码//A . B 交替执行public static void main(String[] args) {Object obj = new Object();   //同步监视器Thread A = new Thread(() -> {synchronized (obj) {System.out.println("A 1");try {obj.wait();    //阻塞暂停运行,让就绪的线程运行} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 2");System.out.println("A 3");}} );Thread B = new Thread(() -> {synchronized (obj) {System.out.println("B 1");System.out.println("B 2");obj.notifyAll();   //运行完毕,唤醒被阻塞的线程System.out.println("B 3");}} );A.start();B.start();}
}

第五章、反射

1.反射的概述

1.1 反射作用

  • java文件在运行时会创建一个Class对象

  • Class对象存储在方法区,存储类的所有信息,是类的一面镜子

  • 可以通过Class对象获取类的所有信息,反射在手,天下我有

1.2 反射是什么

  • 动态加载和静态加载

  1. 动态加载:发生在运行期
  2. 静态加载:发生在编译期
  • 概念:

  1. 《Using Java Reflection》文章: 反射是一个可以获取Java类、属性的工具,因为他是动态加 载的。
  2. 《A Button is a Bean》中提到:反射是为了能把一个类的属性可视化展示给用户。
  3. 简单说: 反射是可以动态获取一个类的所有信息及动态调用类中定义的方法的机制。

1.3 反射的应用

  • 反射可以使Java具备动态性语言的特征,很强大!

  • 反射是框架的灵魂! 反射可以使我们很流畅的阅读源码!

  • 反射可以让我变更强,自己编写框架!

2.反射的创建三种方式

2.1类名.class

//1类名.Class
@Test
public void  testCteatClass1() {Class class1 = Person.class;System.out.println(class1);   //class com.hqyj.qpi.reflect.Person
​
}

2.2Class.forName()

//2 class.forName(),抛出异常
@Test
public void  testCteatClass2() throws Exception {  //类全名Class class2 = Class.forName("com.hqyj.qpi.reflect.Person");System.out.println(class2);  //class com.hqyj.qpi.reflect.Person
}

2.3 对象.getClass()

//3.有了对象后,使用Object类的getClass方法
@Test
public void  testCteatClass3() throws Exception {Person ps = new Person("z3",23);Class class3 = ps.getClass();System.out.println(class3);  //class com.hqyj.qpi.reflect.PersonField[] fields = class3.getFields();System.out.println(Arrays.toString(fields));
}

2.4 三种创建方式的区别

//测试三个对象是不是同一个对象
@Test
public void testObjectIs() throws Exception {Class c1 = Person.class;Class<?> c2 = Class.forName("com.hqyj.qpi.reflect.Person");Person person = new Person();Class c3 = person.getClass();System.out.println(c1 == c2);System.out.println(c3 == c2);
}
​
//测试三种创建方式,创建时的不同,区别
@Test
public void testCreatePeriod() throws  Exception{
​//方法二、静态加载Class c2= Class.forName("com.hqyj.qpi.reflect.Person");System.out.println("---------------------------------------");Person person = new Person();Class c3 = person.getClass();System.out.println("---------------------------------------");
​Class c1= Person.class;  //方法一,
​
}
创建方式: 1.类名.class: 类加载前(还没有加载的内存前),不做类的初始化工作 2.Class.forname() :进内存,做类的静态初始化工作,需要抛出异常 3.XX.getClass():进内存对类做静态、非静态初始化; 这三种加载方式,如果创建的Class对象的顺序不同,其结果也就不同,所有内容只加载一次 不论哪种创建方式,产生的对象是唯一的

3.类的加载

  • 加载:

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构, 然后生成一个代表这个类的java.lang.Class对象.

  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

  1. 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  2. 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方 法区中进行分配。
  3. 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化:

  1. 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作 和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造 器)。
  2. 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

4.获取类中的信息

4.1获取Class中的属性

//获取class属性
​
public class TestReflectField {@Testpublic void testField() throws  Exception {
​Class cls = Person.class;Person person = new Person();System.out.println(cls.newInstance());
​//获取多个对象Field[] fields = cls.getFields();System.out.println(Arrays.toString(fields));
​//获取单个属性   (暴力获取)Field name = cls.getDeclaredField("name");name.setAccessible(true);name.set(person,"z3");System.out.println(person.getName());
​}
}

4.2获取Class中的方法

//测试class的方法
public class TestMethod {@Testpublic void testMethod() throws  Exception {Class cls = Class.forName("com.hqyj.qpi.reflect.Person");
​//获取所有的方法Method[] methods = cls.getMethods();System.out.println(Arrays.toString(methods));
​//获取单个方法Method show = cls.getMethod("setName", String.class);Person person = new Person();show.invoke(person,"l6");System.out.println(person.getName());
​//访问私有的方法时,使用暴力获取Method setName = cls.getDeclaredMethod("setName", String.class);setName.setAccessible(true);setName.invoke(person,"p6");System.out.println(person.getName());
​}
}

第六章、设计模式

1.设计模式概述及分类

设计模式分为三大类:

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模 式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、 命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

2.单例模式

​
/*
单例模式:创建的对象时唯一的
工厂 spring beanfactory ReflectFavtory   抽象工厂、
代理模式 spring 美团外卖
​*/
public class TestSingleton {public static void main(String[] args) {Dog dog1 = Dog.newInstance();Dog dog2 = Dog.newInstance();Dog dog3 = Dog.newInstance();Dog dog4 = Dog.newInstance();System.out.println(dog1 == dog2);}
}
class Dog{
​private static Dog dog = new Dog();
​//私有化构造方法private Dog() {}
​public static  Dog newInstance() {return dog;}
​
}

java核心编程(集合、io、反射等)相关推荐

  1. 【Java网络编程与IO流】Java之Java Servlet详解

    Java网络编程与IO流目录: [Java网络编程与IO流]Java中IO流分为几种?字符流.字节流.缓冲流.输入流.输出流.节点流.处理流 [Java网络编程与IO流]计算机网络常见面试题高频核心考 ...

  2. Java核心编程总结(五、线程池与死锁),淘汰了80%的Java面试者

    | Java核心编程总结(八.IO输入输出流)

  3. 【Java网络编程与IO流】Java中IO流分为几种?字符流、字节流、缓冲流、输入流、输出流、节点流、处理流

    Java网络编程与IO流目录: [Java网络编程与IO流]Java中IO流分为几种?字符流.字节流.缓冲流.输入流.输出流.节点流.处理流 [Java网络编程与IO流]计算机网络常见面试题高频核心考 ...

  4. Java是动态语言吗?从《Java核心编程》探索真知

    目录 一.Java是动态语言吗? 1.动态语言 2.静态类型 3.<Java核心编程>中探索~~为什么Java可以称之为"准动态语言"? 二.了解ClassLoader ...

  5. Java 核心编程技术干货

    Java 基础篇 Java 多线程篇 Java JVM篇 Java 进阶篇 Java 新特性篇 Java 工具类篇 Java 综合篇 Java基础篇 恕我直言,在座的各位根本写不好Java! 8张图带 ...

  6. Java核心编程随笔

    1.XML 是一种描述数据的方式.可以使用任何一种程序设计 语言处理 XML 数据,而 Java API 对 XML 处理提供了很好的支持. 2.HTML 是一种描述网页结构的方式.除了用于在网页上放 ...

  7. Java核心编程总结(十、反射),linux技术支持

    1.基础回顾+面试 =========================================================================== 1.1单元测试 什么是单元测 ...

  8. JAVA核心编程之集合

    1. java集合框架 Java集合框架提供了一套性能优良.使用方便的接口和类,它们位于java.util包中 Collection接口存储一组不唯一,无序的对象 List接口存储一组不唯一,有序(插 ...

  9. java核心编程视频教学

    前言 Java作为最全面的语言,国内开发者也是最多的,Java综合起来各方面都不错,在大部分场景下是一种稳健的技术选择.加上近年来安卓的推动,目前也是最流行的一种语言. 现在Java的就业市场看起来还 ...

最新文章

  1. python 实用程序代码_【转】python常用工具代码
  2. NSProxy的理解和使用
  3. python 读取音频文件(mp3,wav)时间的两种方法
  4. 网络爬虫框架cetty的实现
  5. elasticsearch 数据类型_基于 MySQL Binlog 的 Elasticsearch 数据同步实践
  6. 晨读,难道只是为了完成任务而读的吗?
  7. C++中如何读取一个数的位数_求1000以内的水仙花数
  8. 正则判断windows文件路径是否正确
  9. 【Computer Organization笔记12】流水线技术概述
  10. 学习了1!+...10!
  11. 发那科机器人示教器键盘_不限 发那科机器人示教器触摸屏急停按键失效维修...
  12. php 读取 日文文件名,PHP文件上传-处理阿拉伯文/中文/日文文件名
  13. 前端实现3D魔方旋转特效
  14. 电商平台日志分析系统(大数据) 上(不完整-版本不对应)
  15. java拆箱 装箱 一篇文章就够了
  16. 电脑如何使用硕鼠批量下载哔哩哔哩视频
  17. mininet和ryu简单实现自定义topo
  18. 去除WordPress网站链接中“category”的四种方法
  19. 【已解决】导入tensorflow报错/python已停止工作/The kernel appears to have died
  20. python输入三角形三条边长_python问题:输入三角形的三条边a,b,c,判断此三边是否可以构成三角形。等边、等腰、直角三角形?...

热门文章

  1. Python 创建一个二维列表
  2. 华为服务器(鲲鹏)aarch64/arm64架构下编译nmap RPM包
  3. 荣耀战魂冥界回归服务器维护,荣耀战魂万圣节活动冥界的回归玩法及奖励介绍...
  4. 259高校毕业设计选题
  5. 关于免流搭建教程免流服务内容
  6. java乘法逆元与除法取模,逆元
  7. c#中cookies的存取操作
  8. Python贪心算法解决收银员找零问题
  9. 计算机三级可以入东莞户口吗,初中家长注意,非东莞户籍在东莞读高中,必须满足3个条件!...
  10. 徐瑞华/王峰教授团队建立我国千人肠癌基因组新分型