java学习笔记

java.util.Calendar类:日历类

1.Calendar 类是一个抽象类,里面提供了很多操作日历字段的方法
2.Calendar 类无法直接创建对象使用,里面有一个静态方法叫getInstance(),该方法返回了Calender 类的子类对象
3.static Calendar getInstance()使用默认时区和语言环境获得一个日历

public class DemoCalendar(){
	public satice void main(String[] args){
	Calendar c  = Calendar.getInstance();//多态
	Systen.out.println(c);//输出一个日历,里面包括年月日时分秒等等,通过其成员方法可以修改或添加日历信息
 }
}

calendar类的常用成员方法

  1. public int get(int field):返回给定日历字段的值。
  2. public void set(int fleid, int value):将给定的日历字段设置为给定值。
  3. public abstract void add(int field ,int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量
  4. public Date getTime():返回一个表示此Calender时间值的Date对象,把一个日历对象转化为日期对象
  5. int field:日历字段(日历中的相关信息):YEAR:年,MONTH:月…
    下面展示一些 内联代码片
public class DemoCalender {
    public static void main(String[] args) {
        demo01();
    }

    public static void demo01() {
        //public int get(int field):返回给定日历字段的值。
        Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        System.out.println(year);

        int month = c.get(Calendar.MONTH);
        System.out.println(month);

        int day = c.get(Calendar.DAY_OF_MONTH);
        System.out.println(day);

        // public void set(int fleid, int value):将给定的日历字段设置为给定值。
        System.out.println("=================================");
        c.set(Calendar.MONTH,3);
        int month2 = c.get(Calendar.MONTH);
        System.out.println(month2);

        //public abstract void add(int field ,int amount):根据日历的规则,
        // 为给定的日历字段添加或减去指定的时间量
        System.out.println("=================================");
        c.add(Calendar.MONTH,-1);
        int month3 = c.get(Calendar.MONTH);
        System.out.println(month3);

        //public Date getTime():返回一个表示此Calender时间值的Date对象
        Date date = c.getTime();
        System.out.println(date);
    }
}

System类的常用方法

  1. public static long currentTimeMillis():返回以毫秒为单位的当前时间
  2. public static void arraycopy():将数组中指定的数据拷贝到另一个数组。
public class Demo01System {
    public static void main(String[] args) {
       // demo01();
        demo02();

    }

    private static void demo02() {
        //public static void arraycopy():将数组中指定的数据拷贝到另一个数组。
        //定义源数组
        int [] src = {1,2,3,4,5};
        //定义目标数组
        int[] dest = {6,7,8,9,10};
        System.out.println("复制前:"+Arrays.toString(dest));
        System.arraycopy(src,0,dest,0,3);//src:源数组,dest:目标数组,3是元素个数,两个0是数组复制起始目标索引。
        System.out.println("复制后:"+Arrays.toString(dest));
         /*
            复制前:[6, 7, 8, 9, 10]
            复制后:[1, 2, 3, 9, 10]
            数组长度在java中无法改变
        */
    }

    private static void demo01(){
        //public static long currentTimeMillis():返回以毫秒为单位的当前时间,
        // 一般用来验证程序的效率

        long s = System.currentTimeMillis();
        for (int  i = 0; i < 9999; i++) {
            System.out.print(i);
        }
        System.out.println("");
        long e = System.currentTimeMillis();
        System.out.println("程序共耗时:"+(e -s) +"毫秒"); 
    }

}

字符串缓冲区StringBuilder方法

字符串是常量,底层是一个被final修饰的数组,创建之后不能改变。
进行字符串的相加,其实是新建一个新的字符串,把原来的字符串名贴在新字符串的地址上。

StringBuilder类
字符串缓冲区,底层也是一个数组,但没有被final修饰,可以改变长度。

常用构造方法:

  1. public StringBuilder append():添加任意类型数据的字符串形式,并返回当前对象自身
  2. toString():将当前StringBuilder对象转换为String对象

String和StringBuilder可以相互转换:

  1. StringBuilder(String str) str->SB
  2. public String toString(): SB->str
    String str = SB.toString();
	public class Demo01StringBuilder {
    public static void main(String[] args) {
        //空参构造方法
        StringBuilder sb1 = new StringBuilder();
        System.out.println("sb1:"+sb1);

        //带参构造方法
        StringBuilder sb2 = new StringBuilder("abc");
        System.out.println("sb2:"+sb2);//sb2:abc

        //append():添加任意类型数据的字符串形式,并返回当前对象自身
        StringBuilder sb3 = new StringBuilder();
        sb3.append(81);
        //链式编程
        sb3.append(80).append(true).append("str").append(8.8);
        System.out.println(sb3);

        //字符串和字符串缓冲区的转换
        demo01();
    }

    //字符串和字符串缓冲区的转换
    public static void demo01(){
        String str = "Hello";
        System.out.println("str:"+str);
        StringBuilder SB = new StringBuilder(str);
        SB.append(" World");
        String str2 = SB.toString();
        System.out.println("str2:"+str2);

    }
}

第五章:包装类

概念

基本数据类型,使用起来非常方便,但是没有对应的方法来操作这些基本类型的数据
可以使用一个类,把基本数据类型的数据装起来,在类中定义一些方法,这个类叫做包装类 我们可				
以使用类中的方法来操作这些基本类型的数据

int-> Integer
char -> Character
byte,short,long,float,double,boolean这些都是首字母大写

装箱与拆箱

包装类和基本数据类型之间可以自动转换

public class DemoPakeage {
    public static void main(String[] args) {
            /*
     自动装箱
     相当于Integer in = new Integer(1);
    */
        Integer in = 1;//相当于Integer in = new Integer(1);

    /*
     自动拆箱
     相当于in = in.intValue()+2 = 3;
     */
        in= in+2;
     /*
        ArrayList集合无法直接存储整数,但是可以存储包装类
     */
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        int a  = list.get(0);//list.get(0).intValue();
    }

基本类型与字符串之间的转换

  1. 基本类型的值+""最简单的方法(工作中常用)
  2. 包装类的静态方法toString(参数),
    static String toString(int i),返回一个指定的整数的String对象
  3. String类的静态方法VauleOf(int i)返回int参数的字符串表示形式
  4. 使用包装类的静态方法parseXXX(“字符串”)
    Integer类:static int parseInt(String s)
    Double类:static double parseDouble(String s)
public class Demo03Integer {
    public static void main(String[] args) {
        //基本类型->字符串
        int i1 = 100;
        String s1 = i1 + "";//这里的100是字符串不是数字
        System.out.println(s1+200);//100200字符串相加

        String s2 = Integer.toString(100);//100
        System.out.println(s2+100);//100100

        String s3 = String.valueOf(i1);//100
        System.out.println(s3);/100

        //字符串->基本类型
        int i = Integer.parseInt(s1);
        System.out.println(i-10); //90
    }
}

Collection集合

在这里插入图片描述
java.util.Collection接口
说有单列集合的最顶层的接口,里面定义了所有单列集合共性的方法
任意的单列集合都可以使用Collection接口中的方法

Collection 常用的方法(即单列集合共性的方法)

1. public void clear():清空集合
2. public boolean remove(E e):把e从数组中移除
3. public boolean contains(E e):判断数组中是否有e
4. public boolean isEmpty():判断数组是否为空
5. public int size():返回集合中元素的个数
6. public Object[] toArray():把集合中的元素,存储到数组中
7. public boolean add(E e): 把给定的对象添加到当前集合中
public class DemoCollection {

    public static void main(String[] args) {
        //创建集合对象,可以使用多态
        Collection<String> coll = new ArrayList<>();
        System.out.println(coll);
        //public boolean add(E e): 把给定的对象添加到当前集合中
        boolean b1 = coll.add("张三");
        System.out.println(b1);
        System.out.println(coll);

        coll.add("李四");
        coll.add("王五");
        coll.add("赵六");
        coll.add("田七");
        System.out.println(coll);

        //public boolean remove(E e):把e从E数组中移除
        System.out.println("============================");
        boolean b2 =coll.remove("赵六");
        System.out.println(coll);

        //public boolean contains(E e):判断数组中是否有e
        System.out.println("============================");
        boolean b3 =coll.contains("田七");
        System.out.println(b3);

        //public boolean isEmpty():判断数组是否为空
        System.out.println("============================");
        boolean b4 = coll.isEmpty();
        System.out.println(b4);

        //public int size():返回集合中元素的个数
        System.out.println("============================");
        int b5 = coll.size();
        System.out.println(b5);

        //public Object[] toArray():把集合中的元素,存储到数组中
        System.out.println("============================");
        Object[] obj = coll.toArray();
        for (int i = 0; i < obj.length; i++) {
            System.out.println(obj[i]);
        }

    }
}

java泛型

泛型:让数据类型变得参数化,可以看做是未知的数据类型,相当于未知变量(指数据类型),可以用来接收数据
E e element 元素
T t Type 类型

定义泛型时,对应的数据类型是不确定的。泛型方法被调用时,会指定具体类型。

核心目标:解决容器类型在编译时安全检查的问题。就是用于类型检查

优点:

  1. 避免了类型转换,存储的是什么类型,取出的就是什么类型。
  2. 如果想要存储的数据的数据类型出错,会直接报错,把运行期错误(程序运行)提前为编译期错误(写程序的时候)
    在这里插入图片描述
    定义泛型类

//当我们不确定数据类型的时候用泛型代替

public class ArrayList<E>{
        public boolean add(E e){}
        public E get(int index){}
    }

//当我们传递数据时,自动把E替换成想要的类型,这里替换成了String

ArrayList<String> list = new ArrayList<>();

含有泛型的方法

public <E> void GenericMethod(E e){
		System.out.println(e);
}

静态方法可以类名.方法名直接使用,不需要new个实例对象

//含有泛型的接口

迭代器Iterator(集合都可以使用迭代器)

java.util.Iterator接口:迭代器(可用于对集合遍历,有点像递归)
常用方法:

  1. boolean hasNext() 判断集合中有没有下一个元素,有就返回true

  2. E next() 返回迭代的下一个元素,取出集合中的下一个元素

    【注】:Iterator迭代器,是一个接口,无法直接使用,需要使用Iterator接口的实现类对象,
    获取实现类对象的方法比较特殊,要用Collection接口中的一个方法iterator(),
    该方法返回的就是迭代器的实现类对象
    Iterator iterator() 返回在此Collection的元素上进行迭代的迭代器

    迭代器使用步骤:

    1. 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
    2. 使用Iterator接口中的hasNext()方法,判断是否有下一个元素
    3. 使用Iteratot接口中的next()方法,取出集合的下一个元素
public class demo02Iterator {
    public static void main(String[] args) {
        //创建一个集合
        Collection<String> coll = new ArrayList<>();//这里使用了多态的写法,前面是接口,后面是实现类
        //添加元素
        coll.add("博丽灵梦");
        coll.add("雾雨魔理沙");
        coll.add("十六夜咲夜");
        coll.add("东风谷早苗");
        coll.add("伊吹萃香");

        //获取迭代器,并把指针(索引)指向集合的-1位置
        //Iterator<E> iterator() 返回在此Collection的元素上进行迭代的迭代器
        Iterator<String> it = coll.iterator();

        //使用循环遍历集合
        //boolean hasNext() 判断集合中有没有下一个元素,有就返回true
        //E next() 返回迭代的下一个元素,取出集合中的下一个元素
        while(it.hasNext()){
            System.out.println(it.next());//指针下移至0位置,并返回元素
        }

    }
}

增强for循环

增强for循环:底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写
Collection<E>extends Iterable<E>:所有的单列集合都乐意使用增强for
public interface Iterable<E>:这个接口的实现类允许成为“foreach”语言的目标

作用:用来遍历集合和数组

格式:
    for(集合/数组的数据类型 变量名:集合名/数组名){
        sout(变量名);
    }
import java.util.ArrayList;

public class Demo04for {
    public static void main(String[] args) {
        demo01();
        demo02();
    }
    private  static  void demo01(){
        int [] array = {1,2,3,4,5};
        for(int i: array){
            System.out.println(i);
        }
    }
    private static  void demo02(){
        ArrayList<String> list = new ArrayList<>();
        list.add("博丽灵梦");
        list.add("雾雨魔理沙");
        list.add("十六夜咲夜");
        list.add("东风谷早苗");
        list.add("芙兰朵露");

        for(String i: list){
            System.out.println(i);
        }

    }
}

泛型通配符

当使用泛型类或者接口时,传递的数据中,泛型的类型不确定,可以通过通配符<?>表示,但是一旦适应泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法(以及继承其父类Collection的方法)无法使用。
泛型的通配符:
?:代表任意的数据类型
注意:1.创建对象时不能使用
2.只能作为方法传递集合的参数使用

import java.util.ArrayList;
import java.util.Iterator;

public class Demo05 {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);

        ArrayList<String> list2 = new ArrayList<>();
        list2.add("a");
        list2.add("b");

        printArray(list1);
        printArray(list2);

    }
    //在集合定义的时候不能用泛型通配符,但是在集合作为参数被传递的时候可以使用
    public static void printArray(ArrayList<?> list){//这里使用了泛型通配符,list只能接受数据,不能存储数据
        Iterator<?> it = list.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}
  1. 泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
  2. 泛型的下限限定:? extends E 代表使用的泛型只能是E类型的父类/本身
import java.util.ArrayList;
import java.util.Collection;

public class Demo06Generic {
    public static void main(String[] args) {
        Collection<Integer> list1 = new ArrayList<Integer>();
        Collection<String> list2 = new ArrayList<String>();
        Collection<Number> list3 = new ArrayList<Number>();
        Collection<Object> list4 = new ArrayList<Object>();

        getElement1(list1);//Integer是Number的子类
        getElement1(list2);
        getElement1(list3);//本身
        getElement1(list4);

        getElement2(list1);
        getElement2(list2);
        getElement2(list3);//本身
        getElement2(list4);//父类

    }
    //泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
    public static void getElement1(Collection<? extends Number> coll){}
    //泛型的下限限定:? extends E 代表使用的泛型只能是E类型的父类/本身
    public static void getElement2(Collection<? super Number> coll){}
}

数据结构

数组

  1. 查询快:根据索引就可以找到指定元素
  2. 增删慢:需创建新的数组,把原数组未增删的部分复制过来,把新地址赋值回去

List集合

介绍及常用方法

list接口的特点:
    1.有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出123)
    2.有索引,包含了一些带索引的方法
    3.允许存储重复的元素

  List接口带索引的方法(特有)
       1.add(int index, E element) 将指定的元素到这个列表的末尾(可选操作)。
       2.get(int index) 返回此列表中指定位置的元素.
       3.remove(int index) 移除此列表中指定位置的元素(可选操作)。
       4.set(int index, E element) 用指定元素替换此列表中指定位置的元素(可选操作)。
public class DemoList {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();//多态的写法

        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("a");//可以存储相同的元素

        System.out.println("=======================");
        //1.add(int index, E element) 将指定的元素到这个列表的末尾(可选操作)。
        list.add(3,"ithema");
        System.out.println(list);

        System.out.println("============================");
        //2.get(int index) 返回此列表中指定位置的元素.
        String s = list.get(3);
        System.out.println(s);

        System.out.println("============================");
        //3.remove(int index) 移除此列表中指定位置的元素(可选操作)。
        String b = list.remove(3);
        System.out.println(b);

        System.out.println("===========================");
        //4.set(int index, E element) 用指定元素替换此列表中指定位置的元素(可选操作)。
        String a = list.set(4, "A");
        System.out.println(a);
        System.out.println(list);
    }
}

