1.Lambda表达式
Integer [] nums = {2,1,4,3,5,7,6,8,9};
// 方法一,使用匿名内部类
Arrays.sort(nums, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
// 方法二,使用lambda表达式
Arrays.sort(nums,(a,b)->{
return a-b;
});
// 方法三
Arrays.sort(nums,((o1, o2) -> o1-o2));
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i]+" ");
}
2.集合进阶
单列集合(Collection),双列集合(Map)
List系列集合:添加的元素是有序、可重复、有索引
Set系列集合:添加的元素是无序、不重复、无索引
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回当前集合中元素的个数/集合的长度 |
细节:判断元素是否包含底层是依赖equals方法来判断是否包含,那么在javabean类中一定要重写equals方法。
2.1 List集合
List集合特有的方法:
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E set(int index) | 返回指定索引处的元素 |
List系列集合的遍历方式:
- 迭代器遍历:在遍历的过程中需要删除元素,请使用迭代器。
- 列表迭代器:在遍历的过程中需要添加元素,请使用列表迭代器
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
ListIterator<Integer> it = list.listIterator();
while (it.hasNext()){
int temp = it.next();
if (temp==3)
it.add(21);
}
System.out.println(list); // [1, 2, 3, 21, 4]
- 增强for:只是想遍历
- Lambda表达式:只是想遍历
- 普通for循环:遍历的时候想操作索引,可以用普通for
2.2 Set集合
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for遍历,也不能通过索引来获取元素
Set集合的实现类:
- HashSet:无序,不重复,无索引
- LinkedHashSet:有序,不重复,无索引
- TreeSet:可排序,不重复,无索引
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在集合中删除 |
public boolean contains(Object obj) | 判断当前集合是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数/集合的长度 |
Set演示:
Set<String> s = new HashSet<>();
boolean b1 = s.add("张三");
boolean b2 = s.add("李四");
boolean b3 = s.add("王五");
boolean b4 = s.add("张三");
System.out.println(b1+" "+b2+" "+b3+" "+b4);//true true true false
System.out.println(s); // [李四, 张三, 王五]
Iterator<String> it = s.iterator();
while (it.hasNext()){ // 迭代器遍历
String temp = it.next();
System.out.print(temp+" "); // 李四 张三 王五
}
System.out.println();
for (String s1 : s) { // 增强for遍历
System.out.print(s1+" "); // 李四 张三 王五
}
System.out.println();
s.forEach(e-> System.out.print(e+" ")); // Lambda表达式
// 李四 张三 王五
HashSet:底层采取哈希表存储数据
- 哈希值:根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
- 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的(各属性值相同其hash值也不同)
LinkedHashSet:
有序、不重复、无索引
TreeSet:
不重复、无索引、可排序
可排序:按照元素的默认规则(从小到大)排序
小例子:存储整数并进行排序
TreeSet<Integer> ts = new TreeSet<>();
ts.add(10);
ts.add(12);
ts.add(21);
ts.add(5);
// 默认从小到大排序
System.out.println(ts.toString()); // [5, 10, 12, 21]
如果要使用自定义类进行排序要实现Comparable接口,重写里面的抽象方法,再指定比较规则
public class Person implements Comparable<Person> { // 实现Comparable接口
private String Name;
private int age;
public Person() {
}
public Person(String name, int age) {
Name = name;
this.age = age;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int compareTo(Person p){ // 重写compareTo方法
return this.getAge()-p.getAge();
}
public String toString(){
return "Person:{"+this.getName()+","+this.getAge()+"}" ;
}
}
也可以使用比较器排序:创建TreeSet对象时候,传递比较器Comparator指定规则
小练习:存入几个字符串,按照长度排序,如果一样长则按照首字母排序
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>((e,x)->{
if (e.length()==x.length()){
return e.compareTo(x);
}
return e.length()-x.length();
});
ts.add("he");
ts.add("an");
ts.add("gsdg");
ts.add("okk");
ts.add("h");
System.out.println(ts); // [h, an, he, okk, gsdg]
}
2.3 双列集合
实际上存的就是键值对(其中键不可重复,值可以重复)
双列集合一次需要存一对数据,分别为键和值
Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
方法名称 | 说明 |
---|---|
V put(K key, V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合中是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String,String> m = new HashMap<String, String>();
String s1 = m.put("矿泉水","5元");
String s2 =m.put("方便面","4元");
String s3 =m.put("辣条","10元");
String s4 = m.put("辣条","15元"); // 如果key重复,会把原来的值覆盖并返回
System.out.println(s1+" "+s2+" "+s3+" "+s4); // null null null 10元
String res = m.remove("方便面");
System.out.println(res); // 4元,会返回删除key对应的值
boolean b1 = m.containsKey("面包"); //显然不包含面包这个key,结果为false
boolean b2 = m.containsKey("矿泉水"); // 包含矿泉水这个key,结果为true
System.out.println("b1="+b1+" b2="+b2); // b1=false b2=true
boolean b3 = m.containsValue("5元"); // 由于矿泉水这个key对应的value为5元,结果为true
System.out.println("b3 = "+b3); // b3 = true
boolean b4 = m.isEmpty(); // 由于m不为空,结果为false
System.out.println("b4 = "+b4); // b4 = false
int size = m.size();
System.out.println(size); // 2
// System.out.println(m);
}
}
2.3.1 Map集合的遍历方式一:键找值
// 创建map集合的对象
Map<String,String> map = new HashMap<>();
map.put("瓜子","5元");
map.put("矿泉水","2元");
map.put("方便面","6元");
// 通过键找值
Set<String> keys = map.keySet(); // 获取键的集合
keys.forEach(k -> System.out.println(k+": "+map.get(k)));
// 方便面: 6元
// 瓜子: 5元
// 矿泉水: 2元
2.3.2 Map集合的遍历方式二:键值对
Map<String,String> map = new HashMap<>();
map.put("齐天大圣","孙悟空");
map.put("天蓬元帅","猪八戒");
map.put("卷帘大将","沙悟净");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+": "+value);
}
System.out.println("---------------------------------------");
entries.forEach(e-> System.out.println(e.getKey()+": "+e.getValue()));
System.out.println("---------------------------------------");
Iterator it = entries.iterator();
while (it.hasNext()){
Map.Entry<String,String> m = (Map.Entry<String,String>)it.next();
System.out.println(m.getKey()+": "+m.getValue());
}
/*
齐天大圣: 孙悟空
天蓬元帅: 猪八戒
卷帘大将: 沙悟净
---------------------------------------
齐天大圣: 孙悟空
天蓬元帅: 猪八戒
卷帘大将: 沙悟净
---------------------------------------
齐天大圣: 孙悟空
天蓬元帅: 猪八戒
卷帘大将: 沙悟净
* */
2.3.3 Map集合的遍历方式三:lambda表达式
Map<String ,String> map = new HashMap<>();
map.put("张飞","莽夫");
map.put("刘备","大耳贼");
map.put("吕布","三姓家奴");
map.put("邢道荣","灵灵上将");
map.forEach((k,v)-> System.out.println(k+": "+v));
/* 张飞: 莽夫
邢道荣: 灵灵上将
吕布: 三姓家奴
刘备: 大耳贼
*/
2.3.4 HashMap
HashMap的键为位置如果存储的是自定义对象,需要重写hashCode和equals方法
2.3.5 LinkHashMap
由键决定:有序、不重复、无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序
2.3.6 TreeMap
TreeMap和TreeSet底层原理一样,都是红黑树结构
由键决定特性:不重复、无索引、可排序
可排序:对键进行排序
注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
代码书写两种排序规则
- 实现Comparable接口,指定比较规则
- 创建集合时传递Comparator比较器对象,指定比较规则
2.3.7 可变参数
在变量名前使用…
public class ArgsTest {
public static void main(String[] args) {
ArgsTest argsTest = new ArgsTest();
System.out.println(argsTest.getSum(1,2,3,4)); // 10
System.out.println(argsTest.getSum(1,2,3,4,5)); // 15
}
public int getSum(int ...args){
int res = 0;
for (int i = 0; i < arr.length; i++) {
res += args[i];
}
return res;
}
}
注意事项:
- 方法形参中最多只能写一个可变参数
- 在方法中,如果除了可变参数以外,还有其他的形参,那么可变参数要写在最后
2.3.8 Collections工具类
java.util.Collections:是工具类
作用:Collections不是集合,而是集合的工具类。
Collections常用的API:
方法名称 | 说明 |
---|---|
public static <T> boolean addAll(Collection<T> c,T …elements) | 批量添加元素 |
public staitc void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public staitc <T> void sort(List<T> list) | 排序 |
public staitc <T> void sort(List<T> list,Comparator<T> c) | 根据指定规则进行排序 |
public staitc <T> int binraySearch(List<T> list,T key) | 以二分查找法查找元素 |
public staitc <T> void copy(List<T> dest,List<T> src) | 拷贝集合中的元素 |
public staitc <T> int fill(List<T> list,T obj) | 使用指定的元素填充给集合 |
public staitc <T> int max/min(Collection<T> coll) | 根据默认的自然排序获取最大/最小值 |
public staitc <T> void swap(List<?> list,int i,int j) | 交换集合中指定位置的元素 |
import java.util.ArrayList;
import java.util.Collections;
public class ArrTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
Collections.addAll(arrayList,1,2,3,4,6);
System.out.println(arrayList); // [1,2,3,4,6]
Collections.shuffle(arrayList);
System.out.println(arrayList); // 随机打乱后的集合
}
}
3.Stream流
3.1 不可变集合
创建不可变集合的场景
- 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好地实践
- 当集合对象被不可信地库调用时,不可变形式是安全的
创建不可变集合的书写格式:
在List、Set、Map接口中,都存在静态的of方法,可以获取一个不可变的集合
方法名 | 说明 |
---|---|
static <E> List<E> of(E …elements) | 创建一个具有指定元素的List集合对象 |
static <E> Set<E> of(E …elements) | 创建一个具有指定元素的Set集合对象 |
static <K,V> List<K,V> of(E …elements) | 创建一个具有指定元素的Map集合对象 |
注意:这个集合不能添加,不能删除,不能修改
细节:
- 当获取一个不可变Set集合时,里面的参数一定要保证唯一性
- 当获取一个不可变Map集合时,里面的元素不能重复,键值对数量最多是10个。超过10个用ofEntries方法
3.2 初识stream流
从集合中获取所有以”张“开头的并且名字长度大于3的人名并打印出来
ArrayList<String> asList = new ArrayList<>();
asList.add("张无忌");
asList.add("孙悟空");
asList.add("张三");
asList.add("猪八戒");
asList.add("张三丰");
asList.stream().filter(name->name.startsWith("张")).filter(name->name.length() >=3).forEach(name-> System.out.println(name));
stream流的思想:
Stream流的作用:结合了Lambda表达式,简化集合、数组的操作
Stream流的使用步骤:
- 先得到一条Stream流(流水线),并把数据放上去
- 使用中间方法对流水线上的数据进行操作
- 使用终结方法对流水线上的数据进行操作
获取Stream流:
获取方式 | 方法名 | 说明 |
---|---|---|
单列集合 | default Stream<E> stream() | Collection中的默认方法 |
双列集合 | 无 | 无法直接使用stream流 |
数组 | public static <T> Stream<T> stream(T[] array) | Arrays工具类中的静态方法 |
一堆零散数据 | public static <T> Stream<T>of(T …values) | Stream接口中的静态方法 |
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"a","b","c","d","e");
Stream<String> s = list.stream();
s.forEach(e-> System.out.print(e)); // abcde
System.out.println("------------------");
HashMap<String,String> hm = new HashMap<>();
hm.put("1","a");
hm.put("2","b");
hm.put("3","c");
hm.put("4","d");
// 第一种获取hashmap的stream流方式
hm.keySet().stream().forEach(e-> System.out.println(e+": "+hm.get(e)));
// 1: a
// 2: b
// 3: c
// 4: d
System.out.println("------------------");
// 第二种获取hashmap的stream流方式
hm.entrySet().stream().forEach(e-> System.out.println(e.getKey()+": "+e.getValue()));
// 1: a
// 2: b
// 3: c
// 4: d
数组中使用stream流:
int arr[] = {1,2,3,4,5};
Arrays.stream(arr).forEach(e-> System.out.println(e));
/*
* 1
2
3
4
5
* */
3.3 Stream流的中间方法
名称 | 说明 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) | 过滤 |
Stream <T> limit(long maxSize) | 获取前几个元素 |
Stream <T> skip(long n) | 跳过前几个元素 |
Stream <T> distinct() | 元素去重,依赖(hashCode方法和equals方法) |
static <T>Stream<T> concat(Stream a,Stream b) | 合并a和b两个流为一个流 |
Stream <R> map(Function <T,R> mapper) | 转换流中的数据类型 |
filter方法测试:
ArrayList<String> asList = new ArrayList<>();
asList.add("张无忌");
asList.add("孙悟空");
asList.add("张三");
asList.add("猪八戒");
asList.add("张三丰");
asList.stream().filter(new Predicate<String>(){
@Override
public boolean test(String s) {
return s.startsWith("张");
}
}).forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// 简化写法
asList.stream().filter(str->str.startsWith("张")).forEach(str-> System.out.println(str));
注意:
- 中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
- 修改Stream流中的数据,不会影响原来集合或者数组中的数据
3.4 Stream流的终结方法
名称 | 说明 |
---|---|
void forEach(Consumer action) | 遍历 |
long count() | 统计 |
toArray() | 收集流中的数据,放到数组中 |
collect(Collector collector) | 收集流中的数据,放到集合中 |
toArray的用法:
ArrayList<String> list = new ArrayList<>();
list.add("22");
list.add("101");
list.add("32");
list.add("15");
// IntFunction的泛型:具体类型的数组
// apply的形参:流中数据的个数,要跟数组的长度保持一致
// apply的返回值:具体类型的数组
// 方法体:就是创建数组
Integer[] arr1 = list.stream().map(s->Integer.parseInt(s)).toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});
System.out.println(Arrays.toString(arr1));
// [22, 101, 32, 15]
// toArray的简化写法
Integer[] arr2 = list.stream().map(s->Integer.parseInt(s)).toArray(v->new Integer[v]);
collect用法:
// 收集所有男性名字,并放到一个List集合中
ArrayList<String> names = new ArrayList<>();
Collections.addAll(names,"孙悟空-男-500","猪八戒-男-1000","唐僧-男-40","白骨精-女-32");
List<String> namelist = names.stream()
.filter(s->"男".equals(s.split("-")[1]))
.collect(Collectors.toList());
namelist.forEach(e-> System.out.println(e));
// 收集所有男性名字和年龄,并放到一个Map集合中
Map<String,Integer> namemap = names.stream()
.filter(s->"男".equals(s.split("-")[1]))
.collect(Collectors.toMap(s->s.split("-")[0],s->Integer.parseInt(s.split("-")[2])));
System.out.println(namemap); //{孙悟空=500, 猪八戒=1000, 唐僧=40}
3.5 方法引用
把已经有的方法拿过来用,当作函数式接口中抽象方法的方法体
需要满足的条件:
- 引用处必须是函数式接口
- 被引用的方法必须已经存在
- 被引用方法的形参和返回值需要和抽象方法保持一致
- 被引用方法的功能要满足当前需求
FunctionalTest中:
import java.util.Arrays;
public class FunctionalTest {
public static void main(String[] args) {
Integer[] arr = {5,2,1,6,3,8,9};
Arrays.sort(arr,FunctionalTest::subtraction); // :: 方法引用符
System.out.println(Arrays.toString(arr));// [9, 8, 6, 5, 3, 2, 1]
}
public static int subtraction(Integer a,Integer b){
return b-a;
}
}
引用成员方法:
格式:对象::成员方法
- 其他类:其他类对象::方法名
- 本类:this::方法名
- 父类:super::方法名
引用构造方法:
格式:类名::new
引用数组的构造方法:
格式:数据类型[]::new
4.异常
异常就是代表程序出现的问题
误区:不是让我们以后不出异常,而是程序出了异常之后,该如何处理
Error:代表的是系统级别错误(属于严重问题)
Exception:叫做异常,代表程序可能出现的问题
编译时异常:在编译阶段,必须要手动处理,否则代码报错
运行时异常:在编译阶段是不需要处理的,是代码运行时出现的异常
4.1 异常的处理方式
JVM默认的处理方式:
- 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
- 程序停止执行,下面的代码不会再执行了
自己处理(捕获异常):
try {
可能出现异常的代码;
}catch(异常类型 变量名){
异常的处理代码;
}
目的:当代码出现异常时,可以让程序向下继续执行
pl:
public static void main(String[] args) {
try {
System.out.println(2/0);
}catch (ArithmeticException e){
System.out.println("除数不能为0 :"+e.getMessage()); // 除数不能为0/ by zero
}
System.out.println("程序执行到了这里"); // 正常执行
}
4.2 Throwable的成员方法
方法名称 | 说明 |
---|---|
public String getMessage() | 返回此throwable可抛出的简短描述 |
public String toString() | 返回throwable的详细消息字符串 |
public void printStackTrace() | 把异常的错误信息输出在控制台 |
try {
System.out.println(2/0);
}catch (ArithmeticException e){
System.out.println("除数不能为0 :"+e.getMessage());
System.out.println("除数不能为0 :"+e.toString());
e.printStackTrace();
}
System.out.println("程序执行到了这里");
// 除数不能为0 :/ by zero
// 除数不能为0 :java.lang.ArithmeticException: / by zero
// 程序执行到了这里
// java.lang.ArithmeticException: / by zero
// at ExceptionTest.main(ExceptionTest.java:5)
4.3 抛出处理
throws:写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常
public void 方法() throws 异常类名1,异常类名2...{
...
}
throw:写在方法内,结束方法,手动抛出异常对象,交给调用者,方法中下面的代码不再执行
public void 方法(){
throw new NullPointerException();
}
编译时异常:必须要写
运行时异常:可以不写
4.4 自定义异常
- 定义异常类
- 写继承关系
- 空参构造
- 带参构造
public class AgeOutOfBoundsException extends RuntimeException{
public AgeOutOfBoundsException(String message)
{
super(message);
}
public AgeOutOfBoundsException()
{
super();
}
}
public class AgeTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int age = 0;
try{
age = sc.nextInt();
if(age < 0 || age > 200)
throw new AgeOutOfBoundsException();
System.out.println("Age is " + age);
}catch (AgeOutOfBoundsException e){
System.out.println(e);
}catch (Exception e){
System.out.println(e);
}
}
}
4.5 finally
try {
可能出现异常的代码
}catch(异常类型 e){
处理异常
}finally{
最终执行的代码
}
特点:finally里面的代码一定被执行,除非虚拟机停止
接口:AutoCloseable
特点:特定的情况下,可以手动释放资源
创建流对象1;
创建流对象2;
try(流1;流2){
可能出现异常的代码;
}catch(异常类名 变量名){
}
// 资源用完最终自动释放
pl:
public static void main(String[] args) throws InterruptedException, FileNotFoundException {
FileInputStream fis = new FileInputStream("LearnIO\\a.txt");
FileOutputStream fos = new FileOutputStream("LearnIO\\b.txt");
try(fis;fos){
int b;
while((b = fis.read()) != -1){
fos.write(b);
}
System.out.println("复制完成");
}catch (IOException e){
e.printStackTrace();
}
Thread.sleep(1000*100);
}
5. File
5.1 File的构造方法
方法名 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent,String child) | 根据父路径名字符串和子路径名字符串创建文件对象 |
public File(File parent,String child) | 根据父路径对应文件对象和子路径名字符串创建文件对象 |
//path
String path = "C:\\Users\\29250\\Desktop\\a.txt";
File file1 = new File(path);
System.out.println(file1);
// 父级路径:C:\Users\29250\Desktop\
// 子级路径:a.txt
File file2 = new File("C:\\Users\\29250\\Desktop\\","a.txt");
System.out.println(file2);
5.2 File的成员方法
方法名 | 说明 |
---|---|
public boolean isDirectory() | 判断此路径名表示的File是否为文件夹 |
public boolean isFile() | 判断此路径名表示的File是否为文件 |
public boolean exists() | 判断此路径名表示的File是否存在 |
public long length() | 返回文件的大小(字节数量) |
public String getAbsolutePath() | 返回文件的绝对路径 |
public String getPath() | 返回定义文件时使用的路径 |
public String getName() | 返回文件的名称,带后缀 |
public long lastModified() | 返回文件的最后修改时间(时间毫秒值) |
5.3 File的常见成员方法(创建、删除)
方法名 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 创建单级文件夹 |
public boolean mkdirs() | 创建多级文件夹 |
public boolean delete() | 删除文件、空文件夹 |
创建文件:
public static void main(String[] args) throws IOException {
File f = new File("C:\\users\\29250\\Desktop\\a.txt");
boolean success = f.createNewFile();
System.out.println(success);
}
创建文件夹:
public static void main(String[] args) throws IOException {
File f = new File("C:\\users\\29250\\Desktop\\dir");
boolean success = f.mkdir();
System.out.println(success);
}
5.4 File的常见成员方法(获取并遍历)
方法名 | 说明 |
---|---|
public File[] listFiles() | 获取当前路径下所有内容 |
获取文件夹的大小:
public int getSize(File dir){
File[] arr = dir.listFiles();
if (arr == null){
return 0;
}
int size = 0;
for (File file : arr){
if (file.isDirectory()){
size += getSize(file);
}else {
size += file.length();
}
}
return size;
}
5.5 File的常见成员方法(获取并遍历)
方法名 | 说明 |
---|---|
public static File[] listRoots() | 列出可用的文件系统根 |
public String[] list() | 获取当前路径下所有内容 |
public String[] list(FilenameFilter filter) | 利用文件名过滤器获取当前该路径下所有内容 |
public File[] listFiles() | 获取当前路径下所有内容 |
public File[] listFiles(FileFilter filter) | 利用文件名过滤器获取当前该路径下所有内容 |
public File[] listFiles(FilenameFilter filter) | 利用文件名过滤器获取当前该路径下所有内容 |
6. IO流
IO流:存储和读取数据的解决方案
6.1 FileOutputStream
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中
步骤:
- 创建字节输出流对象
- 写数据
- 释放资源
String path = "C:\\Users\\29250\\Desktop\\test.txt";
try {
File file = new File(path);
FileOutputStream fos = new FileOutputStream(file,false); // 创建字节输出流对象,false表示关闭续写
fos.write(new String("你好,张三").getBytes()); // 写入文件
fos.close(); // 释放资源
} catch (IOException e) {
e.printStackTrace();
}
6.2 FileInputStream
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来
步骤:
- 创建字节输入流对象
- 读数据
- 释放资源
String path = "LearnIO\\test.txt";
FileInputStream inputStream = new FileInputStream(path); // 创建对象
while (true) {
int data = inputStream.read();
if (data == -1) {
break;
}
System.out.print((char) data);
}
inputStream.close(); // 释放资源
小文件拷贝:
FileInputStream is = new FileInputStream(new File("LearnIO\\a.txt")); // 创建字节输入流对象
FileOutputStream os = new FileOutputStream(new File("LearnIO\\b.txt")); // 创建字节输出流对象
int temp;
while ((temp = is.read())!=-1){
os.write((byte)temp); // 写入文件
}
os.close();
is.close(); // 释放资源
FileInputStream一次读多个字节
方法名 | 说明 |
---|---|
public int read() | 一次读一个字节数据 |
public int read(byte[] buffer) | 一次读一个字节数组数据 |
注意:一次读一个字节数组的数据,每次读取会尽可能把数组装满
1024的整数倍
拷贝大文件:
FileInputStream is = new FileInputStream(new File("LearnIO\\a.txt")); // 创建字节输入流对象
FileOutputStream os = new FileOutputStream(new File("LearnIO\\b.txt")); // 创建字节输出流对象
int len;
byte bytes = new byte[1024*1024*5];
while ((len= is.read(bytes))!=-1){
os.write(bytes,0,len); // 写入文件
}
os.close();
is.close(); // 释放资源
6.3 java中编码和解码的方式
编码的方法:
String类中的方法 | 说明 |
---|---|
public byte[] getBytes() | 使用默认方式进行编码 |
public byte[] getBytes(String charsetName) | 使用指定方式进行编码 |
解码的方法:
String类中的方法 | 说明 |
---|---|
String(byte[] bytes) | 使用默认方式进行解码 |
String(byte[] bytes, String charsetName) | 使用指定方式进行解码 |
public static void main(String[] args) throws UnsupportedEncodingException {
String str1 = "hello,你好";
byte[] bytes1 = str1.getBytes();
System.out.println(Arrays.toString(bytes1));
// [104, 101, 108, 108, 111, 44, -28, -67, -96, -27, -91, -67]
byte[] bytes2 = str1.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));
// [104, 101, 108, 108, 111, 44, -60, -29, -70, -61]
String str2 = new String(bytes1);
System.out.println(str2); // hello,你好
String str3 = new String(bytes1, "GBK"); // 使用GBK进行解码UTF-8编码的数据
System.out.println(str3); // hello,浣犲ソ
}
6.4 字符输入流 FileReader
字符流:字符流的底层其实就是字节流
字符流=字节流+字符集
特点:
输入流:一次读一个字节,遇到中文时,一次读多个字节
输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中
使用场景:对于纯文本文件进行读写操作
流程:
- 创建字符输入流对象
构造方法 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流关联本地文件 |
public FileReader(String path) | 创建字符输入流关联本地文件 |
- 读取数据
成员方法 | 说明 |
---|---|
public int read() | 读取数据,读到末尾返回-1 |
public int read(char[] buffer) | 读取多个数据,读到末尾返回-1 |
- 释放资源
public int close()
释放资源
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("LearnIO\\b.txt");
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
fr.close();
}
读取多个数据:
FileReader fr = new FileReader("LearnIO\\b.txt");
char[] chars = new char[1024];
int len;
while ((len = fr.read(chars)) != -1) {
System.out.print(new String(chars, 0, len));
}
fr.close();
6.5 字符输出流 FileWriter
构造方法:
构造方法 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流关联本地文件 |
public FileWriter(String pathname) | 创建字符输出流关联本地文件 |
public FileWriter(File file,boolean append) | 创建字符输出流关联本地文件,续写 |
public FileWriter(String pathname,boolean append) | 创建字符输出流关联本地文件,续写 |
FileWriter成员方法
成员方法 | 说明 |
---|---|
void write(int c) | 写出一个字符 |
void write(String str) | 写出一个字符串 |
void write(String str,int off,int len) | 写出一个字符串的一部分 |
void write(char[] cbuf) | 写出一个字符数组 |
void write(char[] cbuf,int off,int len) | 写出字符数组的一部分 |
6.6 字节缓冲流
方法名 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 把基本流包装成高级流,提高读取数据的性能 |
public BufferedOutputStream(OutputStream os) | 把基本流包装成高级流,提高写出数据的性能 |
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("LearnIO\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("LearnIO\\buffered.txt"));
byte [] bytes = new byte[1024];
int len ;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bos.close();
bis.close();
}
6.7 字符缓冲流
方法名 | 说明 |
---|---|
public BufferedReader(Reader r) | 把基本流变成高级流 |
public BufferedWriter(Writer r) | 把基本流变成高级流 |
字符缓冲流特有方法:
字符缓冲输入流特有方法 | 说明 |
---|---|
public String readLine() | 读取一行数据,如果没有数据可读了,会返回null |
字符缓冲输出流特有方法 | 说明 |
---|---|
public void newLine() | 跨平台的换行 |
6.8 序列化流和反序列化流
序列化流
可以把java中的对象写到本地文件中
构造方法 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 |
成员方法 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象序列化到文件中去 |
让需要被写入的JavaBean类实现Serializable接口
public class Student implements Serializable {
private int age;
private String name;
public Student(String name,int age) {
this.age = age;
this.name = name;
}
public Student(){}
}
序列化代码:
public static void main(String[] args) throws IOException {
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("LearnIO\\obj.txt"));
Student s = new Student("张三",22); // 创建对象
oos.writeObject(s); // 写入对象
oos.close(); // 关闭流
}
反序列化流
可以把序列化到本地的对象,读取到程序中来
构造方法 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把基本流变成高级流 |
成员方法 | 说明 |
---|---|
public Object readObject() | 把序列化到本地文件中的对象,读取到程序中来 |
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("LearnIO\\obj.txt")); // 创建对象输入流
Student s = (Student)ois.readObject(); // 读取对象数据
ois.close(); // 关闭流
System.out.println(s.getName()+s.getAge());
}
如果将对象写入文件中后,再去修改原来JavaBean类,会导致序列化版本号不同
解决办法:固定序列化版本号
public class Student implements Serializable {
private static final long serialVersionUID = 1L; // 固定序列化版本号
private int age;
private String name;
public Student(String name,int age) {
this.age = age;
this.name = name;
}
public Student(){}
}
6.9 打印流
PrintStream,PrintWriter
特点:
- 打印流只操作文件目的地,不操作数据源
- 特有的写出方法可以实现数据原样写出
– 例如:打印:97 文件中:97
– 打印:true 文件中:true - 特有的写出方法,可以实现自动刷新,自动换行
字节打印流:
构造方法 | 说明 |
---|---|
public PrintStream(OutputStream/File/String) | 关联字节输出流/文件/文件路径 |
public PrintStream(String fileName,Charset charset) | 指定字符编码 |
public PrintStream(OutputStream out, boolean autoFlush) | 自动刷新 |
public PrintStream(OutputStream out,boolean autoFlush,String encoding) | 指定字符编码且自动刷新 |
成员方法 | 说明 |
---|---|
public void write(int b) | 常规方法:将指定的字节输出 |
public void println(Xxx xxx) | 特有方法:打印任意数据,自动刷新,自动换行 |
public void print(Xxx xxx) | 特有方法:打印任意数据,不换行 |
public void printf(String format,Object…args) | 特有方法:带有占位符的打印语句,不换行 |
字符打印流:
构造方法 | 说明 |
---|---|
public PrintWriter(Writer/File/String) | 关联字节输出流/文件/文件路径 |
public PrintWriter(String fileName,Charset charset) | 指定字符编码 |
public PrintWriter(Write w, boolean autoFlush) | 自动刷新 |
public PrintWriter(OutputStream out,boolean autoFlush,String encoding) | 指定字符编码且自动刷新 |
成员方法 | 说明 |
---|---|
public void write(int b) | 常规方法:将指定的字节输出 |
public void println(Xxx xxx) | 特有方法:打印任意数据,自动刷新,自动换行 |
public void print(Xxx xxx) | 特有方法:打印任意数据,不换行 |
public void printf(String format,Object…args) | 特有方法:带有占位符的打印语句,不换行 |
6.10 压缩流和解压缩流
解压缩流:
public static void main(){
// 1.创建一个File表示要解压的压缩包
File src = new File("C:\\a.zip");
// 2.创建一个File表示解压的目的地
File dest = new File("D:\\")
upzip(src,dest);
}
public static void unzip(File src,File dest) throw IOException{
// 解压的本质:把压缩包里面的每一个文件或文件夹读取出来,按照层级拷贝到目的地中
// 创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
// 先获取到压缩包里面的每一个zipentry对象
ZipEntry e = null;
while((e=zip.getNextEntry())!=null){
if(e.isDirectory()){
File file = new File(dest,e.toString());
file.mkdirs();
}else{
FileOutputStream fos = new FileOutputStream(new File(dest,e.toString()));
int b;
while((b=zip.read())!=-1){
fos.write(b);
}
fos.close();
zip.closeEntry();
}
}
zip.close();
}
压缩流:
public static void main(String[] args) throws IOException {
// 1.创建File对象表示要压缩的文件夹
File src = new File("LearnIO\\src1");
// 2.创建File对象表示压缩包放在哪里
File destParent = src.getParentFile();
// 3.创建File对象表示压缩包的路径
File dest = new File(destParent, src.getName()+".zip");
// 4.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
// 5.获取src里面的每一个文件,变成ZipEntry对象,放入压缩包中
compress(src, zos, src.getName());
zos.close();
}
public static void compress(File src, ZipOutputStream zos,String name) throws IOException{
if (src.isDirectory()){
File[] files = src.listFiles();
for (File file : files) {
if (file.isDirectory()){ // 如果是文件夹,递归调用
compress(file, zos, name + "\\" + file.getName());
}else{
compress(file, zos, name+"\\");
}
}
}else {
// 1.创建ZipEntry对象表示压缩包中的一个文件
ZipEntry entry = new ZipEntry(name + src.getName());
// 2.将ZipEntry对象写入压缩包
zos.putNextEntry(entry);
// 3.读取src文件,写入压缩包
FileInputStream fis = new FileInputStream(src);
byte[] flush = new byte[1024];
int len = -1;
while ((len = fis.read(flush)) != -1) {
zos.write(flush, 0, len);
}
fis.close();
zos.closeEntry();
}
}
6.11 转换流
对于含有中文的字符串,如果按照传统输入输出流要将其写入到文件中,或从文件中读取,会导致乱码的问题。
需要使用转换流进行读取或写入,因为可以指定其字符集或编码方式
InputStreamReader用于将字节输入流转换为字符输入流
InputStream is = new FileInputStream(new File("test.txt"));
InputStreamReader isr = new InputStreamReader(is,"UTF-8");
int count = 0;
while ((count=isr.read())!=-1){
System.out.print((char)count);
}
isr.close();
is.close();
OutputStreamWriter用于将字节输出流转换为字符输出流,创建使用指定字符集的 OutputStreamWriter,如果不指定字符集就使用默认字符集创建OutputStreamWriter。转换之后可以不用关闭OutputStream
String string = "今天天气真不戳,aaaabbbccc";
File file = new File("\\test.txt");
OutputStream os = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(os,"UTF-8");
writer.write(string);
writer.flush();
writer.close();
6.12 Commons-io工具包
有关IO操作的开源工具包
常见方法:
FileUtils类中的方法 | 说明 |
---|---|
static void copyFile(File srcFile,File destFile) | 复制文件 |
static void copyDirectory(File srcDir,destDir) | 复制文件夹 |
static void copyDirectorytoDirectory(File srcDir,File destDir) | 复制文件夹 |
static void deleteDirectory(File directory) | 删除文件夹 |
static void cleanDirectory(File directory) | 清空文件夹 |
static String readFileToString(File file,Charset encoding) | 读取文件中的数据变成字符串 |
static void write(File file,CharSequence data,String encodiing) | 写出数据 |
流相关
IOUtils类中的方法 | 说明 |
---|---|
public static int copy(InputStream input,OutputStream output) | 复制文件 |
public static int copyLarge(Reader input,Writer output) | 复制大文件 |
public static String readLines(Reader input) | 读取数据 |
public static void write(String data,OutputStream output) | 写出数据 |
6.13. Hutool工具包
7. 多线程
7.1 多线程实现方式
- 继承Thread类的方式进行实现
创建子线程类
public class MyThread extends Thread{
public void run(){
int i;
for (i=0;i<10;i++){
System.out.println("子线程执行"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试类中:
public static void main(String[] args) {
MyThread mt = new MyThread(); // 创建线程对象
mt.start(); // 开启线程
for (int i = 0; i < 10; i++) {
System.out.println("main线程:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 实现Runnable接口的方式进行实现
实现Runnable接口的类中:
public class MyRun implements Runnable{
public void run()
{
Thread t = Thread.currentThread();
int i;
for (i = 0; i < 3; i++){
System.out.println(t.getName()+"正在执行:" + i);
}
}
}
测试类中:
public static void main(String[] args) {
MyRun task = new MyRun();
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.setName("线程1"); // 设置线程名称
t2.setName("线程2");
t1.start();
t2.start();
}
- 利用Callable接口和Future接口方式实现
– 1.创建一个类MyCallable实现Callable接口
– 2.重写call(是有返回值的,表示多线程运行的结果)
– 3.创建MyCallable的对象(表示多线程要执行的任务)
– 4.创建FutureTask对象(创建管理多线程运行的结果)
– 5.创建Thread类的对象,并启动(表示线程)
MyCallable接口中:
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
public String call(){
int i;
for(i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"正在运行 "+i);
}
return Thread.currentThread().getName()+"运行结束";
}
}
测试类中:
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask1 = new FutureTask<>(new MyCallable());
FutureTask<String> futureTask2 = new FutureTask<>(new MyCallable());
Thread t1 = new Thread(futureTask1); // 根据futureTask创建线程
Thread t2 = new Thread(futureTask2);
t1.setName("线程1"); // 设置线程名
t2.setName("线程2");
t1.start();
t2.start();
System.out.println(futureTask1.get()); // 可以通过FutureTask的get方法获取线程执行结果
System.out.println(futureTask2.get());
}
7.2 常见的成员方法
方法名 | 说明 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名称 |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位是毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程 |
public static void join() | 插入线程/插队线程 |
7.3 同步代码块(synchronized)
demo:使用多个线程进行计数
MyThread中:
public class MyThreadextends Thread{
public static int num = 100;
static Object objLock = new Object();
public void run(){
// 同步代码块
synchronized (objLock){
while (num>0){
System.out.println(currentThread().getName()+num);
num -= 1;
}
}
}
}
同步方法:
public class MyThread extends Thread{
public static int num = 0;
public void run(){
while (true){
// 同步代码块(同步方法)
if (method())break;
}
}
private synchronized boolean method(){
if (num>=100){
return true;
}
num += 1;
System.out.println(currentThread().getName()+":"+num);
return false;
}
}
7.4 Lock锁
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
pl:由三个线程进行计数
CountThread类中:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CountThread extends Thread{
public static int count = 0;
static Lock l = new ReentrantLock();
public void run(){
while(count < 5){
try {
Thread.sleep(1000);
l.lock();
if (count>=5)break;
System.out.println(Thread.currentThread().getName()+" :在数第"+count+"次");
count += 1;
}catch(Exception e){
e.printStackTrace();
}finally{
l.unlock();
}
}
}
}
测试类中:
public class CountThreadTest {
public static void main(String[] args) {
CountThread c1 = new CountThread();
CountThread c2 = new CountThread();
CountThread c3 = new CountThread();
c1.setName("线程1");
c2.setName("线程2");
c3.setName("线程3");
c1.start();
c2.start();
c3.start();
}
}
7.5 生产者消费者(阻塞队列)
阻塞队列:
ArrayBlockingQueue:底层是数组,有界
LinkedBlockingQueue:底层是链表,无界,但不是真正的无界,最大为int的最大值
Producer.java中
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
public class Producer extends Thread{
ArrayBlockingQueue<String> arrayBlockingQueue;
public Producer(ArrayBlockingQueue<String> arrayBlockingQueue){
this.arrayBlockingQueue = arrayBlockingQueue;
}
Random r = new Random();
public String[] productType = {"螺丝","盒钉","螺母","工件1","轴承","螺栓","螺帽","销钉","扇叶"};
@Override
public void run(){
while (true){
// 生产者不断地将产品放入队列中
try {
String product = productType[r.nextInt(0,productType.length)];
arrayBlockingQueue.put(product);
System.out.println("工厂生产了一个产品:"+product);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Consumer中:
import java.util.concurrent.ArrayBlockingQueue;
public class Consumer extends Thread{
ArrayBlockingQueue<String> arrayBlockingQueue;
public Consumer(ArrayBlockingQueue<String> arrayBlockingQueue){
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run(){
while (true){
// 不断从队列中取得产品
try {
String product = arrayBlockingQueue.take();
System.out.println("消费者已经取得了一个产品:"+product);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试类中:
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(1);
Producer p = new Producer(abq);
Consumer c = new Consumer(abq);
p.setName("生产商");
c.setName("消费者");
p.start();
c.start();
}
}
7.6 线程池
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程还给池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但如果提交任务时,线程池中没有空闲线程,也无法创建新的线程,任务就会排队等待。
线程池代码实现:
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建 一个没有上限的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
测试类中:
import java.util.concurrent.*;
public class PoolTest {
public static void main(String[] args) throws InterruptedException {
// 1.获取线程池对象,线程池中最多有3个线程
ExecutorService pool = Executors.newFixedThreadPool(3);
// 2.提交任务
for (int i = 0; i < 10; i++) {
pool.submit(new MyRunnable());
Thread.sleep(100);
}
// 3.销毁线程池
pool.shutdown();
}
}
MyRunnable类中:
public class MyRunnable implements Runnable{
@Override
public void run(){
for (int i = 1; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
可以看到,只是复用了线程池中的几个线程
自定义线程池:
任务拒绝策略:
策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常(不推荐) |
ThreadPoolExecutor.DiscardOldestPolicy | 抛出队列中等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
public static void main(String[] args) {
// 自定义线程池时需要的参数
ThreadPoolExecutor pool = new ThreadPoolExecutor(
4, // 核心线程数量,不能小于0
6, // 最大线程数,不能小于0,最大数量>=核心线程数
60, // 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(20), // 任务队列长度
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);
for (int i = 0; i < 10; i++) {
pool.submit(new MyRunnable()); // 提交任务交给线程池执行
try {
Thread.sleep(100);
}catch(Exception e){
e.printStackTrace();
}
}
pool.shutdown(); // 关闭线程池
}
8. 网络编程
网络编程三要素:IP、端口号、协议
8.1 InetAddress的使用
常用方法
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 确定主机的IP地址,主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
import java.net.InetAddress;
import java.net.UnknownHostException;
public class INetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
InetAddress address1 = InetAddress.getByName("192.168.31.179");
System.out.println(address1); // /192.168.31.179
InetAddress address2 = InetAddress.getByName("lonely");
System.out.println(address2); // lonely/192.168.31.179
String name = address1.getHostName();
String ip = address1.getHostAddress();
System.out.println(name+":"+ip); // lonely:192.168.31.179
}
}
udp发送数据案例:
发送端代码:
import java.io.IOException;
import java.net.*;
public class SendMessageDemo {
public static void main(String[] args) throws IOException {
// 1.创建DataGramSocket对象
// 细节:绑定端口,通过这个端口向外发送
// 空参:所有可用的端口中随机一个进行使用
// 有参:指定端口进行绑定
DatagramSocket ds = new DatagramSocket();
// 2.打包数据
String s = "我是数据";
byte[] dataBytes = s.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 60000;
DatagramPacket dp = new DatagramPacket(dataBytes,dataBytes.length,address,port);
// 3.发送数据
ds.send(dp);
}
}
接收端代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class ReceiveMessageDemo {
public static void main(String[] args) throws IOException {
// 1.创建DataGramSocket对象
// 细节:
// 在接收的时候,一定要绑定端口,要和发送的端口一致
DatagramSocket ds = new DatagramSocket(60000);
// 接收数据包
byte [] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);// 该方法是阻塞的,直到有数据发送过来
// 解析数据包
byte [] data = dp.getData(); // 接收到的数据
int len = dp.getLength(); // 获取接收数据的长度
InetAddress address = dp.getAddress(); // 获取发送数据的IP地址
int port = dp.getPort(); // 获取发送数据的端口号
System.out.println("从IP地址为:"+address+",端口号为:"+port+" 接收到数据:"+new String(data,0,len));
ds.close();
}
}
8.2 组播
发送端代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class MulSendMessageDemo {
public static void main(String[] args) throws IOException {
// 组播发送代码
// 创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket();
// 创建DataGramPacket对象
String s = "你好";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.0.1");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 调用MulticastSocket发送数据方法发送数据
ms.send(dp);
// 释放资源
ms.close();
}
}
接收端代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class MulReceMessageDemo {
public static void main(String[] args) throws IOException {
// 组播接受代码
// 1.创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(10086);
// 2.将当前本机,添加到224.0.0.1的这一组中
InetAddress address = InetAddress.getByName("224.0.0.1");
ms.joinGroup(address);
// 3.创建DatagramPacket数据包对象
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
// 4.接收数据
ms.receive(dp);
// 5.解析数据
byte[] data = dp.getData();
int len = dp.getLength();
String ip = dp.getAddress().getHostAddress();
String name = dp.getAddress().getHostName();
System.out.println("ip为"+ip+",主机名为"+name+"的用户,发送了数据:"+new String(data,0,len));
// 6.释放资源
ms.close();
}
}
8.3 TCP协议
TCP协议是一种可靠的网络协议,它在通信的两端各建立了一个Socket对象
通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
发送数据案例:
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
// TCP协议,发送数据
// 1.创建Socket对象
// 细节:在创建对象的同时会连接服务端
// 如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",10088);
// 2.可以从连接通道中获取数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write("你好".getBytes());
// 3.释放资源
outputStream.close();
socket.close();
}
}
接收数据案例:
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// TCP协议,发送数据
// 1.创建对象ServerSocket
ServerSocket ss = new ServerSocket(10088);
// 2.监听客户端的连接
Socket socket = ss.accept();
// 3.从连接通道中获取输入流读取数据
InputStream inputStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream); // 将字节流转换成字符流
int b;
while ((b=isr.read())!=-1){
System.out.println((char)b);
}
// 释放资源
socket.close();
ss.close();
}
}
9. 反射
反射允许对封装类的字段,方法和构造函数的信息进行编程访问
9.1 获取class对象的三种方式
- Class.forName(“全类名”);
- 类名.class
- 对象.getClass();
创建一个Student类:
public class Student {
private String name;
private int age;
public String nickname;
public Student(String name,int age,String nickname){
this.name = name;
this.age = age;
this.nickname = nickname;
}
public Student(){
this.name = "张三";
this.nickname = "二狗";
this.age = 18;
}
public void getNickname(String word){
System.out.println(this.name+"的绰号是"+this.nickname+"并且ta说:"+word);
}
public int getAge(){
return this.age;
}
}
三种获取Class对象的方式演示:
import java.lang.reflect.InvocationTargetException;
public class GetClassWayDemo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class c1 = Class.forName("Student");
System.out.println(c1); // class Student
Class c2 = Student.class;
System.out.println(c2); // class Student
Student s = new Student("李四",22,"小黑子");
Class c3 = s.getClass();
System.out.println(c3); // class Student
// 通过方法名调用方法
c1.getMethod("getNickname",String.class).invoke(s,"食不食油饼"); // 张三的绰号是二狗
}
}
9.2 利用反射获取构造方法
Class类中用于获取构造方法的方法:
方法名 | 说明 |
---|---|
Constructor <?> [] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor <?> [] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor<T> getConstructor(Class<?>…parameterTypes) | 返回单个公共构造方法对象 |
Constructor<T> getDeclaredConstructor(Class<?>…parameterTypes) | 返回单个构造方法对象 |
Constructor类中用于创建对象的方法
方法名 | 说明 |
---|---|
T newInstance(Object …initargs) | 根据指定的构造方法创建对象 |
setAccessible(boolean flag) | 设置为true,表示取消访问检查 |
创建一个Student类:
public class Student {
private String name;
private int age;
public String nickname;
// 构造方法1,带参
public Student(String name,int age,String nickname){
this.name = name;
this.age = age;
this.nickname = nickname;
}
public Student(){ // 构造方法2,不带参
this.name = "张三";
this.nickname = "二狗";
this.age = 18;
}
private Student(String name){ // 构造方法3,私有的,带参
this.name = name;
this.nickname = "三毛";
this.age = 10;
}
public void getNickname(String word){
System.out.println(this.name+"的绰号是"+this.nickname+"并且ta说:"+word);
}
public int getAge(){
return this.age;
}
@Override
public String toString() { // 重写toString方法
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", nickname='" + nickname + '\'' +
'}';
}
}
测试类中:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.util.Arrays;
public class GetConstructorDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class c = Class.forName("Student");
Constructor[] arr1 = c.getConstructors(); // 获取所有公共构造函数
System.out.println(Arrays.toString(arr1));
// [public Student(java.lang.String,int,java.lang.String), public Student()]
Constructor[] arr2 = c.getDeclaredConstructors(); // 获取所有构造函数
System.out.println(Arrays.toString(arr2));
//[public Student(java.lang.String,int,java.lang.String), private Student(java.lang.String), public Student()]
Constructor c1 = c.getConstructor(String.class,int.class,String.class); // 获取参数为String,int,String的构造函数
System.out.println(c1);
// public Student(java.lang.String,int,java.lang.String)
Parameter[] parameters = c1.getParameters(); // 获取该构造函数的所需参数类型
System.out.println(Arrays.toString(parameters));
// [java.lang.String arg0, int arg1, java.lang.String arg2]
Constructor c2 = c.getDeclaredConstructor(String.class);
System.out.println(c2);
// private Student(java.lang.String)
// 用指定的构造方法创建对象,返回的是Object类型的对象,需要进行强转
Student s = (Student) c1.newInstance("张三",22,"小黑子");
System.out.println(s);
// Student{name='张三', age=22, nickname='小黑子'}
}
}
9.3 利用反射获取成员变量
Class类中用于获取成员变量的方法:
方法名 | 说明 |
---|---|
Field[] getFields() | 获取所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
Field类中用于创建对象的方法
方法名 | 说明 |
---|---|
void set(Object obj,Object value) | 赋值 |
Object get(Object obj) | 获取值 |
还是利用 上述的Student类
测试类中:
import java.lang.reflect.Field;
import java.util.Arrays;
public class GetFieldDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class c = Class.forName("Student"); // 获取Student的class对象
Field[] fields1 = c.getFields(); // 获取所有公共成员变量
System.out.println(Arrays.toString(fields1));;
// [public java.lang.String Student.nickname]
Field[] fields2 = c.getDeclaredFields(); // 获取所有成员变量
System.out.println(Arrays.toString(fields2));;
// [private java.lang.String Student.name, private int Student.age, public java.lang.String Student.nickname]
Field f1 = c.getField("nickname"); // 获取Student的成员变量nickname
System.out.println(f1.toString());
// public java.lang.String Student.nickname
Field f2 = c.getDeclaredField("name"); // 获取Student的成员变量name
System.out.println(f2.toString());
// private java.lang.String Student.name
Student s = new Student();
String nickname = (String) f1.get(s); // 获取Student对象s的成员变量:nickname
f2.setAccessible(true); // 临时设置私有成员变量可访问
String name = (String) f2.get(s); // 获取Student对象s的成员变量:name
System.out.println(name+":"+nickname); // 张三:二狗
f2.set(s,"李四"); // 为Student对象s设置name成员变量值:李四
f1.set(s,"小李子"); // 为Student对象s设置nickname成员变量值:小李子
System.out.println(s); // Student{name='李四', age=18, nickname='小李子'}
}
}
9.4 利用反射获取成员方法
Class类中获取成员方法的方法:
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name,Class<?>…parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name,Class<?>…parameterTypes) | 返回单个成员方法对象 |
Method类中用于创建对象的方法
方法名 | 说明 |
---|---|
Object invoke(Object obj,Object … args) | 运行方法如果有参数则传入参数 |
参数1—obj:用obj对象调用该方法
参数2—args:调用该方法传递的参数(如果没有就不写)
返回值:方法的返回值,如果没有就不写
Student类:
public class Student {
private String name;
private int age;
public String nickname;
public Student(String name,int age,String nickname){
this.name = name;
this.age = age;
this.nickname = nickname;
}
public String getNickname(String word){
System.out.println(this.name+"的绰号是"+this.nickname+"并且ta说:"+word);
return this.nickname;
}
private String getName(String s){
System.out.println(s);
return this.name;
}
public int getAge(){
return this.age;
}
}
测试类中:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class GetMethodDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class c = Class.forName("Student");
Method[] methods1 = c.getMethods(); // 获取所有公有方法,包括继承的
Method[] methods2 = c.getDeclaredMethods(); // 获取所有成员方法,不包括继承的
System.out.println(Arrays.toString(methods1));
// [public java.lang.String Student.toString(),
// public int Student.getAge(),
// public java.lang.String Student.getNickname(java.lang.String),
// public boolean java.lang.Object.equals(java.lang.Object), public native int //java.lang.Object.hashCode(),
// public final native java.lang.Class java.lang.Object.getClass(),
// public final native void java.lang.Object.notify(),
// public final native void java.lang.Object.notifyAll(),
// public final void java.lang.Object.wait(long) throws java.lang.InterruptedException,
// public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException,
// public final void java.lang.Object.wait() throws java.lang.InterruptedException]
System.out.println(Arrays.toString(methods2));
// [private java.lang.String Student.getName(java.lang.String),
// public java.lang.String Student.toString(),
// public int Student.getAge(),
// public java.lang.String Student.getNickname(java.lang.String)]
Method m1 = c.getDeclaredMethod("getName",String.class); // 获取私有成员方法getName
Method m2 = c.getMethod("getNickname",String.class); // 获取公有成员方法getNickname
Student s = new Student("Jack",21,"小丑");
m1.setAccessible(true); // 设置Student的成员方法getName临时可见
String name = (String) m1.invoke(s,"调用了getName方法"); // 调用方法
// 调用了getName方法
String nickname = (String)m2.invoke(s,"调用了getNickname方法");
// Jack的绰号是小丑并且ta说:调用了getNickname方法
System.out.println(name+":"+nickname); // Jack:小丑
}
}
10. 动态代理
1.为什么需要代理?
代理可以无侵入式的给对象增强其他的功能
调用者—>代理—>对象
2.代理长什么样?
代理里面就是对象要被代理的方法
3.Java通过什么来保证代理的样子?
通过接口保证,后面的对象和代理需要实现同一个接口,接口中就是被代理的所有方法
如何为Java对象创建一个代理对象?
java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数一 loader:用于指定哪个类加载器,去加载生成的代理类
参数二 interfaces:指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
参数三 h:用来指定生成的代理对象要干什么事情
Star接口中:
public interface Star {
public abstract String sing(String name);
public abstract void dance();
}
BigStar类中:
public class BigStar implements Star{
private String name;
public BigStar() {}
public BigStar(String name) {
this.name = name;
}
@Override
public String sing(String name){
System.out.println(this.name+"正在唱"+name);
return "谢谢全民制作人";
}
@Override
public void dance(){
System.out.println(this.name+"正在跳舞");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ProxyUtil类中:创建代理的工具类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
* 类的作用:创建一个代理
* */
public class ProxyUtil {
/*
* 方法的作用:
* 给一个明星的对象,创建一个代理
*
* 形参:被代理的明星对象
* 返回值:给明星创建的代理
* */
public static Star createProxy(BigStar bigStar){
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(), // 参数一 loader:用于指定哪个类加载器,去加载生成的代理类
new Class[]{Star.class}, // 参数二 interfaces:指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
new InvocationHandler() { // 参数三 h:用来指定生成的代理对象要干什么事情
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("sing".equals(method.getName())){
System.out.println("准备话筒,准备让鸡哥唱歌");
}else if ("dance".equals(method.getName())){
System.out.println("布置现场,准备让鸡哥跳舞");
}
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
测试类中:
public class Test {
/*
* 如果外面的人想要大明星唱歌
* 1.获取代理的对象
* 代理对象=ProxyUtil.createProxy(大明星的对象)
* 2.再调用代理的唱歌方法
* 代理对象.唱歌的方法("只因你太美")
*/
public static void main(String[] args) {
// 1.获取代理的对象
BigStar bs = new BigStar("坤坤");
Star proxy = ProxyUtil.createProxy(bs);
// 调用唱歌的方法
String res = proxy.sing("只因你太美");
System.out.println(res);
proxy.dance();
// 输出结果:
// 准备话筒,准备让鸡哥唱歌
// 坤坤正在唱只因你太美
// 谢谢全民制作人
// 布置现场,准备让鸡哥跳舞
// 坤坤正在跳舞
}
}