LinkedList集合的特点(需要操作链表首尾的时候使用)

LinkedList接口的特点:
	1.顶层是一个链表结构:查询慢,增删快
	2.里面包含了大量操作首尾元素的方法
	注意:使用使用LinkList特有的方法不能使用多态

特有方法:

  1. addFirst(E e) 在该列表开头插入指定的元素。
  2. addLast(E e) 将指定的元素追加到此列表的末尾。
  3. getFirst() 返回此列表中的第一个元素。
  4. getLast() 返回此列表中的最后一个元素
  5. push(E e) 将元素推送到由此列表表示的堆栈上。相当于addFirst(E e)
  6. removeFirst() 从此列表中删除并返回第一个元素。
  7. removeLast() 从此列表中删除并返回最后一个元素。
  8. pop() 从此列表表示的堆栈中弹出一个元素。

数据结构

链表

在这里插入图片描述

哈希值

哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的,不是数据实际存储的物理地址)
int hashCode() 返回该对象的哈希码值

set接口(查询速度快,但是不能存储重复元素)

java.util.Set接口 extends Collection 接口
Set接口的特点:

  1. 不允许存储重复的元素
  2. 没有索引,也没有带索引的方法,也不能使用普通for循环遍历
  3. 是一个无序的集合,存储元素和取出元素的顺序有可能不一致
  4. 底层是一个哈希表结构(查询的速度非常的快)
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Demo01Set {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();

        set.add(1);
        set.add(2);
        set.add(3);

        Iterator<Integer> it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}

HashSet集合存储自定义类型元素

set集合存储的元素(String,Integer,…Student,Person…)唯一

要求:同名同年龄的人,视为同一个人,只能存储一次(Hash值相同,equals方法判断相同的元素唯一)

public class Demo03HashSetSavePerson {
    public static void main(String[] args) {
        //创建HashSet集合存储Person
        HashSet<Person> set  =new HashSet<>();
        Person p1 = new Person("小美女",18);
        Person p2 = new Person("小美女",18);
        Person p3 = new Person("小美女",19);
        System.out.println(p1.hashCode());//
        System.out.println(p2.hashCode());
        System.out.println(p3.hashCode());

        System.out.println(p1==p2);//false(==比较的是地址值),没有重写Hashset and insert方法,比较的是三者地址值
        System.out.println(p1.equals(p2));//true(equals方法比较的是实际内容),重写了HI方法,比较的是Hash值
        set.add(p1);//没有重写HI方法,三者地址不同,三者都可以存储经set集合
        set.add(p2);//重写方法后p1==p2,两者hash值一致,内容相同,因为不可存储相同元素,只能存储其中之一
        set.add(p3);
        System.out.println(set);
    }
}

【注释】:

  1. ==比较的是地址值
  2. equals方法比较的是实际内容

LinkedHashSet(HashSet的子类,有序的自定义类型存储)

public class Demo04LinkedHashSet {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("www");
        set.add("abc");
        set.add("abc");
        set.add("itcast");
        System.out.println(set);//[abc, www, itcast]无序,按照ASIC码排序,不允许重复

        LinkedHashSet<String> linked = new LinkedHashSet<>();
        linked.add("www");
        linked.add("abc");
        linked.add("abc");
        linked.add("itcast");
        System.out.println(linked);//[www, abc, itcast] 有序,什么顺序存储,就是什么顺序输出,不允许重复
    }
}

可变参数(当不确定参数个数的时候使用,实际是数组)

使用情况:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数,可变参数实际是一个数组

  1. 格式:
    修饰符 返回值类型 方法名(数据类型…变量名){}
  2. 原理:
    可变参数的底层是一个数组,根据传递参数个数的不同,会创建不同长度的数组,来存储这些参数
public class Demo01VarArgs {
    public static void main(String[] args) {
        int i=add(10,20);
        System.out.println(i);
    }
    /*
        已知:计算整数的和,数据类型确定,元素个数不定,
        因为:可变参数int...arr的底层实际是一个数组
        所以:add()建立时,就会创建一个长度为0的数组,存储传递过来的参数 new int[]{10,20};
     */
    public static  int add(int...arr){
        System.out.println(arr);
        System.out.println(arr.length);
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }
}

可变参数(实际是个数组,当不确定传递多少个参数时使用)

使用情况:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数,可变参数实际是一个数组
格式:
    修饰符 返回值类型 方法名(数据类型...变量名){}
原理:
    可变参数的底层是一个数组,根据传递参数个数的不同,会创建不同长度的数组,来存储这些参数

快捷键目录标题文本样式列表链接代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图插入类图
代码片复制

下面展示一些 内联代码片

public class Demo01VarArgs {
    public static void main(String[] args) {
        int i=add(10,20);
        System.out.println(i);
    }
     public static  int add(int...arr){
        System.out.println(arr);
        System.out.println(arr.length);
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }
已知:计算整数的和,数据类型确定,元素个数不定,
    因为:可变参数int...arr的底层实际是一个数组
    所以:add()建立时,就会创建一个长度为0的数组,存储传递过来的参数 new int[]{10,20};

可变参数的注意事项:
1. 一个方法的参数列表,只能有一个可变参数
2. 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
3. Object…obj ,Object数据类型作为可变参数,功能强大,可以接受任意类型数据

Collections集合工具类的常用方法

【注意】Colllection接口是集合层次结构中的根界面
Collections是集合工具类,用来对集合进行操作,
常用方法如下:

  1. addAll(Collection<? super T> c, T… elements) 将所有指定的元素添加到指定的集合。
  2. shuffle(List<?> list) 打乱列表。
  3. sort(List list) 根据其元素的natural ordering对指定的列表进行排序
  4. sort(List list, Comparator<? super T> c) 传递集合和比较规则
    根据指定的比较器引起的顺序对指定的列表进行排序。
    comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
    comparator:相当于找一个第三方的裁判,比较两个
  5. Comparator的排序规则:
    o1-o2:升序,
    o2-o1:降序
public class Demo02Sort {
    public static void main(String[] args) {
        //加入多个元素到集合
        ArrayList<Integer> list01 = new ArrayList<>();
        Collections.addAll(list01, 1, 2, 3);
        System.out.println(list01);//[1, 2, 3]

        //打乱集合元素
        Collections.shuffle(list01);
        System.out.println(list01);//[2, 3, 1]

        //升序排序
        ArrayList<String> list02 = new ArrayList<>();
        Collections.addAll(list02,"a","c","b");
        System.out.println(list02);//[a, c, b]
        Collections.sort(list02);
        System.out.println(list02);//[a, b, c]

        //自定义排序
        ArrayList<Person> list03 = new ArrayList<>();
        list03.add(new Person("张三",18));
        list03.add(new Person("李四",19));
        list03.add(new Person("王五",17));
        System.out.println(list03);
        
        //重写了Person类的CompareTo方法,按照年龄大小排序
        Collections.sort(list03);
        System.out.println(list03);//[Person{name='王五', age=17}, Person{name='张三', age=18}, Person{name='李四', age=19}]
    }
}

如果是排序自己写的实现类,就必须重写了该实现类的CompareTo方法

 @Override
    public int compareTo(Person o) {
        //return 0;//认为元素都是相同的
        //自定义比较规则,比较两个人的年龄(this,参数Person)
        //return this.getAge()-o.getAge();//升序排序
        return o.getAge()-this.getAge();//降序排序

this(自己)-o(参数):升序
o(参数)-this(自己):降序

Map集合

util.Map<K,V>集合
Map集合的特点:

  1. Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
  2. Map集合中的元素,key和value的数据类型是可以相同,也可以不同
  3. key不允许重复,但是value是可以重复的
  4. key和value是一一对应的

Map集合的常用实现类:
HashMap<k,v>集合的特点:
5. HashMap的底层是哈希表:查询速度非常快
6. HashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致

LinkedHashMap<k,v>集合的特点:

  1. 低层是哈希表+链表(保证迭代的顺序)
  2. 是一个有序的集合,存储元素和取出元素的顺序是一致的

输出集合map,若输出的不是地址值,则该方法重写了toString方法
Map常用方法:
8. put(K key, V value)将指定的key和指定的value添加到Map当中。
若key重复着return null,若key不重复则替换成新value并return 被替换的value
9. remove(Object key) 移除指定key的value并返回 。return v
10. get(Object key) 获取指定key的value,若无这返回null。return v
11. containsKey(Object key) 是否包含指定的key,则返回 true 。return boolean
12. keySet():返回一个set集合,可通过遍历set集合间接遍历Map集合

public class Demo03Map {
    public static void main(String[] args) {
        show01();
        show02();
        show03();
    }

    private static void show03() {
        Map<String,Integer> map03 = new HashMap<>();//尽量使用int等常用数据类型的包装类
        map03.put("赵丽颖",168);
        map03.put("杨颖",165);
        map03.put("林志玲",178);

        Integer v1 = map03.get("杨颖");
        System.out.println("v1:"+v1);//v1:165

        Integer v2 =map03.get("迪丽热巴");
        System.out.println("v2:"+v2);//v2:null
    }

    private static void show02() {
        Map<String,Integer> map02 = new HashMap<>();//多态写法
        map02.put("a",1);
        map02.put("b",2);
        map02.put("c",3);
        Integer s = map02.remove("a");
        System.out.println(s);//1
        System.out.println(map02);//{b=2, c=3}

    }
    private static void show01(){
        Map<String,String> map = new HashMap<>();

        String v1 = map.put("李晨", "范冰冰1");
        System.out.println(v1);

        String v2 = map.put("李晨", "范冰冰2");
        System.out.println(v2);

        map.put("1","a");
        map.put("2","b");
        map.put("3","c");
        System.out.println(map);//输出的不是地址值,说明map重写了toString方法


    }
}

Map集合的常用遍历方法

第一种遍历方法:键找值
Set<K> keySet()返回对应的set集合
步骤:
1.Map集合的方法keySet()方法获取set()集合
2.遍历set()集合获取每一个key值:迭代器,增强for循环
3.通过Map集合的方法get(key)获取对应key的value
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class Demo02KeySet {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("赵丽颖",168);
        map.put("杨颖",165);
        map.put("林志玲",165);

        Set<String> s = map.keySet();//获取map中key的set集合
        
		 System.out.println("--------------------------");//迭代器
        Iterator<String> it = s.iterator();
        while(it.hasNext()){
            System.out.println(map.get(it.next()));
        }


        System.out.println("--------------------------");//增强for循环
        for(String key:s){
            System.out.println(map.get(key));
        }

    }
}
Map集合遍历的第二种方式:使用Entry对象遍历

Map集合中的方法:
    set<Map.Entry<K,V>> entrySet() 返回此映像中包含的映射关系的Set视图。(返回一个set集合)

步骤:1.使用Map集合中的EntrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中,
     2.遍历Set()集合,获取每一个Entry对象
     3.使用Entry()中的getKey()和getValue()方法获取键值对
public class Demo04Map2 {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("赵丽颖",168);
        map.put("杨颖",165);
        map.put("林志玲",178);

        Set<Map.Entry<String,Integer>> set = map.entrySet();//获取map的entry(键值对)的set集合
        
		 System.out.println("--------------------------");
        Iterator<Map.Entry<String,Integer>> it = set.iterator();
        while (it.hasNext()){
            Map.Entry<String,Integer> entry = it.next();
            String key = entry.getKey();
            Integer value =entry.getValue();
            System.out.println(key+ "=" + value);
        }

        System.out.println("--------------------------");
        for (Map.Entry<String, Integer> me : set) {
            String key =me.getKey();
            Integer value = me.getValue();
            System.out.println(key+"="+value);

        }
    }

Map集合存储自定义类型(存储键值对,key值唯一)

HashMap集合存储自定义键值
Map集合保证key是唯一的:必须重写hashCode方法和equals方法,以保证key唯一
若key值重复,则会用新的value值替换旧的value值。
public class HashMapSavePerson {
    public static void main(String[] args) {
        show01();
    }
    private static void show01() {
        HashMap<String,Person> map = new HashMap<>();
        //key重复,会用新的value替换旧的value。
        map.put("北京",new Person("张三",18));
        map.put("上海",new Person("李四",19));
        map.put("广州",new Person("王五",20));
        map.put("北京",new Person("赵六",18));

        Set<String> set = map.keySet(); //获取map关于key的set集合
        for (String key : set) {
            Person value = map.get(key);
            System.out.println(key + "-->" + value);//Person重写了toString方法,所以输出的额不是地址而是内容
        /*
            上海-->Person{name='李四', age=19}
            广州-->Person{name='王五', age=20}
            北京-->Person{name='赵六', age=18}
         */
        }
    }

LinkedHashMap(输入和输出的顺序一致的hashmap集合)

public class Demo01LinkedHashMap {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<>();
        map.put("a","a");
        map.put("c","c");
        map.put("b","b");
        map.put("a","e");
        System.out.println(map);//{a=e, b=b, c=c}hashmap集合的顺序按照ASIC码,
                                //有可能输入和输出顺序不一致

        System.out.println("---------------------------");
        LinkedHashMap<String,String> linked = new LinkedHashMap();
        linked.put("a","a");
        linked.put("c","c");
        linked.put("d","d");
        linked.put("a","e");
        System.out.println(linked);//{a=e, c=c, d=d}
                                    // LinkedHashMap集合输入和输出的顺序一致

    }
}

计算字符串中每个字符的个数

练习:计算一个字符串中每个字出现的次数

分析:
1.使用Scanner获取用户输入的字符串。
2.创建Map集合,key是字符串中的字符,value是字符串中字符的个数。
3.遍历字符串,获取每一个字符
4.使用获取到的字符,去map集合中判断key是否存在
    key存在:
        通过map.get(key)的方法获取相应的value;
        并value++;
        map.put(key,value)方法更新map集合
    key不存在:
        则map.put(key,1),使value为1
public class Demo03MapTest {
    public static void main(String[] args) {
        //1.使用Scanner获取用户输入的字符串。
        System.out.println("请输入一段字符串:");
        Scanner sc  = new Scanner(System.in);
        String str = sc.next();

        //2.创建Map集合,key是字符串中的字符,value是字符串中字符的个数。
        Map<Character,Integer> map = new HashMap<>();

        for (char c : str.toCharArray()) {
            if(map.containsKey(c)){
                Integer value =map.get(c);
                value++;
                map.put(c,value);
            }else{
                map.put(c,1);
            }
        }
        System.out.println(map);//{a=3, c=2, v=4}
    }
}

JDK9版本新增静态方法of

List几口,Set接口,Map接口:里面新增了一个静态方法of,可以给集合一次性添加多个元素
static<E> List<E> of (E...element)
使用前提:
    当集合中存储的元素的个数已经确定了,不在改变时使用
 注意:
    1.of方法只适用于List接口,Set接口,Map接口,不适用于接接口的实现类
    2.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常
    3.Set接口和Map接口在调用of方法的时候,不能有重复元素,否则会抛出异常
public class Demo01JDK9 {
    public static void main(String[] args) {
        List<String> list = List.of("a","b","a","c","d",);
        System.out.println(list);

        Set<String> set = Set.of("a","b","a","c","d",);
        System.out.println(set);
    }
}

Debug调试程序说明

   使用方式:
        在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
        右键,选择Debug执行程序
        程序就会停留在添加的第一个断点处
        
    执行程序:
        f8:逐行执行程序
        f7:进入到方法
        shift+f8:跳出方法
        f9:调到下一个断点,如果没有下一个断点,那么久结束程序
        ctrl+f2:推出debug模式,停止程序
        Console:切换到控制台

斗地主案例

package demo18;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/*
        斗地主综合案例:
        1.准备牌
        2.洗牌
        3.发牌
        4.排序
        5.看牌
 */
public class DouDiZhu {
    public static void main(String[] args) {
        //1.准备牌
        //创建一个Map集合存储牌和牌的索引
        HashMap<Integer,String> poker = new HashMap<>();
        ArrayList<Integer> pokerindex = new ArrayList<>();
        List<String> color = new ArrayList<>();
        Collections.addAll(color,"♥","♠","♣","♦");
        List<String> number = new ArrayList<>();
        Collections.addAll(number,"2","A","K","Q","J","10","9","8","7","6","5","4","3");

        int index = 0;
        poker.put(index,"大王");
        pokerindex.add(index);
        index++;
        poker.put(index,"小王");
        pokerindex.add(index);
        index++;
        for (String c : number) {
            for (String n : color) {
                String Poker = c+n;
                poker.put(index,Poker);
                pokerindex.add(index);
                index++;
            }
        }
        System.out.println(poker);
        System.out.println(pokerindex);

        System.out.println("-------------------");
        //洗牌
        Collections.shuffle(pokerindex);
        System.out.println(pokerindex);

        //发牌
        ArrayList<Integer> player01 = new ArrayList<>();
        ArrayList<Integer> player02 = new ArrayList<>();
        ArrayList<Integer> player03 = new ArrayList<>();
        ArrayList<Integer> DiPai = new ArrayList<>();

        for (int i = 0; i < pokerindex.size(); i++) {
            Integer in = pokerindex.get(i);
            if(i>=51){
                DiPai.add(in);
            }else if (i%3==0){
                //给玩家一发牌
                player01.add(in);
            }else if (i%3==1){
                //给玩家二发牌
                player02.add(in);
            }else if (i%3==2){
                //给玩家三发牌
                player03.add(in);
            }
        }

        /*
        4.排序
        使用Collections中的方法sort(List)
         */
        Collections.sort(DiPai);
        Collections.sort(player01);
        Collections.sort(player02);
        Collections.sort(player03);

        /*
            5.看牌
            定义一个看牌的方法,提高程序的复用性
            参数:
                String name:玩家名称
                HashMap<Integer,String> poker:存储牌的poker集合
                存储玩家牌的索引的集合
         */
        show("周润发",poker,player01);
        show("刘德华",poker,player02);
        show("周星驰",poker,player03);
        show("底牌",poker,DiPai);
    }
    public static void show(String name,HashMap<Integer,String> poker,ArrayList<Integer> list){
        System.out.print(name+":");
        for (Integer key : list) {
            String value = poker.get(key);
            System.out.print(value+" ");
        }
        System.out.println();
    }
}

异常的解析及其处理方式

java.lang.Throwable类:是java语言中所有错误或异常的超类。
    Exception:编译期异常,进行编译(写代码)java程序出现的问题
        RuntimeException:运行期异常,java程序运行过程中出现的问题
        异常就相当于程序得了一个小毛病(感冒,发烧),把异常处理掉,可以继续运行
     解决方式:
           1. try...catch
           2. ctrl+enter   throw(抛出这个异常)
     Error错误:
        错误相当于程序得了一个无法治愈的绝症,必须修改源代码。
public class Demo01Exception {
    public static void main(String[] args) {
        int[] arr = {1,2,3};

        try{
            System.out.println(arr[3]);//可能出现异常的程序,这里是数组越界异常
        }catch (Exception e){
            System.out.println("后续代码");//后面的代码依旧会执行,throw方法则不会。
        }
    }
}
    java异常处理的五个关键字:try,catch,finally,throw,throws..

throw关键字:

    作用:可以使用throw关键字在指定的方法中抛出指定的异常
    使用格式:
        throw new xxxException("异常产生的原因");
    注意:
        1.throw关键字必须写在方法的内部
        2.throw关键字后边new的对象必须是Exception或者Exception的子类对象
        3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
            throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
            throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws(抛出异常),要么try...catch
public class Demo01ExceptionThrow {
    public static void main(String[] args) {
        int[] arr = new int[3];
        int e = getElement(arr,3);
        System.out.println(e);
    }

    private static int getElement(int[] arr, int index) {
        /*
            我们对传递过来的参数数组,进行合法性校验
            如果数组arr的值是空
            则抛出空指针异常
         */
        //NullPointerException是一个运行期异常,我们不用处理,默认交给JVM处理。
        if(arr == null){
            throw new NullPointerException("传递的数组的值是空");
        }
        //索引越界异常
        if(index<0||index>arr.length)
        {
            throw new ArrayIndexOutOfBoundsException("传递的数组的索引超出数组的使用范围");
        }
        int ele = arr[index];
        return ele;
    }
}

objects非空判断(简化throw空指针异常)

public class Demo04Object {
    public static void main(String[] args) {
        method(null);
    }

    private static void method(Object obj) {
        /*if(obj == null){
            throw new NullPointerException("传递的对象的值是null");
        }*/
		//两个方法等同
        Objects.requireNonNull(obj,"传递的对象的值是null");
    }
}

声明异常throws关键字

throws关键字:异常处理的第一种方式,就是交给别人处理
 作用:
     会把异常对象的声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理-->中断处理
  使用格式:在方法声明时使用
        修饰符  返回值类型 方法名(参数列表) throws AAAException,BBBException{
                throw new AAAException("产生原因");
                throw new BBBException("产生原因");
        }
   注意:
    1.throws关键字必须写在方法声明处
    2.throws关键字后边声明的异常必须是Exception或者其子类
    3.如果抛出多个异常,那么throws后面也必须声明多个异常
    4.抛出的异常必须处理
            要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM(在方法声明处在声明)
            要么try...catch自己处理
public class Demo05Throws {
    public static void main(String[] args) throws IOException {
    //这里继续声明了异常,抛给JVM处理
        readFile("c:\\a.txt");
    }

    注意:FileNotFoundException是编译异常,抛出了编译异常,
    就必须处理这个异常,采用4中的两种方法
    
          FileNotFoundException extends IOException 只需声明父类即可
     
    public static void readFile(String fileName) throws IOException {
        if(!fileName.equals("c:\\\\a.txt")){
            throw new FileNotFoundException("传递的文件不是c:\\a.txt");
        }

        /*
              如果传递的路径,不是.txt结尾
              抛出IO异常对象
         */
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件后缀名异常");
        }
    }
}

try…catch处理异常

try..catch.异常处理的第二种方式,自己处理异常
格式:
    try{
        可能出现异常的代码
    }catch(定义一个异常的变量,用来接受异常对象){
            异常的处理逻辑
            在工作中,一般会把异常的信息记录在一个日志中
    }
    【注意】1.try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
           2.如果try中产生了异常,那么就会执行catch,然后执行之后的程序
           3.如果没有产生异常,那么就不会执行catch,继续执行之后的代码
public class Demo02tryCatch {
    public static void main(String[] args) {
        try{
            readFile("d:\\a.tx");
        }
        catch (IOException e){
            System.out.println("文件的后缀不是.txt");
        }
        System.out.println("后续代码");

    }
    public static void readFile(String fileName) throws IOException {
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件后缀名异常");
        }
        System.out.println("路径没有问题");
    }
}

Throwable类中定义了3中异常处理的方法(最好用)

  1. String getMassage() 返回此throwable的简短信息
  2. String toString()返回此throwable的详细消息字符串
  3. void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息时最全面的
public class Demo03Throwable {
    public static void main(String[] args) {
        try{
            readFile("d:\\a.tx");
        }
        catch (IOException e){
            e.printStackTrace();
            //System.out.println(e.getMessage());
        }
        System.out.println("后续代码");
    }
    public static void readFile(String fileName) throws IOException {
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件后缀名异常");
        }
        System.out.println("路径没有问题");
    }
}

finally代码块(当某个代码无论是否异常,都必须执行时)

格式:
try{
可能出现异常的代码
}catch(定义一个异常的变量,用来接受异常对象){
异常的处理逻辑
在工作中,一般会把异常的信息记录在一个日志中
}finally{
无论是否出现异常,该代码块都会执行
}

【注意事项】:1. 不能单独使用,必须和try…catch一起使用
2. 一般用于资源释放

public class Demo03Throwable {
    public static void main(String[] args) {
        try {
            readFile("d:\\a.tx");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            System.out.println("资源释放");
        }
    }
    public static void readFile(String fileName) throws IOException {
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件后缀名异常");
        }
        System.out.println("路径没有问题");
    }
}

多个异常处理

  1. 分别处理(多try多catch)
public class Demo06Exception {
    public static void main(String[] args) {
        try {
            int[] arr = {1,2,3};
            System.out.println(arr[3]);

        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }

        try{
            List<Integer> list = new ArrayList<>();
            Collections.addAll(list,1,2,3);
            System.out.println(list.get(4));
        }catch (IndexOutOfBoundsException e){
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

  1. 多个异常一次捕获,多次处理(一try多catch)
    【注意】:多catch若有子父类关系,则子类必须写在上面
public class Demo06Exception {
    public static void main(String[] args) {
        try {
            int[] arr = {1,2,3};
            System.out.println(arr[3]);
                List<Integer> list = new ArrayList<>();
                Collections.addAll(list,1,2,3);
                System.out.println(list.get(4));

        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }catch (IndexOutOfBoundsException e){
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}
  1. 一try一catch
    多catch所对应的异常必须有父子关系,保留父类catch就ok
    尽量不要在finally语句中写return语句

父类异常和子类异常的相统一

子父类的异常处理:
1.如果父类抛出多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常
2.父类方法没有抛出异常,子类重写父类方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,不能声明异常抛出
注意:父类异常是什么样,子类异常就怎么样

public class fu {
      public void show01() throws NullPointerException,ClassCastException{}
      public void show02() throws IndexOutOfBoundsException{}
      public void show03() throws IndexOutOfBoundsException{}
      //父类没有声明异常时,子类也不能声明异常,只能捕获处理
  public void show04(){}
}
class zi extends fu{
  //子类重写父类方法时,抛出和父类相同的异常
  public void show01() throws NullPointerException,ClassCastException{}
  //子类重写父类方法时,可以抛出父类异常的子类
  public void show02() throws IndexOutOfBoundsException{}
  //子类重写父类方法时,可以不抛出异常
  public void show03(){}
  //父类没有声明异常时,子类也不能声明异常,只能捕获处理
  public void show04(){
      try {
          throw new Exception("编译异常");
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
}

自定义异常

自定义异常:
        java提供的异常类,不够我们使用,需要自己定义一些异常类
    格式:
        public class XXXException extends Exception | RuntimeException{
            添加一个带空参的构造方法
            添加一个带异常信息的构造方法
        }
     注意:
        1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
        2.自定义异常类必须继承Exception或者RuntimeException
            继承Exception:说明是编译器异常,如果方法内部抛出了编译器异常,就必须处理这个异常,要么try...catch,要么throw
            继承RuntimeException:运行期异常,无需处理,交给虚拟机
public class RegisterException extends Exception{
    public RegisterException(){
        super();
    }
    public RegisterException(String message)
    {
        super(message);
    }
}

要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提醒:亲,该用户已被注册

public class Demo01RegisterException {
        private static String[] usernames = new String[]{"张三","李四","王五"};

    public static void main(String[] args)  {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String next = sc.next();
        checkUsername(next);
    }
    public static void checkUsername(String username) {
        for (String s : usernames) {
            if(s.equals(username)){
                try {
                    throw new RegisterException("亲,该用户名已经被注册");
                } catch (RegisterException e) {
                    e.printStackTrace();
                    return ;//结束方法,不运行下面的sout
                }
            }
        }
        System.out.println("恭喜您注册成功!");
    }
}

并发和并行

并发:指两个或者多个事件在同一个时间段内发生(交替执行)
并行:指两个或多个事件在同一时间发生(同时发生)(同时执行)

在这里插入图片描述

线程与进程

进程:是指一个内存中运行的应用程序进入到内存的程序就是进程
线程:是进程中的一个执行单元,负责当前进程中程序的执行,例如:360管家内的清理垃圾
在这里插入图片描述

在这里插入图片描述

线程调度

  1. 分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用CPU的时间
  2. 抢占式调度:
    优先让优先级高的线程使用CPU,若优先级相同,则随机选取一个执行,java是抢占式调度。
  3. 主线程:执行主方法的线程
    执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径叫做main(主)线程

创建多线程

创建多线程的第一种方式:创建Thread类的子类
java.lang.ThreadL类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

实现步骤:
    1.创建一个Thread类的子类
    2.在Thread类的子类中重写Tread类中的run方法,设置线程任务(开启线程要做什么)
    3.创建Thread类的子类对象
    4.调用Thread类中的方法start方法,开启新的线程,执行run方法。
    【注】:void start()使该线程开始执行,Java虚拟机调用该线程的run方法
    结果是两个线程并发地运行;当前线程(main)和另一个线程(创建的新线程,执行其run方法).
    多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。

main优先级更高先执行main线程,然后抢占式执行创建的线程

public class Demo01Thead extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("run1"+i);
        }
    }
}
public class MyThread {
    public static void main(String[] args) {
        Demo01Thead t = new Demo01Thead();
        Demo02Thread s =new Demo02Thread();
        s.start();
        //t.run();
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main:"+i);//main优先级更高先执行main线程,然后抢占式执行创建的线程
        }
    }
}

在这里插入图片描述

Thread类(线程类)

Thread中的常用方法

获取线程的名称:
1.使用Thread类中的方法getName()
    String getName() 返回该线程的名字
2.可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程名称
   static Thread currentThread() 返回对当前正在执行的线程对象的引用。
public class MyThread extends Thread{
    //重写Thread方法中的run方法,设置线程任务
    @Override
    public void run(){
        //获取线程名称,这里用的第一种方法
        String name = getName();
        System.out.println(name);
        //第二种方法
        Thread t = Thread.currentThread();
        System.out.println(t);
        //第三种方法
        String name1 = t.getName();
        System.out.println(name1);
    }
}
public class GetThreadName {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new MyThread().start();
        mt.start();
        //获取主线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}
main
Thread-0
Thread[Thread-0,5,main]
Thread-0
Thread-1
Thread[Thread-1,5,main]
Thread-1
设置线程名称
设置线程的名称:
    1.使用Thread类中的方法setName(名字)
        void setName(String name) 改变线程名称,使之与参数name相同
    2.创建一个待参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,吧线程名称传递给父类
    让父类(Thread)给子线程起一个名字Thread(String name)分配新的Thread对象
public class MyThread extends Thread{
    public MyThread(){}

    public MyThread(String name){
        super(name);//通过构造方法把线程名称传递给父类,让父类给子线程起一个名字
    }

    //重写run方法,写明要执行的任务
    @Override
    public  void run(){
        System.out.println(Thread.currentThread().getName());//输出线程名称
    }

}
public class Demo01setThreadName {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.setName("x");//通过setName方法设置名称
        t.start();

        MyThread mt = new MyThread("小姐姐");//通过构造方法把线程名称传递给父类,让父类给子线程起一个名字
        mt.start();
    }
}
暂停线程
 sleep(long millis, int nanos)
    使正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行)
    毫秒数结束之后,程序继续执行	
public class Demo02Sleep {
    public static void main(String[] args) {
        for (int i = 0; i < 60; i++) {
            System.out.println(i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
开启多线程的第二种方法:实现Runnable接口
创建多线程的第二种方法:实现Runnable接口
实现步骤:
    1.创建一个Runnable接口的实现类
    2.在实现类中重写run方法来设置线程任务
    3.创建一个Runnable接口的实现类对象
    4.创建Thread(线程)类对象,构造方法中传递Runnable接口的实现类对象
    5.调用Thread类中的start方法,开启新的线程执行run方法
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
    //2.在实现类中重写run方法来设置线程任务
    @Override
    public void run(){
        //第二个线程
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}
public class Demo01Runnable {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //4.创建Thread(线程)类对象,构造方法中传递Runnable接口的实现类对象
        Thread t= new Thread(run);
        //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();

        //第一个线程
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        /*
            main-->0
            main-->1
            main-->2
            Thread-0-->0
            Thread-0-->1
            Thread-0-->2
         */
    }

}
 实现Runnable接口创建多线程程序的好处:
1. 避免了单继承的局限性
		一个类只能继承一个类(一个人只能有一个亲爹),(但接口可以多个),
		类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其他的类,
		实现其他的接口
2. 增强了程序的扩展性,降低了程序的耦合性(解耦)
		把设置线程任务和开启新线程进行了分离,可以写新的Runnable实现类来实现不同的任务
总之,第一种方法多个Thread子类,开启多线程,是用了继承的方式
		第二种方法多个Runnable实现类,开启多线程,是用了接口的方式

线程安全问题

简述:三个窗口同时买1-100号票,可能出现买同一张票的情况
在这里插入图片描述

线程安全问题买票代码实现
public class RunnableImpl implements Runnable {
    private int ticket = 20;
    @Override
    public void run(){
        while(ticket>0){
        try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
         if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
                ticket--;
        }
        }
    }
}
public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }
}
线程安全问题解决方法(同步代码块)
同步代码块解决线程安全问题:
   格式:
   synchronized(锁对象){
        可能出现线程安全问题的代码块(访问共享数据的代码)
   }
   注意:
        1.同步代码块中的锁对象,可以使用任意的对象
        2.但是必须保证多个线程使用的锁对象是同一个
        3.锁对象作用:
                ==把同步代码块锁住,只让一个线程在同步代码块中执行==

把同步代码块锁住,只让一个线程在同步代码块中执行

public class RunnableImpl implements Runnable {
    private int ticket = 20;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务卖票
    @Override
    public void run(){
        while(ticket>0){
            synchronized (obj){
                //先判断票是否存在
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
                    ticket--;//票在卖,ticket--
                }
            }
        }
    }
}

public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }
}

同步技术的原理:使用一个锁对象,这个锁对象叫做同步锁,也叫对象锁,或者叫对象监视器

同步代码块中的线程,执行前会获取锁对象,没有执行完毕不会释放锁对象,同步代码块外的线程没有锁对象进入不了同步代码块,会处于阻塞状态,等待锁对象被释放

同步方法解决线程安全问题
  使用同步方法解决线程安全问题:
        使用步骤:
            1.把访问了共享数据的代码抽取出来,放在一个方法中
            2.在方法中添加synchronized修饰符

   【注】:定义一个同步方法,同步方法会把方法内部的代码锁住,只让一个线程执行
        同步对象的锁对象是实现类对象 new RunnableImpl,也就是this
public class RunnableImpl implements Runnable {
    private  int ticket = 20;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务卖票
    @Override
    public void run(){
    //thisDemo05Threadsafe.RunnableImpl@1b6d3586与run对象的地址相同
    System.out.println("this"+this);
        while(true){
            payTicket();
            }
        }
    public  synchronized void payTicket(){
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
            ticket--;//票在卖,ticket--
        }
    }
}

静态的同步方法
        静态的同步方法锁对象不能是this,this是创建对象之后产生的,
        静态方法优于对象(先进入内存中)
        静态方法的锁对象是本类的class属性-->class文件(反射)
public class RunnableImpl implements Runnable {
    private static int ticket = 20;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务卖票
    @Override
    public void run(){
        while(true){
            payTicket();
            }
        }
    //静态方法要调用静态对象
    public static synchronized void payTicket(){
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
            ticket--;//票在卖,ticket--
        }
    }
}

Lock锁解决线程安全问题
java.util.concurrent.l.Locks.Lock接口
使用Lock锁解决线程安全问题:
    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作

Locks接口常用方法:
    void Lock();  获取锁
    void unLock(); 释放锁

使用步骤:
    1.在成员位置创建一个ReentrantLock对象
    2.在可能出现安全问题的代码前调用Lock接口中的方法Lock获取锁
    3.在可能出现安全问题的代码后调用Lock接口中的方法unLock释放锁
public class RunnableImpl implements Runnable {
    private  int ticket = 20;

    Lock l  =new ReentrantLock();

    //设置线程任务卖票
    @Override
    public void run(){
         System.out.println("this"+this);
        while(true){
            l.lock();
            if(ticket>0){
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
                    ticket--;//票在卖,ticket--
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    l.unlock();//无论程序是否出现异常,都会把锁释放,会提高程序的效率
                }
            }
        }
 }
}

第三章 线程状态

在这里插入图片描述

3.1 等待唤醒案例

等待唤醒案例:线程之间的通信
        1.创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃CPU的执行,
        进入到waitting状态(无限等待状态)
        2.创建一个老板线程(生产者): 花落5秒做包子,做好包子后,调用notify方法,唤醒顾客线程

注意:
    顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
    同步使用的锁对象必须保证唯一
    只有锁对象才能调用wait()和notify方法

 Object类中的方法
  void wait()
        导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
  void notifyAll()
        唤醒正在等待对象监视器的所有线程。 
public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //创建一个锁对象,并保证锁唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    System.out.println("告诉老板我要多少个包子");
                    //调用wait方法,放弃cpu执行,进入到WAITTING状态
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子已经做好了,开冲");
                }
            }
        }.start();
        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //花费时间做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("包子做好,唤醒消费者线程");
                    obj.notify();//如果有多个线程,则会随机唤醒其中之一
                }
            }
        }.start();

    }
}

告诉老板我要多少个包子
包子做好,唤醒消费者线程
包子已经做好了,开吃

Object类中的wait(long m)方法

进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之间,没有被notify唤醒,则会自动醒来,线程进入到Runnable/Blocked状态

唤醒的方法:
    1.notify() 唤醒正在等待对象监视器的单个线程。
    2.notifyAll() 唤醒正在等待对象监视器的所有线程。
public class Demo02WaitAndNotify {
    public static void main(String[] args) {
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    System.out.println("顾客1告诉老板我要多少个包子");
                    //调用wait方法,放弃cpu执行,进入到WAITTING状态
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子已经做好了,顾客1开吃");
                }
            }
        }.start();
        //创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    System.out.println("顾客2告诉老板我要多少个包子");
                    //调用wait方法,放弃cpu执行,进入到WAITTING状态
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子已经做好了,顾客2开吃");
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //花费时间做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("包子做好,唤醒消费者线程");
                    obj.notifyAll();//可以把所有等待的线程唤醒
                }
            }
        }.start();
    }
}

等待与唤醒机制(案例)

在这里插入图片描述

包子类

   资源类:包子
   设置包子属性:皮,陷,
   包子的状态:有true,无false

public class BaoZi {
    //皮,陷
    String pi;
    String xian;
    boolean flag = false;

}

包子铺类

 生产者(包子铺)类:是一个线程类,可以继承Thread
   设置线程任务(run):生产包子
   对包子的状态进行判断
   true :
        包子铺调用wait方法进入到等待状态
   false :
         包子铺生产包子
         增加一些趣味性:交替生产两种包子
                        有两种状态(i%2==0)
         包子铺生产好包子
         修改包子状态为true有
         唤醒吃货线程,让吃货线程吃包子

    注意:
        包子铺线程和包子线程关系-->通信(互斥)
        必须同时同步技术保证两个线程只能有一个在执行
        锁对象必须保证唯一,可以使用包子对象作为锁对象
        包子铺类和吃货类就需要把包子对象作为参数传递进来
            1.需要在成员位置创建一个包子变量
            2.使用带参数构造方法,为这个包子变量赋值
//生产者(包子铺)类:是一个线程类,可以继承Thread
public class BaoZiPu extends Thread{
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;
    //2.使用带参数构造方法,为这个包子变量赋值
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }
    //设置线程任务(run):生产包子
    public int count = 0;

    @Override
    public void run() {
        while(true){
            //以包子对象为锁对象
            synchronized (bz){
                if(bz.flag ==true){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("包子铺正在生产包子");
                if(count%2==0){
                    bz.pi = "薄皮";
                    bz.xian = "三鲜馅";
                }else{
                    bz.pi = "冰皮";
                    bz.xian = "牛肉大葱馅";
                }

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好包子
                //修改包子状态为true有
                count++;
                bz.flag =true;
                System.out.println("包子铺已经生产好了"+bz.pi+bz.xian+"包子");
                //唤醒吃货线程,让吃货线程吃包子
                bz.notify();

            }
        }

    }
}

吃货类

 消费者类(吃货类):是一个线程,可以继承Thread
    设置线程任务run:吃包子
    对包子的状态进行判断:
    false:
        吃货线程调用wait进入等待状态
    true:
        吃包子(counter--)
        包子.flag=false;
        唤醒包子铺线程
        bz.notify();
public class ChiHuo extends Thread{
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;
    //2.使用带参数构造方法,为这个包子变量赋值
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag==false){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    System.out.println("开始吃包子");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子已经吃完了");
                    bz.flag=false;
                    bz.notify();
                    System.out.println("---------------");
                }
            }
        }
    }
}

测试类

public class test {
    public static void main(String[] args) {
        BaoZi bz = new BaoZi();
        BaoZiPu bzp = new BaoZiPu(bz);
        ChiHuo ch = new ChiHuo(bz);

        bzp.start();
        ch.start();
    }
}

线程池

如果并发地线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,
这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
java线程池可以解决这种情况

在这里插入图片描述

在这里插入图片描述

Lambda表达式

函数式编程思想

面向对象的思想: 
		做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
函数式编程思想:
		只要能获取到结果,谁去做的不重要,怎么做的不重要,重要的是结果,不重视过程


 Lambda表达式的格式:
        (参数列表) -> {一段重写的代码}
        说明:():接口中抽象方法的参数列表
            {}:重写接口的抽象方法的方法体
public class Demo01Lambda {
    public static void main(String[] args) {
        //使用匿名内部类的方法实现多线程
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"新的线程创建了");
            }
        }).start();
        //使用Lambda表达式实现多线程
        //使用()->代替过程,直接把所需要达到的目的传递给线程,
        //()内是参数列表,->表示传递
        new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"新的线程创建了");
        }).start();
    }
}

Lambda表达式无参的案例

public class Demo01Cook {
    public static void main(String[] args) {
        invokeCook(()->{System.out.println("吃饭了");});


        invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("吃饭了");
            }
        });
    }
    public  static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

Lambda表达式有参的案例

Person类重写了toString方法

public class Demo01Array {
    public static void main(String[] args) {
        //使用数组存储多个Person对象
        Person[] arr = {new Person("爱丽丝",18),
                        new Person("魔理沙",17),
                new Person("四季映姬",16)
        };

        //根据年龄升序排序
        /Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        //使用Lambda简化匿名内部类
        //因为是有参数传递的,所以是有返回值的
        Arrays.sort(arr,(Person o1, Person o2)->{return o1.getAge()-o2.getAge();});


        //增强for循环遍历arr数组
        for (Person p : arr) {
            System.out.println(p);
        }
    }
}

【例2】

public interface Calculator {
    public abstract int calc(int a,int b);
}
/*
    给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值
 */
public class Demo01Lambda {
    public static void main(String[] args) {
        invokeCalc(10, 20, new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a+b;
            }
        });
        //只要是匿名内部类,就可以使用Lambda方法简化
        invokeCalc(20,30,(a,b)->{return a+b;});
    }
    public static void invokeCalc(int a,int b,Calculator c){
        int sum = c.calc(a,b);
        System.out.println(sum);
    }
}

Lambda的省略格式

可以省略的内容:
         1.(参数列表):括号中参数列表的参数类型可以省略
         2.(参数列表):括号中如果只有一个参数,则可以省略括号
         3.{一些代码}:如果代码只有一行,则无论是否有返回值都可以省略{},return,分号
         且三个部分必须同时省略
invokeCalc(20,30,(a,b)->{return a+b;});
invokeCalc(20,30,(a,b)-> a+b);
【注】:1.使用Lambda必须具有接口(有些是内置的接口),且要求接口中有且仅有一个抽象方法
		2.方法的参数或局部变量类型必须为Lambda对应的接口类型,
		才能使用Lambda作为该接口的实例

File类(主要用于操作文件和文件夹)

文件和目录路径名的抽象表示

File类:
java把电脑中的文件和文件夹(目录)封装为一个File类,我们可以使用File类对文件和文件夹进行操作
可用于:创建一个文件/文件夹
删除文件/文件夹
获取文件/文件夹
判断文件/文件夹是否存在
对文件夹进行遍历
获取文件的大小
【注】:File类是与系统无关的类,任何的操作系统都可以使用这个类中的方法
重点:记住这三个单词
file:文件
directory:文件夹/目录
path:路径

File内的四个静态成员变量

   File内的四个静态成员变量
    static String pathSeparator 与系统相关的路径分隔符字符,为方便起见,表示为字符串
    static char pathSeparatorChar 与系统相关的路径分隔符。

    static String separator 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串
    static char separatorChar 与系统相关的默认名称分隔符。
public class Demo01File {
    public static void main(String[] args) {
        String pathSeparator = File.pathSeparator;
        System.out.println(pathSeparator);//路径分隔符 windows:分号;  Linux:冒号:

        String  Separator = File.pathSeparator;
        System.out.println(Separator);//文件名分隔符,windows:反斜杠\  linux:正斜杠/
    }
}

绝对路径与相对路径

绝对路径:是一个完整的路径
    以盘符(c:,D:)开始的路径
    C:\\a.txt
    D:\\IdeaProjects\\itcast\\123.txt

相对路径:是一个简化的路劲
    相对于当前项目的根目录(D:\\IdeaProjects\\itcast\\123.txt),
    对于使用当前项目根目录的文件,路劲可简写:(D:\\IdeaProjects\\itcast\\123.txt->123.txt)
【注意】:1.路劲不区分大小写
       2.在windo中\表示转义字符,需要\\表示一个\

File类中的构造方法

File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

.

File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
    参数:
        pathname:字符串路劲名称
        创建File类对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
public class Demo02File {
    public static void main(String[] args) {
        show1();
    }

    private static void show1() {
        File f1 = new File("D:\\IdeaProjects\\itcast\\a.txt");//错的绝对路径
        System.out.println(f1);

        File f2 = new File("D:\\IdeaProjects\\itcast");//对的绝对路径
        System.out.println(f2);

        File f3 = new File("b.txt");//相对路径
        System.out.println(f3);
        /*
            D:\IdeaProjects\itcast\a.txt
            D:\IdeaProjects\itcast
            b.txt
         */
    }
}
public static void main(String[] args) {
        show1();
        show2("c:\\","a.txt");
    }

    private static void show2(String parent ,String child) {
        /*
        File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
        parent:父路径
        child:子路径
        */ 
       
        File f2 = new File(parent,child);
        System.out.println(f2);
    }
 public static void main(String[] args) {
        show1();
        show2("c:\\","a.txt");
        File f4 = new File("c://");
        show3(f4,"d.txt");
    }

    private static void show3(File parent, String child) {
        //File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
        File f3 = new File(parent,child);
        System.out.println(f3);
    }

File中的常用方法

 常用方法:
        1.long    length() 返回由此抽象路径名表示的文件的大小。返回文件
        2.String    getPath() 将此抽象路径名转换为路径名字符串。路径是什么样就返回什么样
        3.String   getName() 返回由此抽象路径名表示的文件或目录的名称。获取路径结尾部分
        4.File    getAbsoluteFile() 返回此抽象路径名的绝对形式。必定返回绝对路径
private static void show01() {
        //File    getAbsoluteFile() 返回此抽象路径名的绝对形式。
        File f1 = new File("D:\\IdeaProjects\\itcast\\a.txt");
        File absoluteFile = f1.getAbsoluteFile();
        System.out.println(absoluteFile);

        File f2 = new File("a.txt");
        File absoluteFile1 = f2.getAbsoluteFile();
        System.out.println(absoluteFile1);//重写了toString方法
        /*
            D:\IdeaProjects\itcast\a.txt
            D:\IdeaProjects\itcast\a.txt
         */
    }

File中判断功能的方法

File判断功能的方法:
      -boolean   exists() 测试此抽象路径名表示的文件或目录是否存在。
      -boolean isDirectory() 测试此抽象路径名表示的文件是否为目录(文件夹),是否以文件夹结尾。
      -boolean isFile() 测试此抽象路径名表示的文件是否为普通文件,是否是以文件结尾。
public class Demo04File{
    public static void main(String[] args) {
        show1();
        show2();
    }
    private static void show2() {
        File f3 =new File("D:\\IdeaProjects\\itcast");
        //判断文件是否存在
        if(f3.exists()){
            System.out.println(f3.isDirectory());//true
            System.out.println(f3.isFile());//false
        }
    }

    private static void show1() {
        File f1 = new File("D:\\IdeaProjects\\itcast");//true
        System.out.println(f1.exists());

        File f2 = new File("D:\\IdeaProjects\\itcast\\shuan");//false
        System.out.println(f2.exists());
    }
}

File类中创建与删除的方法

1.boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,原地创建一个由该抽象路径名命名的新的空文件。(只能用于创建文件)
  2.boolean delete() 删除由此抽象路径名表示的文件或目录。
  3.boolean mkdir() 创建由此抽象路径名命名的目录。
  4.boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
public class Demo05File {
    public static void main(String[] args) throws IOException {
        show01();
        show02();
    }

    private static void show02() {
        File f2 =new File("itcast\\src\\Demo13File\\aaa");
        System.out.println(f2.mkdir());

        File f3 = new File("itcast\\src\\Demo13File\\111\\222\\333");
        System.out.println(f3.mkdirs());
        System.out.println(f3.delete());
    }

    private static void show01() throws IOException {
        //createNewFile(),只用于创建文件
        File f1 =new File("D:\\IdeaProjects\\itcast\\src\\Demo13File\\1.txt");
            boolean f = f1.createNewFile();
            System.out.println(f);
        System.out.println(f1.delete());//直接在硬盘上删除,不走回收站;

        File f2 =new File("itcast\\src\\Demo13File\\3.txt");
        boolean fz = f2.createNewFile();
        System.out.println(fz);
        System.out.println(f2.delete());
    }
}

File的遍历操作

1.String[] list() 返回一个字符串数组,表示的目录中的所有子文件和目录。
2.File[] listFiles() 返回一个抽象路径名数组,表示该路径名表示的目录中的所有子文件和目录

若给出的目录的路径不存在或者不是目录,则抛出空指针异常

public class Demo06File {
    public static void main(String[] args) {
        //show01();
        show02();
    }

    private static void show02() {
        File f1 = new File("D:\\IdeaProjects\\itcast\\src\\Demo13File");
        File[] files = f1.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }

    private static void show01() {
        File f1 = new File("D:\\IdeaProjects\\itcast\\src\\Demo13File");
        String[] list = f1.list();
        for (String s : list) {
            System.out.println(s);
        }
    }

递归

概念,分类

直接递归:
       main(){
        a();
       }
       a(){
            a();
       }
间接递归:
       main(){
            b();
       }
       b(){
            c();
       }
       c(){
            b();
       }

  注意事项:
        1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出
        2.在递归中虽然有限定条件,但是递归次数不能太多次,否则也会发生栈内存溢出
        3.构造方法,禁止递归。
  递归的使用前提:
        当调用方法的时候,方法的主体不变,每次调用方法的参数不同,就可以使用递归
public class Demo14DiGui {
    public static void main(String[] args) {
            a(1);
    }
    /*
         构造方法,禁止递归
                编译报错:构造方法是创建对象使用的,一直递归,会产生无数个对象,直接编译报错
     */
    public Demo14DiGui(){
        Demo14DiGui();
    }

    private static void a(int i) {
        System.out.println(i);
        //限定条件
        if(i==10000){
            return;
        }
        a(++i);
    }
}

递归解决1到n之和

递归常用思想:
1+2+3+…+n等同于
n+(n-1)+(n-2)+…+1;

public class Demo01DiGui {
    public static void main(String[] args) {
        System.out.println("请输入n的值");
        Scanner sc =new Scanner(System.in);
        int n = sc.nextInt();
        int sum = sum(n);
        System.out.println(sum);
    }
    /*
      递归常用思想:
        1+2+3+...+n等同于
        n+(n-1)+(n-2)+...+1;
     */
    private static int sum(int n) {
        if(n==1) {
            return 1;
        }
        return n+sum(n-1);
    }
}

在这里插入图片描述

递归求阶乘

递归常用思想:
n! = n*(n-1)!

public class Demo02DiGui {
    public static void main(String[] args) {
        System.out.println("请输入n的值");
        Scanner sc =new Scanner(System.in);
        int n = sc.nextInt();
        int sum = sum(n);
        System.out.println(sum);
    }

    private static int sum(int n) {
        if(n==1){
            return 1;
        }
        return n*sum(n-1);
    }
}

递归打印多级目录

public class Demo03DiGui {
    public static void main(String[] args) {
        File file = new File("D:\\IdeaProjects\\itcast\\src");
        getAllFile(file);
    }

    private static void getAllFile(File file) {
        File[] files = file.listFiles();//获取该文件夹内的所有文件
        for (File file1 : files) {
            if(file1.isDirectory()){//判断是否是文件夹
                getAllFile(file1);
            }else{
                System.out.println(file1);
            }
        }
    }
}

递归打印以.java结尾的文件名

练习:
    递归打印多级目录
需求:
    遍历文件夹,及其文件夹的子文件夹
    只输出.java文件
public class Demo15FileSearch {
    public static void main(String[] args) {
        File file  = new File("D:\\IdeaProjects\\itcast\\src");
        getAllFile(file);
    }

    private static void getAllFile(File dir) {
        File[] file = dir.listFiles();
        for (File f : file) {
            if(f.isDirectory()){
                getAllFile(f);
            }else {
                //1.把文件转换为字符串

                //String path = f.getPath();//默认调用的是toString方法
                //String s = f.toString();//会输出完整路径
                String name = f.getName();//abc.java只返回文件名

                //把字符串转换为小写
                name = name.toLowerCase();

                //2.调用String类中的方法endsWith()方法判断字符串是否是以.java结尾
                boolean b = name.endsWith(".java");
                if (b){
                    System.out.println(name);
                }

               /*
                    if(f.getName().toLowerCase().endsWith(".java")){
                        System.out.println(name);
                        }
                */
            }
        }

    }
}

文件过滤器

使用过滤器来对文件过滤
File[] listFiles(FileFilter filter)
java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器。
    作用:用来过滤文件(File文件)
    抽象方法:
        boolean accept(File pathname) 测试指定抽象路径名是否应该包含在某个路径名列表中。
        参数:
            File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象

File[] listFiles(FilenameFilter filter)
java.io.FilenameFilter接口
       作用:用于过滤文件的名称
       抽象方法:用来过滤文件的方法
              boolean accept(File dir, String name)
              测试指定文件是否应包含在文件列表中。 
                参数 
                dir - 找到该文件的目录。 
                name - 文件的名称。 
                结果 
                true当且仅当该名称应包含在文件列表中时; false否则。

过滤器原理:
listFiles方法自动会调用FileFilterImpl过滤器中的accept方法
把dir中的每一个文件或者文件夹传递给accept中的pathname
如果accept方法返回的是true,则会将该pathname传递给f【】数组中保存下来
所以可以在accept方法中书写过滤原则

过滤器

public class FileFilterImpl implements FileFilter {
    @Override
    public boolean accept(File pathname) {
        if (pathname.isDirectory()){
            return true;
        }else{
            return pathname.getName().toLowerCase().endsWith(".java");
        }
    }
}

主程序

public class Demo04Filter {
    public static void main(String[] args) {
        File file = new File("D:\\IdeaProjects\\itcast\\src");
        getAllFile(file);
    }
    /*
        过滤器原理:
                listFiles方法自动会调用FileFilterImpl过滤器中的accept方法
                把dir中的每一个文件或者文件夹传递给accept中的pathname
                如果accept方法返回的是true,则会将该pathname传递给f【】数组中保存下来
                所以可以在accept方法中书写过滤原则
     */
    private static void getAllFile(File dir) {
        File[] f = dir.listFiles(new FileFilterImpl());
        for (File file : f) {
            if (file.isDirectory()){
                getAllFile(file);
            }else{
                String name = file.getName();
                System.out.println(name);
            }
        }
    }
}

【例二】

public class Demo04Filter {
    public static void main(String[] args) {
        File file = new File("D:\\IdeaProjects\\itcast\\src");
        getAllFile(file);
    }
    /*
        过滤器原理:
                listFiles方法自动会调用FileFilterImpl过滤器中的accept方法
                把dir中的每一个文件或者文件夹传递给accept中的pathname
                如果accept方法返回的是true,则会将该pathname传递给f【】数组中保存下来
                所以可以在accept方法中书写过滤原则
     */
    private static void getAllFile(File dir) {
        //采用FileFilter过滤器
       File[] f = dir.listFiles(new FileFilterImpl(){
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory()||pathname.getName().toLowerCase().endsWith(".java");
            }
        });
		//采用FileFilter过滤器并用Lambda表达式简写
        File[] f = dir.listFiles((File pathname)->{
            return pathname.isDirectory()||pathname.getName().toLowerCase().endsWith(".java");
        }
        );
        
        //采用FilenameFilter过滤器
        File[] f2 = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                //File(dir,name)其中dir是头文件名,name是尾文件名,合在一起组成文件名
                return new File(dir,name).isDirectory()||name.toLowerCase().endsWith(".java");
            }
        });
        for (File file : f2) {
            if (file.isDirectory()){
                getAllFile(file);
            }else{
                String name = file.getName();
                System.out.println(name);
            }
        }       
    }
}

IO流

在这里插入图片描述

i:input 输入(读取)
o:output 输出(写入)
流:数据流

字节流

字节输出流【OutputStream】(给文件写内容)

java.io.OutputStream:此抽象类是表示输出字节流的所有类的超类

以下是其子类都可用的方法:
        void close() 关闭此输出流并释放与此流相关联的任何系统资源。
        void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。
        void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。
        void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
        abstract void write(int b) 将指定的字节写入此输出流。

 public class FileOutputStream extends OutputStream
 构造方法:
        FileOutputStream(File file) 创建文件输出流以写入由指定的 File对象表示的文件。
        FileOutputStream(String name) 创建文件输出流以指定的名称写入文件。

        参数:写入文件的目的地(输出流,即把数据输出到目的地)
            String  name:目的地是一个文件的路径
            File file:目的地是一个文件

        构造方法的作用:
            1.创建一个FileOutputStream对象
            2.会根据构造方法中传递的文件/文件路径,创建一个空的文件
            3.会把FileOutputStream对象指向创建好的文件

        写入数据的原理(内存-->硬盘)
                java程序-->JVM虚拟机-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中

字节输出流的使用步骤:
1.创建一个FileOutputStream对象,构造方法中传递写入数据的路径
2.调用FileOutputStream对象中的方法write,把数据写入到文件中
3.释放资源(流使用会占用一定资源,使用后要清空内存,提高程序效率)

public class Demo01OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\itcast\\src\\Demo15IOStream\\a.txt");
        fos.write(105);//在此写入会重新覆盖
        fos.close();
    }
}

在这里插入图片描述

给文本写入多个字节

 void write(byte[] b) 将 b.length个字节从指定的字节数组写入此文件输出流。
        一次写入多个字节:
            如果写的第一个字节是正数(0-127),那么显示的时候回查询ASCII表
            如果写的第一个字节是负数,那第一个字节会和第二个字节,两个字节组成一个中文显示。查询系统默认码表(GBK)

void write(byte[] b, int off, int len) 把字节数组的一部分写入到文件中。
        off:开始索引
        len:写几个
/*
     一次写多个字节
 */
public class Demo02OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\itcast\\src\\Demo15IOStream\\a.txt");
            
        byte[] bytes = {-65,-66,-67,-68,-69};//烤郊
        fos.write(bytes);
        fos.close();
        
        byte[] bytes1 ="你好".getBytes();
        fos.write(bytes1);//你好
        fos.close();
    }
}

字节输出流的续写和换行

构造方法:
    1.FileOutputStream(File file, boolean append) 
    创建一个向指定name文件中写入数据的文件输出流
    2.FileOutputStream(String name, boolean append) 
    创建一个向指定File对象表示的文件中写入数据的文件输出流

参数:
file:目标文件
name:目标文件的字符串名
append:是否续写开关
true:在源文件末尾续写文件
false:创建一个新文件,覆盖源文件

    写换行:写换行符号
    windows:\r\n
    linux:/n
    mac:/r
public class Demo03OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\itcast\\src\\Demo15IOStream\\a.txt",true);
        byte[] bytes ="加油".getBytes();
        for (int i = 0; i < 10; i++) {
            fos.write(bytes);
            fos.write("/r/n".getBytes());
        }
        fos.close();
    }
}

字节输入流

java.io.InputStream :字节输入流

子类共性方法:
        abstract int read() 从输入流读取数据的下一个字节。
        int read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区b。
        void close() 关闭此输入流并释放与流相关联的任何系统资源。
常用子类:
    public class FileInputStream extends InputStream
作用:吧硬盘中的文件中的数据,读取到内存中使用

构造方法:
      FileInputStream(File file)通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的 File对象file命名。
      FileInputStream(String name)通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。
构造方法作用:
    读取文件的数据流
    1.会创建一个FileInputStream的对象
    2.会把FileInputStream对象指向构造方法中要读取的文件

读取数据的原理(硬盘-->内存):
        java程序-->JVM虚拟机-->OS(操作系统)-->OS调用读取数据的方法-->读取文件

读取文件步骤:
			1.

调用构造方法会自动把指针指向文件第一位

每使用read()一次指针会往后移一位

public class Demo04InputStream {
    public static void main(String[] args) throws IOException {
        FileInputStream fis  =new FileInputStream("itcast\\src\\Demo15IOStream\\a.txt");
        //int read()读取文件的一个字节并返回,若读取到文件末尾则返回-1
        int len = 0;
        while((len = fis.read())!= -1){
            System.out.println((char)len);//abc
        }
        fis.close();
    }
}

字节输入流读取多个字节

字节输入流一次读取多个字节的方法:
    int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中

 String(byte[] bytes):把字节数组转换为字符串
 String(byte[] bytes, int offset, int length):把字节数组的一部分转换为字符串
public class Demo05InputStream {
    public static void main(String[] args) throws IOException {
        //创建FillInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("itcast\\src\\Demo15IOStream\\a.txt");

        //使用FileInputStream对象中的方法read读取文件
        //int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
        byte[] bytes = new byte[2];
        int len  = fis.read(bytes);//把fis指向的文件读取到bytes数组中
        System.out.println(len);//2
        System.out.println(Arrays.toString(bytes));//[65, 66]
        System.out.println(new String(bytes));//AB

        /*
            String(byte[] bytes):把字节数组转换为字符串
            String(byte[] bytes, int offset, int length):把字节数组的一部分转换为字符串
         */
        //释放资源
        fis.close();
    }
}

文件复制

在这里插入图片描述
文件复制练习:一读一写
只要是文件,就可以用字节流读写文件

public class Demo06CopyFile {
    public static void main(String[] args) throws IOException {
        long s =System.currentTimeMillis();

        FileOutputStream fos = new FileOutputStream("D:\\1.jpg");
        FileInputStream fis = new FileInputStream("C:\\1.jpg");
        int len =0;
        byte[] bytes = new byte[65536];
        //利用一个byte型数组,使得一次读写可以读取多个bit,不用大量调用while循环
        //len = -1时文件已经读完
        while((len = fis.read(bytes))!=-1){

            fos.write(bytes);
        }
        //释放资源(先关闭写的,在关闭读的,因为写完了,肯定读完了)
        fos.close();
        fis.close();
        long e = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(e-s)+"ms");
    }
}

字符流

字符输入了以字符为单位读取文件,便于读取中文文本,因为,一个中文字符占多个字节

字符输入流【Reader】

java.io.Reader:字符输入流

共性的成员方法:
     1.int read() 读一个字符
     2.int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
     3.abstract void close() 关闭流并释放与之相关联的任何系统资源。

java.io.FileReader extends InputStreamReader extends Reader
FileReader:文件字符输入流
作用:把硬盘文件中的数据以字符的方式读取到内存中

构造方法:
        FileReader(File file)创建一个新的FileReader,给出 File读取。
        FileReader(String fileName) 创建一个新的FileReader,读取fileName同名文件。

FileReader构造方法的作用:
    1.创建一个FileReader对象
    2.会把FileReader对象指向要读取的文件
public class Demo07Reader {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("itcast\\src\\Demo15IOStream\\a.txt");

        int len = 0;
        //调用char类型数组装数据避免多次调用循环
        char[] c =new char[1024];
        while((len = fr.read(c)) != -1){
            //String类中有一个构造方法可以把字符型数组转化为字符串
            //String(char[] value, int offset, int count)
            //offset开始位置,count有效个数
            System.out.println(new String(c,0,len));
        }

        fr.close();
    }
}

字符输出流

java.io.writer:字符输出流

    共性的成员方法:
            abstract void close() 关闭流,先刷新。
            abstract void flush()刷新流。
            void write(char[] cbuf)写入一个字符数组。
            abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分。
            void write(int c)写一个字符
            void write(String str)写一个字符串
            void write(String str, int off, int len)写一个字符串的一部分。
            Writer append(char c) 将指定的字符附加到此文件。

    public class FileWriter extends OutputStreamWriter:
        FileWriter:文件字符输入流
        作用:把内存中字符数据写入到文件中

    构造方法:
        1.FileWriter(File file) 给一个File对象构造一个FileWriter对象。
        2.FileWriter(String fileName) 构造一个给定文件名的FileWriter对象。
    构造方法:
        1.会创建一个FileWriter对象
        2.会根据构造方法中传递的文件/文件路径,创建文件
        3.会把FileWriter对象指向创建好的文件

    使用步骤:
        1.创建FileWriter对象,构造方法中绑定要写入的数据的目的地
        2.使用FileWriter中的方法write,把数据写入到内存缓冲区(字符)
        3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中(close方法自带flush)
        4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
public class Demo08Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw  =new FileWriter("itcast\\src\\Demo15IOStream\\a.txt");
        fw.write("你好世界");
        fw.flush();//刷新内存缓冲区
        fw.write(97);
        fw.write(new char[]{'1','2','3'});
        fw.close();//close方法之后流已经关闭,不能再写入数据
    }
}

input/output 输入/输出 -->读/写–>read/write

字符输出流的续写和换行

字符输出流续写:
    1.FileWriter(File file, boolean append) boolean为true时续写,false时非续写
    2.FileWriter(String fileName, boolean append)
换行:
    windows:\r\n
    linux:/n
    mac:/r
public class Demo09Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt",true);
        fw.write("\r\n"+"你好");
        fw.write("\r\n"+"我吐了");
        fw.close();
    }
}

使用try…catch…finally处理文件异常

public class IOtryCatch {
    public static void main(String[] args) {
        //提高变量fw的作用域,让finally可以使用
        //fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt",true);
        //若执行失败,则fw没有值,fw.close会报错
        FileWriter fw=null;
        try {
            fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt",true);
            for (int i = 0; i < 5; i++) {
                fw.write("\r\n"+"Hello World");
            }
        }catch (IOException e) {
                System.out.println(e);
        }finally {
            if(fw!=null){
                //如果创建对象失败,fw的默认的值就是null,null是不能调用方法的,会抛出空指针异常,所以需要增加一个判断,不是null在把资源释放
                try {
                    //fw.close()本身可能出现异常
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用try…catch…finally处理文件异常的改进方式

public class Demo10 {
    public static void main(String[] args) {
        //这个改进的异常处理方式,不用fis.close(),fos.close(),自带清除缓存
        try(FileInputStream fis = new FileInputStream("C:\\1.jpg");
            FileOutputStream fos = new FileOutputStream("D:\\1.jpg");)
        {
            int len = 0;
            byte[] bytes = new byte[1024];
            while((fis.read(bytes))!=-1){
                    fos.write(bytes);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

属性集

使用Propertes集合存储数据集(把数组内容存储进文件中store,把文件内容读取到数组中load)

 java.util.Properties集合extends Hashtable<k,v>
    可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
    可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用

Properties集合中一些操作字符串的特有的方法:
    1.String setProperty(String key) 调用Hashtable的方法put
    2.Object getProperty(String key, String value)通过key找到value的值,相当于Map集合中的get(key)方法
    3.Set<String> stringPropertyNames()返回属性列表中的键集,其中该键机器对应值是字符串,此方法相当于Map集合的keySet方法
public class Demo01Properties {
    public static void main(String[] args) {
        Properties prop = new Properties();

        prop.setProperty("miku","168");
        prop.setProperty("luka","165");
        prop.setProperty("aki","170");

        Set<String> set = prop.stringPropertyNames();

        for (String key : set) {
            String value = prop.getProperty(key);
            System.out.println(key+":"+value);
        }
    }
}
 可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        1.void store(Writer writer, String comments)
        2.void store(OutputStream out, String comments)
        参数:
            OutputStream out  字节输出流,不能有中文
            Writer writer  字符输出流,可以有中文
            String comments 注释,不能使用中文,默认为Unicode编码
            一般使用""空字符串
        使用步骤:
            1.创建Properties集合对象,添加数据
            2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
            3.释放Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
            4.释放资源
 private static void show02() throws IOException {
        Properties prop = new Properties();
        prop.setProperty("miku","168");
        prop.setProperty("luka","165");
        prop.setProperty("aki","170");

        FileWriter fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt");
        prop.store(fw,"sava data");
        fw.close();
    }

读取文件数据到集合中

 private static void show03() throws IOException {
        /*
            load方法与store方法类似:
                1.void load(InputStream inStream)
                2.void load(Reader reader)
            步骤:
                1.创建Properties集合
                2.使用Properties集合对象的方法load读取保存键值的文件
                3.遍历Properties
         */
        Properties prop = new Properties();
        FileReader fr =new FileReader("itcast\\src\\Demo15IOStream\\a.txt");
        prop.load(fr);
        Set<String> s = prop.stringPropertyNames();
        for (String key : s) {
            String value = prop.getProperty(key);
            System.out.println(key+":"+value);
        }
        fr.close();
    }

缓冲流

在这里插入图片描述

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率

java.io.BufferedOutputStream extends OutputStream
BufferedOutputStream:字节缓冲输出流

 继承自父类的共性成员方法:
        void close() 关闭此输出流并释放与此流相关联的任何系统资源。
        void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。
        void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。
        void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
        abstract void write(int b) 将指定的字节写入此输出流。

构造方法:
    1.BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    2.BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。
参数:
    OutputStream out:字节输出流,可以传递FileOutputStream等字节输出流
    int size:指定缓冲区内部缓冲区的大小,

使用步骤:
    1.创建一个字节输出流
    2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutStream对象效率
    3.使用BufferedOutputStream对象中的write方法,把数据写入到内部缓冲区中
    4.释放资源(会先调用flush方法刷新数据,把数据刷新到文件中)	
public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        long s1 = System.currentTimeMillis();
        FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
        BufferedOutputStream nos = new BufferedOutputStream(fos);
        nos.write("我把数据写入到内部缓冲区中".getBytes());
        nos.flush();
        nos.close();
        long s2 = System.currentTimeMillis();
        System.out.println((s2-s1)+"ms");
    }
}

缓冲流复制文件提高效率

public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        long s1 = System.currentTimeMillis();
        FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
        BufferedOutputStream nos = new BufferedOutputStream(fos);
        nos.write("我把数据写入到内部缓冲区中".getBytes());
        nos.flush();
        nos.close();
        long s2 = System.currentTimeMillis();
        System.out.println((s2-s1)+"ms");
    }
}

BufferedWriter字符缓冲输出流

 继承自父类的共性成员方法:
        abstract void close() 关闭流,先刷新。
        abstract void close() 关闭流,先刷新。
        abstract void flush()刷新流。
        void write(char[] cbuf)写入一个字符数组。
        abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分。
        void write(int c)写一个字符
        void write(String str)写一个字符串
        void write(String str, int off, int len)写一个字符串的一部分。
        Writer append(char c) 将指定的字符附加到此文件。

构造方法:
       1.BufferedWriter(Writer out)
       2.BufferedWriter(Writer out, int sz)
    参数:
        Writer out:字符输出流Writer
        int sz:缓冲区大小

特有的成员方法:
        void newLine() 写一个行分隔符。会根据不同的操作系统,获得不同的行分隔符
public class Demo03BufferedWriter {
    public static void main(String[] args) throws IOException {
        FileWriter fos = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt");

        BufferedWriter bw = new BufferedWriter(fos);
        bw.write("你吃饱了吗?");//writer系方法可以直接用字符串,outputStream系方法必须先getBytes()

        bw.close();

    }
}

BufferedReader字符缓冲输入流

java.io.BufferedReader extends Reader

继承自父类的方法:
    1.int read() 读一个字符
    2.int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
    3.abstract void close() 关闭流并释放与之相关联的任何系统资源。

构造方法:
    BufferedReader(Reader in)
    BufferedReader(Reader in, int sz)

参数:
   Reader in 字符缓冲输入流Reader系
   int sz:字符缓冲区大小

特有的成员方法:
    String readLine() 读一行文本。
    返回值:若读到最后一行,这返回null
public class Demo04BufferedReader {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("itcast\\src\\Demo15IOStream\\a.txt");
        BufferedReader br = new BufferedReader(fr);
        //第一种方式
        char[] c  =new char[1024];
        int len=0;
        while((len = br.read(c))!= -1){
            System.out.println(new String(c));
        }
        //第二种方式
        String line ;
        while(( line = br.readLine())!=null){
            System.out.println(line);
        }
        br.close();
    }
}

文本内容排序(以.为分隔符)

练习:
        对文本进行排序
    分析:
        1.创建一个HashMap集合,key存储序号,value存储内容
        2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
        3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
        4.使用字符缓冲输入流中的方法readLine,逐行读取文本(以\n换行符为分隔)
        5.对读取好的文本进行切割,获取行中的序号和文本内容
        6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序)
        7.遍历HashMap集合,获取每一个键值对
        8.把每一个键值对拼接成文本行
        9.通过字符输出流中的方法write,把拼接好的文本写入到文件中
        10.释放资源
public class Demo05Test {
    public static void main(String[] args) throws IOException {
        //准备好数组,字符输入/输出缓冲流
        HashMap<String,String> map = new HashMap<>();
        BufferedWriter bw = new BufferedWriter(new FileWriter("itcast\\src\\demo17Buffered\\a.txt"));
        BufferedReader br = new BufferedReader(new FileReader("itcast\\src\\Demo15IOStream\\a.txt"));

        String line ;
        //分隔行内容,并分别作为键和值添加到map集合中
        while((line = br.readLine())!=null){
            String[] arr = line.split("\\.");
            map.put(arr[0],arr[1]);
        }

        //待map集合将键排序好后(map集合会自动排序),将排序好后的内容写入目标文本中
        for (String key : map.keySet()) {
            String value = map.get(key);
            line = key+"."+value;
            bw.write(line);
            bw.newLine();
        }

        br.close();
        bw.close();
    }
}

转换流

ASCII:基本编码表
GBK:最常用的中文编码表
unicode:万国码,有UTF-8,UTF-16,UTF-32三种编码方案,
IDE使用的是UTF-8编码格式
windows系统中文版使用的是GBK,

转换流的原理

在这里插入图片描述

字节输入流FileInputStream只能读取UTF-8格式的文本,并且是两个字节两个字节读取
字符输入流FileReader也只能读取UTF-8格式的文本,并且是三个三个字节一读
InputStreamReader是字节流通向字符流的桥梁,可以把字节输入流FileInputStream转换为查询指定码表的输入流(GBK,UTF-8…)

OutputStreamWriter与InputStreamReader类似

OutputStreamWriter指定编码格式的字符输出流

java.io.OutputStreamWriter extends Writer
OutputStreamWriter:是字符流通向字节流的桥梁,可以使用指定的编码方式(GBK,UTF-8)

继承自父类的成员方法:
            abstract void close() 关闭流,先刷新。
            abstract void flush()刷新流。
            abstract void write(char[] char, int off, int len)写入字符数组的一部分。
            void write(String str, int off, int len)写一个字符串的一部分。
            Writer append(char c) 将指定的字符附加到此文件。

构造方法:
        OutputStreamWriter(OutputStream out, String charsetName)
        创建一个使用命名字符集的OutputStreamWriter。

参数:
    OutputStream out:字节输出流
     String charsetName:自定的编码表名称

使用步骤:
     1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
     2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码,转换为指定编码格式)
     3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中
     4.释放资源
public class demo06OutputStreamWrite {
    public static void main(String[] args) throws IOException {
        //writeUTF8();
        writeGBK();
    }

    private static void writeGBK() throws IOException {
        //OutputStreamWriter相当于一个转换器,把2字节UFT-8的读取文本方式转换为其他读取方式
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\GBK.txt"),"gbk");
        osw.write("坎公骑冠剑");
        osw.flush();
        osw.close();
    }

    private static void writeUTF8() throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\utf-8.txt"),"utf-8");
        osw.write("你好");
        osw.flush();
        osw.close();
    }
}

InputStreamReader指定编码格式的字符输入流

java.io.InputStreamReader extends Reader
InputStreamReader:是字节流通向字符流的桥梁,可以使用指定的编码方式(GBK,UTF-8)

继承自父类的成员方法:
    1.int read() 读一个字符
    2.int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
    3.abstract void close() 关闭流并释放与之相关联的任何系统资源。

构造方法:
        InputStreamReader(InputStream in, String charsetName)
        创建一个使用命名字符集的InputStreamReader。。

参数:
    InputStream in:字节输入流
    String charsetName:指定的编码表名称,不区分大小写

使用步骤:
     1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
     2.使用InputStreamReader对象中的方法read读取文件
     3.释放资源
public class Demo07InputStreamReader {
    public static void main(String[] args) throws IOException {
        read();
    }

    private static void read() throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("itcast\\src\\Demo15IOStream\\a.txt"),"utf-8");

        int len = 0;
        char[] c = new char[1024];
        //读取的内容会放置在c数组中
        while((len=isr.read(c))!=-1){
            System.out.println(c);
        }
        isr.close();

    }
}

转换文件编码

练习:转换文件编码
    将GBK编码的文本文件,转换为UFT-8编码的文本文件
public class Demo08test {
    public static void main(String[] args) throws IOException {
        //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定编码表GBK
        InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\我是GBK文本.txt"),"GBK");
        //2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表UTF-8
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\我是UTF-8文本.txt"),"UTF-8");

        //isr对象的方法read读取文件,数据存储打c数组,然后把c数组保存的内容写入目标文本
        int len  = 0;
        char[] c =new char[1024];
        while((len = isr.read(c))!=-1){
            osw.write(c);
        }
        isr.close();
        osw.close();
    }
}

序列化流

把对象以流的方式,写入到文本中保存,叫做序列化流(ObjectOutputStream)
把保存在文本中的对象,以流的形式读取出来,叫做反序列化流(ObjecInputStream)
在这里插入图片描述

序列化ObjectOutputStream(把实例对象写入文本中)

java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream:对象的序列化
作用:把对象以流的方式写入到文本中保存

构造方法:
    ObjectOutputStream(OutputStream out)
    创建一个写入指定的OutputStream的ObjectOutputStream。

特有的成员方法:
       void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。
使用步骤:
        1.创建ObjectOutputStream对象,构造方法中传递字节输入流
        2.使用ObjectOutputStream对象中的方法writeObject,把对象写入文件中
        3.释放资源
 NotSerializableException没有序列化异常
 Serializable接口也叫做标记型接口
        要进行序列化和反序列化的类必须实现Serializable接口,该接口会给类添加一个标记
            有:就可以序列化和反序列化
            无:就会抛出NotSerializableException异常
        相对于一种权限
public class demo01ObjectOutputStream {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("itcast\\src\\demo17Buffered\\a.txt"));
        Person p =new Person("博丽灵梦",16);
        oos.writeObject(p);

        oos.close();
    }
}

对象的反序列化ObjectInputStream

public class Demo02ObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("itcast\\src\\demo17Buffered\\a.txt"));
        Object person = ois.readObject();

        ois.close();
        System.out.println(person);
    }
}

transient关键字(被修饰的成员变量不能被序列化和反序列化)

static关键字:静态关键字
    静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
    被static修饰的成员变量不能被序列化,序列化对的都是对象
    静态不属于对象,他被所有对象所共享

transient关键字:瞬态关键字
        被transient关键字修饰的成员变量,不能被序列化
        例如:private transient int age;

InvalidClassException

在这里插入图片描述

问题:每次修改类的定义,都回给class文件生成一个新的序列号
解决方案:无论是否对类的定义进行修改,都不重新生成新的序列号,可以手动给类添加一个序列号
例:static final long seriaVersionUID = 42L

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    public   int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

序列化集合(保存多个对象)

练习:序列化集合
    当我们想保存多个对象的时候
    可以把多个对象存储到一个集合中
    对集合进行序列化和反序列化

 分析:
    1.定义一个存储Person对象的ArrayList集合
    2.在ArrayList集合中添加Person类
    3.创建并使用序列化流ObjectOutputStream对象中的方法writeObject,对集合进行序列化
    4.创建并使用反序列化流ObjectInputStream对象中的方法readObject读取文件中保存的集合
    5.把Object类型的集合转换为ArrayList类型
    6.遍历集合,释放资源
练习:序列化集合
        当我们想保存多个对象的时候
        可以把多个对象存储到一个集合中
        对集合进行序列化和反序列化

     分析:
        1.定义一个存储Person对象的ArrayList集合
        2.ArrayList集合中添加Person3.创建并使用序列化流ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        4.创建并使用反序列化流ObjectInputStream对象中的方法readObject读取文件中保存的集合
        5.Object类型的集合转换为ArrayList类型
        6.遍历集合,释放资源

PrintStream 打印流

java.io.PrintStream:打印流
    PrintStream:为其他输出流添加了功能,使他们能方便的打印各种数值表现形式
PrintStream特点:
    1.只负责数据的输出,不负责数据的读取
    2.PrintStream从不抛出IOException
    3.其特有的方法:
        print(任意类型的值)
        println(任意类型的值并换行)

    构造方法:
        1.PrintStream(File file)
        2.PrintStream(OutputStream out)
        3.PrintStream(String fileName)
    注意:
        如果使用的是继承自父类的write方法写数据,那么查看数据的时候回查询编码表,例如:97->a
        如果使用的是自带的方法print写数据,则数据原样输出,例如:97->97
public class Demo04PrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("itcast\\src\\demo17Buffered\\a.txt");
        ps.write(97);//a


        ps.println(97);
        ps.println(8.8);
        ps.println('a');
        ps.println("Hello World");
        ps.println(true);

        System.out.println("我在工作台输出");
        System.setOut(ps);//把输出语句的目的地改为打印流的目的地
        System.out.println("我在打印流的目的地输出");

        ps.close();
    }
}

TCP通信协议下客户端与服务器之间的通信

在这里插入图片描述

客户端

	TCP通信的客户端:向服务器发送请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:
    java.net.Socket:此类实现客户端套接字(也叫"套接字"),套接字是两台机器间通信的端点。
    套接字:包含了IP地址和端口号的网络单位

构造方法:
       Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号。
        参数:
            String host:服务器主机的名称/服务器的IP地址
            int port:服务器的端口号

成员方法:
        1.getInputStream() 返回此套接字的输入流。
        2.OutputStream getOutputStream() 返回此套接字的输出流。
        3.void close() 关闭此套接字

 实现步骤:
        1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        2.使用Socket对象中的方法getOutputStream()方法获取网络字节输出流OutputStream对象
        3.使用网络字节输出流OutputStream对象的方法write,给服务器发送数据
        4.使用Socket对象的方法getInputStream()获取网络字节输入流InputStream对象
        5.使用InputStream的方法read,读取服务器回写的数据
        6.释放资源
    注意:
        1.客户端和服务器进行交互,必须使用网络流Socket,不能用自己创建的流对象
        2.当我们穿件服务器对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
                这时如果服务器没有启动,那么就会抛出异常
                如果服务器已经启动,那么就可以进行交互了
public class TCPClient {
    public static void main(String[] args) throws IOException {
       Socket socket  = new Socket("127.0.0.1",8888);

        OutputStream os = socket.getOutputStream();

        os.write("你好服务器".getBytes());

        InputStream is = socket.getInputStream();
        byte[] b = new byte[1024];
        is.read(b);
        System.out.println(new String(b));

        os.close();
        socket.close();
    }
}

服务器端

TCP通信的服务器端:接受服务器的请求,给服务器写数据

服务器的实现步骤:
    1.创建服务器ServerSocket对象和系统指定的端口
    2.使用呢SerSocket对象中的方法accept,获取到请求的客户端对象Socket
    3.使用Socket对象的方法getInputStream()获取网络字节输入流InputStream对象
    4.使用InputStream的方法read,读取客户端传递的数据
    5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
    6.使用OutputStream对象中的方法write()给客户端回信息
    7.释放资源
public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        Socket a = ss.accept();
        InputStream is = a.getInputStream();

        byte[] b = new byte[1024];
        int len = is.read(b);
        System.out.println(new String(b,0,len));

        OutputStream os = a.getOutputStream();
        os.write("服务器已收到".getBytes());

        a.close();
        ss.close();
    }
}

综合案例:文件上传

在这里插入图片描述

文件上传的客户端

在这里插入图片描述

文件上传的服务器端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

服务器端

public class Service {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        while(true){
            /*
                这里使用多线程提高效率,
                有一个客户端上传文件,就开启一个线程,完成文件的上传
             */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        Socket socket = ss.accept();

                        InputStream is = socket.getInputStream();

                        File file =new File("D:\\upload");
                        if (!file.exists()){
                            file.mkdirs();
                        }
                        String FileName = "itcast"+System.currentTimeMillis()+ new Random().nextInt(9999)+".jpg";

                        FileOutputStream fos =new FileOutputStream(file+"\\"+FileName);

                        int len = 0;
                        byte[] b =new byte[1024];
                        while ((len = is.read(b))!=-1){
                            fos.write(b,0,len);
                        }
                        //is.read读取不到-1,因为不会传入数据中,read方法会进入到阻塞状态
                        //socket.shutdownInput()方法:read进入到阻塞状态则会是read方法结束
                        socket.shutdownInput();

                        OutputStream os = socket.getOutputStream();
                        os.write("上传成功".getBytes());

                        fos.close();
                    }catch (IOException e){
                        System.out.println(e);
                    }
                }
            }).start();

        }
        //服务器在死循环,一直开着服务器,可以不用关闭
        //ss.close();

    }
}

客户端

public class Client {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("c:\\1.jpg");
        Socket socket =new Socket("127.0.0.1",8888);
        OutputStream os = socket.getOutputStream();

        int len = 0;
        byte[] b =new byte[1024];
        while ((len= fis.read(b))!=-1){
            os.write(b,0,len);
        }
        //fis.read读取不到-1,因为不会传入数据中,read方法会进入到阻塞状态
        //socket.shutdownInput()方法:read进入到阻塞状态则会是read方法结束
        /*
        void shutdownOutput() 禁用此套接字的输出流。
        上传完数据之后,主动给服务器一个结束标志
         */
        socket.shutdownOutput();
         len = 0;
        InputStream is = socket.getInputStream();
        while ((len = is.read(b))!=-1){
            System.out.println(new String(b,0,len));
        }
        socket.shutdownInput();


        System.out.println("1");
        fis.close();
        socket.close();
    }
}

模拟B/S服务器

在这里插入图片描述

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8080);

        while(true){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            Socket socket= server.accept();
                            InputStream is = socket.getInputStream();

                            //把网络字节输入流转换为字符缓冲输出流
                            BufferedReader br = new BufferedReader(new InputStreamReader(is));
                            //把客户端请求信息的第一行读取出来GET /11_NET/web/index.html
                            String line  = br.readLine();
                            //切割读取信息的一部分/11_NET/web/index.html
                            String[] arr = line.split(" ");
                            //把路径前边的/去掉,进行截取11_NET/web/index.html
                            String htmlpath = arr[1].substring(1);

                            //创建一个本地的字节输入流,构造方法中绑定要读取的html路径
                            FileInputStream fis = new FileInputStream(htmlpath);
                            //使用Socket中的方法getOutputStream获取网络字节输出流OutputStream
                            OutputStream os = socket.getOutputStream();

                            //写入HTTP协议响应头,固定写法
                            os.write("HTTP/1.1.200 OK\r\n".getBytes());
                            os.write("Content-Type:text/html\r\n".getBytes());
                            os.write("\r\n".getBytes());

                            //一读一写复制文件,把服务器读取的html文件回写到客户端
                            int len = 0;
                            byte[] b =new byte[1024];
                            while ((len = fis.read(b))!=-1){
                                os.write(b,0,len);
                            }
                            //释放资源
                            fis.close();
                            socket.close();
                            server.close();
                        }
                        catch (IOException e){
                            System.out.println(e);
                        }

                    }
                }).start();
        }
    }
}

函数式接口

函数式接口(FunctionalInterface):有且只用一个抽象方法,称之为函数式接口
    当然接口中可以包含其他的方法(默认,静态,私有)
@Override注解
 作用:检查方法是否为重写的方法
    是:编译成功
    否:编译失败

 @FunctionalInterface注解
	作用:可以检测接口是否是一个函数式接口(是否只有一个抽象方法
@FunctionalInterface//此注解可以确定该函数式接口只有一个抽象函数
public interface MyFunctionalInterface {
    //定义一个抽象方法
    public abstract void method();

    
}

函数式接口的使用

public class Demo {
    public  static  void show(MyFunctionalInterface myinter){
        myinter.method();
    }

    public static void main(String[] args) {
        show(new MyFunctionalInterefaceImpl());
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });

        show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
    }
}

函数式接口作为参数的案例

例如java.lang.Runnable接口就是一个函数式接口
假设有一个startThread方法使用该接口作为参数,那么可以使用Lambda进行传参
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别
public class Demo01Runnable {
    public static  void startThread(Runnable run){

        new Thread(run).start();
    }

    public static void main(String[] args) {
        startThread(()-> System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了")
        );
    }

函数式接口作为方法的返回值

public class Demo02Comparator {
    public static Comparator<String> getCompararor(){
            return (o1,o2)-> o2.length()-o1.length();

    }

    public static void main(String[] args) {
       String[] arr = {"aaa","b","ccccc","dddddddddddddddddd"};

        System.out.println(Arrays.toString(arr));

        Arrays.sort(arr, getCompararor());
        System.out.println(Arrays.toString(arr));;

        /*
            [aaa, b, ccccc, dddddddddddddddddd]
            [dddddddddddddddddd, ccccc, aaa, b]
         */
    }
}

常用的函数式接口supplier(供给者)

/*
    java.util.function.Supplier<T>接口仅包含一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据。

    Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的方法get就会生产什么类型的数据
 */
public class Demo01Supplier {
    public static  String getString(Supplier<String> sup){
        return sup.get();
    }
    public static void main(String[] args) {
        String s = getString(()->{return "胡歌";});
        System.out.println(s);
    }
}

常用的函数式接口Consumer(消费者)

Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用该数据),需要自定义(输出,计算...)
public class Demo02Consumer {
    public static void method(String name, Consumer<String> con){
            con.accept(name);
    }

    //调用method方法,消费数据"赵丽颖",怎么消费看自定义
    public static void main(String[] args) {
        method("赵丽颖",(String name)->{
            //System.out.println(name);
            //消费方式:把字符串反转
            String reName = new StringBuffer(name).reverse().toString();
            System.out.println(reName);
        });
    }
}

Consumer接口的默认方法AndThen

Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,对数据进行消费
Consumer<String> con1
Consumer<String> con1
con1.andThen(cn2).accept(s);谁写前边谁先消费
public class Demo03AndThen {
    public static void method(String s, Consumer<String> con1, Consumer<String> con2){
        //con1.accept(s);
        //con2.accept(s);
        con1.andThen(con2).accept(s);
    }

    public static void main(String[] args) {
        //用t来接收"Hello"
        method("Hello",(t)->{
            System.out.println(t.toUpperCase());
        },(t)->{
            System.out.println(t.toLowerCase());
        });
    }
}

格式化打印信息

public class DemoConsumer {
    public static void main(String[] args) {
        String[] arr ={"迪丽热巴,女","古力娜扎,女","马尔扎哈,男"};
        printInfo(arr,(m)->{
            //对message进行分割,分割后返回的是一个数组
            System.out.println(m.split(",")[0]);},(m)->{
            System.out.println(m.split(",")[1]);
        });
    }
    public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String>con2){
        for (String message : arr) {
            con1.andThen(con2).accept(message);
        }
    }
}
/*
迪丽热巴
女
古力娜扎
女
马尔扎哈
男
*/

函数式接口predicate接口(对数据进行判断)

java.util.function.predicate<T>接口
    作用:对某种数据类型的数据进行判断,结果返回一个boolean值

    predicate接口中包含一个抽象方法:
        boolean test(T t):用来对指定数据类型数据进行判断的方法
            结果:
                符合条件,返回true
public class Demo02Predicate {
    public static void main(String[] args) {
        String s ="avcde";
        //用str接收s,str是在函数式接口内运行的
        boolean b = checkString(s,str ->
            str.length() > 5
        );
        System.out.println(b);
    }
    public static boolean checkString(String s , Predicate<String> pre){
        return pre.test(s);
    }

用predicate接口中的and方法实现逻辑与操作

		一个判断字符串的长度是否大于5
        一个判断字符串是否包含a
        两个必须同时满足
public class Demo03Predicate {
    public static void main(String[] args) {
        /*
            一个判断字符串的长度是否大于5
            一个判断字符串是否包含a
           两个必须同时满足
         */
        String str = "abcdef";
        boolean a = checkString(str, (String s) -> {
            return s.length() > 5;
        }, (String s) -> {
            return s.contains("a");
        });
        System.out.println(a);
    }
    public static  boolean checkString(String s , Predicate<String>pre1,Predicate<String>pre2){
        return pre1.test(s)&& pre1.test(s);
        return pre1.and(pre2).test(s);//与上一句等同
    }
}

用predicate接口中的or方法和negate方法实现逻辑或操作和逻辑非

public class Demo03Predicate {
   public static void main(String[] args) {
       /*
           一个判断字符串的长度是否大于5
           一个判断字符串是否包含a
          两个必须同时满足
        */
       String str = "abc";
       boolean a = checkString(str, (String s) -> {
           return s.length() > 5;
       }, (String s) -> {
           return s.contains("a");
       });
       System.out.println(a);
   }
   public static  boolean checkString(String s , Predicate<String>pre1,Predicate<String>pre2){
       //return !pre1.test(s);
       return pre1.negate().test(s);
       //return pre1.test(s)||pre2.test(s);
       //return pre1.or(pre2).test(s);//与上一句等同
   }
}

练习:集合信息筛选

练习:集合信息筛选
    筛选数组中满足一下两个条件的的信息:
        1.必须是女生
        2.姓名为4个字
public class Demo05Test {
    public static void main(String[] args) {
        String[] arr = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"};

        ArrayList<String> list = checkMessage(arr, (String s) -> {
            //条件1,必须包含女
            return s.split(",")[1].contains("女");
        }, (String s) -> {
            //条件2,名字必须为4个字
            return s.split(",")[0].length() == 4;
        });


        System.out.println(list);
    }
    public static ArrayList<String> checkMessage(String[] arr, Predicate<String> pre1,Predicate<String> pre2){
        //两个测试用函数式接口同时对数据arr进行测试
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            boolean b = pre1.and(pre2).test(s);
            if(b){
                list.add(s);
            }
        }
        return list;
    }
}

Function接口

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
    前者称之为前置条件,后者称之为后置条件
Function接口中主要的抽象方法为 R apply(T t)根据类型T的参数获取类型R的结果
    例如:将String类型转换为Integer类型
public class Demo01Function {
    public static void main(String[] args) {
        String s = "12345";
 		String s2 ="3654";	
        change(s,s2,(String str)->{
            return Integer.parseInt(str);
        });
    }

    public static void change(String s ,String s2, Function<String,Integer> fun){
        Integer in1 = fun.apply(s);
        Integer in2 = fun.apply(s2);
        System.out.println(in1);
        System.out.println(in2);
    }
}

之所以用函数式接口,看似多此一举,实际上因为是lambda表达式,参数可以使未知数,可以是传递进来的数据中的任何一个,因此可以不用反复写函数体,只需写一遍,然后反复调用即可

Function接口中的andThen方法(可以把两个接口连接在一起)

用Function接口中的默认方法andThen:进行组合操作

需求:
    把String类型的"123",转换为Integer类型,把转换后的数据加10
    然后在转换为String类型
public class Demo04Function_andThen {
    public static void main(String[] args) {
        String s ="123";
        change(s,(String s1)->{
            int i = Integer.parseInt(s1);
            int i1 = i + 10;
            return i1;
        },(Integer i1)->{
            //Function接口必有返回值
            String s1 = i1.toString();
            return s1;
        });
        //简化写法
        // change(s,s1->Integer.parseInt(s1)+10,i1->i1+"");
    }
    public static  void change(String str, Function<String,Integer> fun1,Function<Integer,String> fun2){
        //Function接口返回的数值在这里
        String s = fun1.andThen(fun2).apply(str);
        System.out.println(s);
    }
}

练习:Function进行函数模型拼接

练习:自定义函数模型拼接
题目
请使用Function进行函数模型拼接,按照顺序需要执行的多个函数操作为:
    String str = "赵丽颖,20";

1.将字符串截取数字年龄部分,得到字符串;
2.将上一步的字符串转换为int类型的数字;
3.将上一步的int数字累加100,得到结果int数字
public class Demo06Test {
    public static void main(String[] args) {
        String str = "赵丽颖,20";
        change(str,(String s1)->{
            String s = s1.split(",")[1];
            return s;
        },(String s2)->{
           return Integer.parseInt(s2)+100;
        });
    }
    public static void change(String s, Function<String,String> fun1, Function<String,Integer> fun2){
        Integer i = fun1.andThen(fun2).apply(s);
        System.out.println(i);
    }
}

stream方法对数据进行遍历筛选操作

public class Demo02StreamFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("阿朱");
        list.add("张三丰");

        //用list集合中的stream方法对数据进行遍历筛选操作
        list.stream()
                //stream流中有filter方法,内置predicate接口可以对数据进行判断
                .filter(name->name.startsWith("张"))
                .filter(name->name.length()==3)
                //stream流中有方法forEach方法,内置Consumer接口可以对数据进行消费
                .forEach(name-> System.out.println(name));
    }
}

流式思想

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
流在一个步骤中使用完,会生成新的流进入下一个步骤,不会在本步骤停留

(重要)	filter过滤流
        limit只要流数据前n个
        skip跳过前n个数据
        map把数据映射为另一种数据
        forEach消费数据

将集合和数组转换为Stream流

java,util.stream.Stream<T>是新加入到额最常用的流接口(这并不是函数式接口)
获取流的方式:
    -所有的Collection集合都可以通过stream默认方法获取流;
        default Stream<E> stream()
    -Stream接口的静态方法of可以获取数组对应的流
        static<T> Stream<T> of {T... values}
        参数是一个可变参数,那么我们就可以传递一个数组
public class Demo03GetStream {
    public static void main(String[] args) {
        //将各类集合转换为Stream流
        List<String> list =new ArrayList<>();
        Stream<String> stream1  =list.stream();
        
        Set<String> set = new HashSet<>();
        Stream<String> stream5 = set.stream();

        HashMap<String, String> map = new HashMap<>();
        Set<String> keyset = map.keySet();
        Stream<String> stream2 = keyset.stream();

        Collection<String> values = map.values();
        Stream<String> stream3 = values.stream();

        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream4 = entries.stream();

        //将数组转换为Stream流
        Stream<Integer> stream6 = Stream.of(1,2,3,4,5);
        Integer[] arr = {1,2,3,4,5};
        Stream<Integer> stream7 = Stream.of(arr);
    }
}

Stream常用方法

在这里插入图片描述

在这里插入图片描述

public class Demo04ideal {
    public static void main(String[] args) {
        Stream<String> s = Stream.of("张三", "李四", "王五", "赵六", "田七");
        //stream流调用forEach方法,会将stream中的数据一个个传递入consumer接口中消费
        s.forEach((String name)->{
            System.out.println(name);
        });
    }
}

Stream流中的filter(过滤)方法

Stream流中的常用方法filter:用于对stream流中的数据进行过滤
Stream<T> filter(Predicate<? super> predicate);
filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
predicate中的抽象方法:
    boolean test(T t);

public class Demo03Stream_filter {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("张三丰","赵敏","张翠山","周芷若","张无忌");

        Stream<String> stream2 = stream1.filter((String name) -> {
            return name.startsWith("张");
        });

        stream2.forEach((String name)->{
            System.out.println(name);
        });
    }
}

Sream流的特点:Stream流只能消费一次,第一个Stream流调用完毕方法,数据就会流转到下一个Stream中,而第一个Stream流就会关闭

这时候在遍历stream1就会出错

映射:使用map方法

在这里插入图片描述

如果需要将流中的元素映射到另一个流中,可以使用map方法.
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
Function中的抽象方法:
    R apply(T t);
public class Demo04map {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1","2","3","4");

        Stream<Integer> stream1 = stream.map((String s) -> {
            return Integer.parseInt(s);
        });
        stream1.forEach(s-> System.out.println(s));
    }
}

Stream流中的count方法(计算stream流中元素个数)

long  count();

count方法是一个终结方法,返回值是一个long类型的整数
所以不能再调用stream中的其他方法

public class Demo05count {
    public static void main(String[] args) {
      ArrayList<Integer> list =   new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);

        Stream<Integer> stream1 = list.stream();
        //count是终结方法,使用后不返回一个流
        long c = stream1.count();
        System.out.println(c);
    }
}

Stream流中的limit方法

在这里插入图片描述

是一个延迟方法,只是对流中的数据进行截取,返回的是一个新的流,可以继续使用Stream流中的其他方法
public class Demo06limit {
    public static void main(String[] args) {
        String[] arr =new String[]{"喜洋洋","美羊羊","懒洋洋","沸羊羊","灰太狼","红太狼"};
        Stream<String> stream = Stream.of(arr);

        Stream<String> stream2 = stream.limit(3);

        stream2.forEach(s-> System.out.println(s));
    }
}

Stream中的skip(跳过)

在这里插入图片描述

Stream流中的concat方法(组合)

在这里插入图片描述

public class demo07concat {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("喜洋洋", "美羊羊", "懒洋洋", "沸羊羊", "灰太狼", "红太狼");
        Stream<String> stream2 = Stream.of("张三丰", "赵敏", "张翠山", "周芷若", "张无忌");

        Stream<String> concat = Stream.concat(stream1, stream2);

        concat.forEach(s-> System.out.println(s));
    }
}
//会将两个数组组合在一起

测试信息筛选

public class StreamTest {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("迪丽热巴");
        list1.add("宋远桥");
        list1.add("苏星河");
        list1.add("石破天");
        list1.add("石中玉");
        list1.add("老子");
        list1.add("庄子");
        list1.add("洪七公");

        ArrayList<String> list2 =new ArrayList<>();
        list2.add("古力娜扎");
        list2.add("张无忌");
        list2.add("赵丽颖");
        list2.add("张三丰");
        list2.add("尼古拉斯赵四");
        list2.add("张天爱");
        list2.add("张三狗");

        ArrayList<String> one = new ArrayList<>();
        for (String s : list1) {
            if(s.length()==3){
                one.add(s);
            }
        }

        ArrayList<String> one1 =new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            one1.add(one.get(i));
        }

        ArrayList<String> two =new ArrayList<>();
        for (String s : list2) {
            if(s.startsWith("张")){
                two.add(s);
            }
        }

        ArrayList<String> two1 = new ArrayList<>();
        for (int i = 2; i < two.size(); i++) {
            two1.add(two.get(i));
        }

        ArrayList<String> all = new ArrayList<>();
        all.addAll(one1);
        all.addAll(two1);

        ArrayList<Person> person  =new ArrayList<>();
        for (String s : all) {
            person.add(new Person(s));
        }
        System.out.println(person);
    }
}

用Stream流对测试数据筛选

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("迪丽热巴");
        list1.add("宋远桥");
        list1.add("苏星河");
        list1.add("石破天");
        list1.add("石中玉");
        list1.add("老子");
        list1.add("庄子");
        list1.add("洪七公");
        //1.第一个集合只要名字为三个字的成员并且只要前三个
        /*
            filter过滤流
            limit限制流个数
            skip跳过前n个数据
            map把数据映射为另一种数据
            forEach消费数据
         */
        Stream<String> list1Stream = list1.stream().filter(s -> s.length() == 3).limit(3);


        ArrayList<String> list2 =new ArrayList<>();
        list2.add("古力娜扎");
        list2.add("张无忌");
        list2.add("赵丽颖");
        list2.add("张三丰");
        list2.add("尼古拉斯赵四");
        list2.add("张天爱");
        list2.add("张三狗");
        //第二个集合只要型张的成员并且不要前两个
        Stream<String> list2Stream = list2.stream().filter(s -> s.startsWith("张")).skip(2);

        // 将两个集合流合并为一个集合流,并且以此创建Person对象,并遍历
        Stream.concat(list1Stream,list2Stream).map(s->new Person(s)).forEach(s-> System.out.println(s));
    }
}

方法应用

在这里插入图片描述

public class Demo02ObjectReference{
    public static void printString(Printable p){
        p.print("Hello");
    }

    public static void main(String[] args) {
        //printString(s-> System.out.println(s));
        printString((s)->{
            MethodRerObject obj = new MethodRerObject();
            //调用MethodRerObject对象中的prinyUpperCaseString方法
            obj.prinyUpperCaseString(s);
        });
        //采用方法引用(采用obj对象中的方法prinyUpperCaseString来对重写p中的print方法)
        MethodRerObject obj = new MethodRerObject();
        printString(obj::prinyUpperCaseString);
    }
}

通过类名引用静态成员方法

通过类名引用静态成员方法
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法
public class demo01StaticMethodReference {
    public static int method(int number,Calcabel c){
        return c.calsAbs(number);
    }

    public static void main(String[] args) {
        int m = method(-4, (int num) -> {
            return Math.abs(num);
        });
        System.out.println(m);


        //使用方法引用优化Lambda表达式
        /*
           Math类是存在的
           abs计算绝对值的静态方法也是存在的
           所以我们可以直接通过类名引用静态方法
         */
        method(-10,Math::abs);
    }
}

通过父类super引用成员方法

在这里插入图片描述

public class Human {
    public void sayHello(){
        System.out.println("Hello 我是Human!");
    }
}
@FunctionalInterface
public interface Greetable {
    //定义一个见面的方法
    void greet();
}

public class Man extends Human{
    @Override
    public void sayHello() {
        System.out.println("Hello 我是Man");
    }
    public void method(Greetable g){
        g.greet();
    }

    //因为有子父类关系,所以存在一个关键字super,代表父类,所以我们可以直接使用super调用父类的方法
    public void show(){
        method(()->{
            /*Human h = new Human();
            h.sayHello();*/
            super.sayHello();
        });
        /*
            因为super已经存在了
            sayHello方法也已经存在了
            方法引用是为了简化Lambda表达式
         */
        method(super::sayHello);
    }
    

    public static void main(String[] args) {
        new Man().show();//Hello 我是Human!
        new Man().sayHello();//Hello 我是Man
    }
}

通过this引用本类的成员方法

函数式接口
@FunctionalInterface
public interface Richable {
    void buy();
}
实现类
/*
    通过this引用本类的成员方法
 */
public class Husband {
    //定义一个买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院");
    }
    //定义一个结婚的方法,参数传递Richable接口
    public void marry(Richable r){
        r.buy();
    }
    public void soHappy(){
        marry(()->{
            this.buyHouse();
        });
        /*
        使用方法引用优化Lambda表达式
        this是已经存在的
        本类的成员方法buyHouse也是已经存在的
        所有我们可以直接使用this引用本类的成员方法buyHouse
         */
        marry(this::buyHouse);
    }
    public static void main(String[] args) {
        new Husband().soHappy();
    }
}

类的构造方法引用

函数式接口
public interface PersonBuilder {
    Person builderPerson(String name);
}
实现l类
/*
    类的构造器(构造方法)引用
 */
public class demo {
    //定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
    public static void printName(String name,PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        printName("迪丽热巴",(String name)->{
            return new Person(name);
        });
        /*
            使用方法引用优化Lambda表达式
            构造方法new Person(String name)已知
            创建对象已知 new
            就可以使用Peron引用new创建对象
         */
        printName("古力娜扎",Person::new);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值