常用API,基本类型包装类,日期类,异常,集合进阶,IO流,多线程

十九.常用API

19.1Math

Math包含执行基本数字运算的方法

没有构造方法,如何使用类中的成员呢?

看类的成员是否都是静态的,如果是,通过类名就可以直接调用公

19.1.1 Math的常用方法

image-20201116095901480

绝对值

public class MathDemo {
    public static void main(String[] args) {
        //public static int abs(int a) :返回参数的绝对值
        System.out.println(Math.abs(88));
        System.out.println(Math.abs(-88));
    }
}
//88
//88

返回大于或等于参数的最小double值,等于一个整数

public class MathDemo {
    public static void main(String[] args) {
        ///public static double ceil(double a):返回大于或等于参数的最小double值,等于一个整数
        System.out.println(Math.ceil(12.34));
        System.out.println(Math.ceil(12.56));
    }
}
//13.0
//13.0

返回小于或等于参数的最大douple值,等于一个整数

public class MathDemo {
    public static void main(String[] args) {
        //public static double floor(double a):返回小于或等于参数的最大douple值,等于一个整数
        System.out.println(Math.floor(12.34));
        System.out.println(Math.floor(12.56));
    }
}
//12.0
//12.0

按照四舍五入返回最接近参数的int

public class MathDemo {
    public static void main(String[] args) {
        //public static int round(float a):按照四舍五入返回最接近参数的int
        System.out.println(Math.round(12.34f));
        System.out.println(Math.round(12.56f));
    }
}
//12
//13

返回两个int值中的较大值

public class MathDemo {
    public static void main(String[] args) {
        //public static int max(int a,int b):返回两个int值中的较大值
        System.out.println(Math.max(9,12));
    }
}
//12

返回两个int值中的较小值

public class MathDemo {
    public static void main(String[] args) {
        //public static int min(int a,int b):返回两个int值中的较大值
        System.out.println(Math.min(9,12));
    }
}
//9

返回a的b次幂的值

public class MathDemo {
    public static void main(String[] args) {
       //public static double pow (double a , double b):返回a的b次幂的值
        System.out.println(Math.pow(2.0,3.0));//2^3

    }
}
//8.0

返回值为double的正值,在[0.0,1.0)之间

public class MathDemo {
    public static void main(String[] args) {
        // public static double random ():返回值为double的正值,[0.0,1.0)
        System.out.println(Math.random());
    }
}
//0.7355578370042718
//随机数[0.0,1.0)
//强转[0,100]
System.out.println((int)(Math.random()*100)+1);

19.2 System

概述:System包含几个有用的类字段和方法,它不能被实例化(他的成员都被静态修饰的)

方法名说明
public static void exit (int status)终止当前运行的java虚拟机,非零表示异常终止
public static long currentTimeMillis()返回当前时间(以毫秒为单位)

public static void exit (int status) 终止当前运行的java虚拟机,非零表示异常终止

public class SystemDemo {
    public static void main(String[] args) {
//public static void exit (int status) 终止当前运行的java虚拟机,非零表示异常终止
        System.out.println("开始");
        System.exit(0);
        System.out.println("结束");
     }
}
//开始

public static long currentTimeMillis()返回当前时间(以毫秒为单位)

public class SystemDemo {
    public static void main(String[] args) {
    //public static long currentTimeMillis()返回当前时间(以毫秒为单位)
        System.out.println(System.currentTimeMillis() * 1.0 / 1000 / 60 / 60 / 24 / 365 + "年");
    }
}
//50.909943042776504年

循环结束的时间

public class SystemDemo {
    public static void main(String[] args) {

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("共耗时:" + (end - start) + "毫秒");
    }
}
//共耗时:86毫秒

19.3 Object

Object是类层次结构的根,每个类都可以将Object作为超类。所有类都直接或者间接的继承自该类

构造方法: public Object( )

回想面向对象中,为什么说子类的构造方法默认访问的是父类的无参构造方法?

因为它们的顶级父类只有无参构造方法

public String toString()返回对象的字符串表示形式.建议所有子类重写该方法,自动生成

//Object是类层次结构的根,每个类都可以将Object作为超类。所有类都直接或者间接的继承自该类
public class ObjectDemo {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("张三");
        s.setAge(23);
        System.out.println(s);//com.java2.API.Student@14ae5a5
        System.out.println(s.toString());

    }
}
/*
Student{name='张三', age=23}
Student{name='张三', age=23}
*/
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
//建议所有子类重写此方法
 //自动生成alt+ins tostring
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

19.3.1 Object 类常用的方法

方法名说明
public String toString()返回对象的字符串表示形式.建议所有子类重写该方法,自动生成
public boolean equals(Object obj)比较对象是否相等。默认比较地址,重写可以比较内容,自动生成

public boolean equals(Object obj) 比较对象是否相等。默认比较地址,重写可以比较内容,自动生成

public class ObjectDemo {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("张三");
        s1.setAge(23);

        Student s2 = new Student();
        s2.setName("张三");
        s2.setAge(23);

/*        //需求;比较两个对象的内容是否相同
        System.out.println(s1 == s2);//false
        */
        System.out.println(s1.equals(s2));
/*        public boolean equals(Object obj) {
                //this ----s1
                //obj -----s2
            return (this == obj);
        }*/
    }
}
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public boolean equals(Object o) {
/*      this ----s1
        o -----s2*/
        //比较地址是否相同,如果相同,直接返回true
        if (this == o) return true;
        //参数是否为null
        //判断俩个对象是否来自同一个类
        if (o == null || getClass() != o.getClass()) return false;
        //向下转型
        Student student = (Student) o;//student = s2
        //比较年龄是否相同
        if (age != student.age) return false;
        //比较姓名内容是否相同
        return name != null ? name.equals(student.name) : student.name == null;
    }
}

19.4 Arrays

19.4.1 冒泡排序

排序: 将一组数据按照固定的规则进行排列

冒泡排序:一种排序的方式,对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序

  • 如果有n个数据进行排序,总共需要比较n-1次
  • 每一次比较完毕,下一次的比较就会少一个数据参与

冒泡排序优化前

public class ArrayDemo {
    public static void main(String[] args) {
        //定义一个数组
        int[] arr = {24, 69, 80, 57, 13};
        System.out.println("排序前: " + arrayToString(arr));

        //第一次比较
        for (int i = 0; i < arr.length-1; i++) {
            if (arr[i] > arr[i + 1]) {
                int temp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = temp;
            }
        }
        System.out.println("第一次比较后: " + arrayToString(arr));
        //第二次比较
        for (int i = 0; i < arr.length-1-1; i++) {
            if (arr[i] > arr[i + 1]) {
                int temp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = temp;
            }
        }
        System.out.println("第二次比较后: " + arrayToString(arr));
        //第三次比较
        for (int i = 0; i < arr.length-1-2; i++) {
            if (arr[i] > arr[i + 1]) {
                int temp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = temp;
            }
        }
        System.out.println("第三次比较后: " + arrayToString(arr));
        //第四次比较
        for (int i = 0; i < arr.length-1-3; i++) {
            if (arr[i] > arr[i + 1]) {
                int temp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = temp;
            }
        }
        System.out.println("第四次比较后: " + arrayToString(arr));

    }

    //把数组中的元素按照指定的规则组成一个字符串;[元素1,元素2,..]
    public static String arrayToString(int[] arr) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < arr.length; i++) {
            if (i == arr.length - 1) {
                sb.append(arr[i]);

            } else {
                sb.append(arr[i]).append(",");
            }
        }
        sb.append("]");
        String s = sb.toString();
        return s;
    }
}
/*
排序前: [24,69,80,57,13]
第一次比较后: [24,69,57,13,80]
第二次比较后: [24,57,13,69,80]
第三次比较后: [24,13,57,69,80]
第四次比较后: [13,24,57,69,80]
*/

优化后的冒泡排序

//优化后
public class ArrayDemo {
    public static void main(String[] args) {
        //定义一个数组
        int[] arr = {24, 69, 80, 57, 13};
        System.out.println("排序前: " + arrayToString(arr));
        
        for (int x=0;x<arr.length-1;x++){
            for (int i=0;i<arr.length-1-x;i++){
                if (arr[i]>arr[i+1]){
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
        }
        System.out.println("排序比较后: " + arrayToString(arr));

    }

    //把数组中的元素按照指定的规则组成一个字符串;[元素1,元素2,..]
    public static String arrayToString(int[] arr) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < arr.length; i++) {
            if (i == arr.length - 1) {
                sb.append(arr[i]);

            } else {
                sb.append(arr[i]).append(",");
            }
        }
        sb.append("]");
        String s = sb.toString();
        return s;
    }
}

19.4.2 Arrays类的概述和常用方法

方法名说明
public static String toString(int[] a)返回指定数组的内容的字符串表示形式
public static void sort(int[] a)按照数字顺序排列指定的数组

工具类的设计思想:·

  • 构造方法用private修饰
  • 成员用public static修饰

排序 public static void sort(int[] a)

import java.util.Arrays;

public class ArrayDemo02 {
    public static void main(String[] args) {
        //定义一个数组
        int[] arr = {24,69,80,57,13};

        System.out.println("排序前: "+ Arrays.toString(arr));
        Arrays.sort(arr);
        System.out.println("排序后: "+ Arrays.toString(arr));
    }
}
/*
排序前: [24, 69, 80, 57, 13]
排序后: [13, 24, 57, 69, 80]
*/

二十.基本类型包装类

20.1 基本数据类型和其包装类

将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据

常用的操作之一:用于基本数据类型与字符串之间的转换

image-20201116150830308

public class IntegerDemo {
    public static void main(String[] args) {
        //需求:我要判断一个数据是否在int范围内
        //public static final int MIN_VALUE
        //public static final int MAX_VALUE
        System.out.println(Integer.MIN_VALUE);
        System.out.println(Integer.MAX_VALUE);

    }
}
/* 
int范围
-2147483648
2147483647
*/

20.2 Integer类的概述和使用

lnteger:包装一个对象中的原始类型int的值

image-20201116151243070

public class IntegerDemo02 {
    public static void main(String[] args) {
        
        //过时方法
/*        Integer i1 = 100; //new Integer(100);
        System.out.println(i1);
//        Integer i2 = new Integer("abc");//NumberFormatException
        Integer i2 = new Integer("100");
        System.out.println(i2);*/

        
        Integer i1=Integer.valueOf(100);
        System.out.println(i1);

        Integer i2=Integer.valueOf("100");
        System.out.println(i2);

    }
}
/*
100
100
*/

20.3 int和String的相互转换

基本类型包装类的最常见操作就是:用于基本类型和字符串之间的相互转换

  1. int转换为String

    public static String ==valueOf(inti):==返回int 参数的字符串表示形式。该方法是String类中的方法

  2. String 转换为int
    public static int parselnt(String s):将字符串解析为int类型。该方法是Integer类中的方法

/*
    int和String的相互转换
 */
public class IntegerDemo03 {
    public static void main(String[] args) {
        //int---String的转换
        int number = 100;
        //方式一
        String s1 = "" + number;
        System.out.println(s1);

        //方式二
        // public static String vaLueOf (int i)
        String s2 = String.valueOf(number);
        System.out.println(s2);
        System.out.println("--------");

        //String---int的转换
        String s = "100";
        //方式1
        //String --- Integer ---int
        Integer i = Integer.valueOf(s);
        //public int intValue
        int x = i.intValue();
        System.out.println(x);
        
        //方式2
        // public static int parseInt (String s)
        int y = Integer.parseInt(s);
        System.out.println(y);

    }
}
/*
100
100
--------
100
*/

20.4 案例:字符串中的数据排序

需求:有一个字符串:“91 27 46 38 50”,请写程序实现最终输出结果是: “27 38 46 50 91"

思路:

①定义一个字符串

②把字符串中的数字数据存储到一个int类型的数组中

  • 得到字符串中每一个数字数据

    • public String[] split(String regex)
  • 定义一个int数组,把String数组中的每一个元素存储到int数组中

    • public static int parselnt(String s)

③对int数组进行排序

④把排序后的int数组中的元素进行拼接得到一个字符串,这里拼接采用StringBuilder来实现

⑤输出结果

import java.util.Arrays;

public class IntegerText {
    public static void main(String[] args) {
        //定义一个字符串
        String s = "91 27 46 38 50";

        //把字符串中的数字数据储存到一个int类型的数组中
        String[] strArray = s.split(" ");//以空格为界拆分字符串

        //定义一个int数组,把String[]数组中的每一个元素储存到int数组中
        int[] arr = new int[strArray.length];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = Integer.parseInt(strArray[i]);
        }
        //对int数组进行排序
        Arrays.sort(arr);
        //把排序后的int数组中的元素进行拼接得到一个字符串,这里拼接采用StringBuilder来实现
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            if(i==arr.length-1){
                sb.append(arr[i]);

            }else {
                sb.append(arr[i]).append(" ");
            }
        }
        String result = sb.toString();
        System.out.println("结果: "+result);
    }
}

20.5 自动装箱和拆箱

  • 装箱:把基本数据类型转换为对应的包装类类型
  • 拆箱:把包装类类型 转换为对应的基本数据类型
public class IntegerDemo04 {
    public static void main(String[] args) {
        //装箱:把基本数据类型转换为包装类类型
        Integer i = Integer.valueOf(100);//装箱
        Integer ii = 100;//自动装箱

        //拆箱:把包装类类型转换为对应的基本数据类型
       //ii = ii.intValue()+200;
        ii += 200;//i+200自动拆箱;i=i+200;自动装箱
        System.out.println(ii);

        Integer iii = null;
        if (iii!=null){
            iii +=300;
        }
    }
}
//300

==注意:==在使用包装类类型的时候,如果做操作,最好先判断是否为null

我们推荐的是,只要是对象,在使用前就必须进行不为null的判断

二十一.日期类

6.1 Date类概述和构造方法

Date代表了一个特定的时间,精确到毫秒

image-20201118092621816

import java.util.Date;

/*
     public Date():分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒
     public Date(Long date):分配一个Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数

 */
public class DateDemo01 {
    public static void main(String[] args) {
        //     public Date():分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒
        Date d1 = new Date();
        System.out.println(d1);

        //public Date(Long date):分配一个Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数
        long date = 1000*60*60;
        Date d2 = new Date(date);
        System.out.println(d2);


    }
}
/*
Wed Nov 18 09:39:12 CST 2020
Thu Jan 01 09:00:00 CST 1970
*/

6.2 Date类的常用方法

image-20201118095602616

6.3.SimpleDateFormat 类概述

SimpleDateFomat是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。重点学习日期格式化和解析

日期和时间格式由日期和时间模式字符串指定,在日期和时间模式字符串中,从 ‘A’ 到 ‘Z’ 以及从 ‘a’ 到 ‘z’ 引号的字母被解释为表示日期或时间字符串的组件的模式字母

常用的模式字幕及对应关系如下:

  • y 年
  • M 月
  • d 日
  • H 时
  • m 分
  • s 秒

构造方法:

方法名说明
public SimpleDateFormat()构造一个SimpleDateFormat,使用默认模式和日期格式
public SimpleDateFormat(String pattern)构造一个SimpleDateFormat使用给定的模式和默认的日期格式
/*
		无参方法
*/
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo {
    public static void main(String[] args) {
        //.格式化(从Date到 String )
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat();
        String s = sdf.format(d);
        System.out.println(s);
    }
}
/*
20-11-18 上午10:31
*/
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo {
    public static void main(String[] args) {
        //.格式化(从Date到 String )
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String s = sdf.format(d);
        System.out.println(s);
    }
}
/* 
2020年11月18日 10:29:39
*/

6.4.SimpleDateFormat 格式化和解析日期

1.格式化(从Date到 String )

public final String format(Date date):将日期格式化成日期/时间字符串

2.解析(从String到Date )

public Date parse(String source):从给定字符串的开始解析文本以生成日期

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo {
    public static void main(String[] args) throws ParseException {
        //.格式化(从Date到 String )
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String s = sdf.format(d);
        System.out.println(s);
        System.out.println("----------------------");

        //从String 到 Date
        String ss ="2020-11-18 11:11:11";
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date dd = sdf2.parse(ss);
        System.out.println(dd);
    }
}

6.5 案例:日期工具类

需求:定义一个日期工具类(DateUtils),包含两个方法: 把日期转换为指定格式的字符串; 把字符串解析为指定格式的日期,然后定义一个测试类(DateDemo),测试日期工具类的方法.

思路:

①定义日期工具类(DateUtils)

②定义一个方法dateToString,用于把日期转换为指定格式的字符串

  • 返回值类型: String

  • 参数: Date date, String format

③定义一个方法stringToDate,用于字符串解析为指定格式的日期

  • 返回值类型:Date

  • 参数: String s, String format

④定义测试类DateDemo,调用日期工具类中的方法

import java.text.ParseException;
import java.util.Date;

public class DateUtilsDemo {
    public static void main(String[] args) throws ParseException {
        //创建日期对象
        Date d = new Date();
        String s1 = DateUtils.dateToString(d, "yyyy年MM月dd日 HH:mm:ss ");
        System.out.println(s1);
        String s2 = DateUtils.dateToString(d, "yyyy年MM月dd日");
        System.out.println(s2);

        String s3 = DateUtils.dateToString(d, "HH:mm:ss");
        System.out.println(s3);
        System.out.println("-----------");

        String s ="2020-11-18 11:20:12";
        Date dd = DateUtils.stringToDate(s,"yyyy-MM-dd HH:mm:ss");
        System.out.println(dd);
    }
}
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtils {
    private DateUtils(){}
    public static String dateToString(Date date,String format){
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        String s = sdf.format(date);
        return s;
    }
    public static Date stringToDate(String s,String format) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        Date d = sdf.parse(s);
        return  d;
    }
}

6.5 Calendar 类概述

Calendar为某一时刻和一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法

Calendar提供了一个类方法getInstance用于获取Calendar对象,其日历字段已使用当前日期和时间初始化: Calendar rightNow = Calendar.getInstance();

import java.util.Calendar;

public class CalendarDemo {
    public static void main(String[] args) {
        //获取对象
        Calendar c = Calendar.getInstance();//多态的形式
//        public int get(int field)
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH)+1;
        int date = c.get(Calendar.DATE);
        System.out.println(year + "年" + month + "月" + date + "日");
    }
}
//2020年11月18日

6.6 Calendar 的常用方法

image-20201118134555826.png

//get  add  set  方法

import java.util.Calendar;

public class CalendarDemo {
    public static void main(String[] args) {
        //获取日历类对象
        Calendar c = Calendar.getInstance();//多态的形式
//     public int get(int field)
//     public abstract void add (int field,int amount):根据日历的规则,将指定的时间量添加或减去给定的日历字段
        c.add(Calendar.YEAR,-3);
        c.add(Calendar.DATE,-5);
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH)+1;
        int date = c.get(Calendar.DATE);
        System.out.println(year + "年" + month + "月" + date + "日");

        c.set(1876,11,23);
        int year2 = c.get(Calendar.YEAR);
        int month2 = c.get(Calendar.MONTH)+1;
        int date2 = c.get(Calendar.DATE);
        System.out.println(year2 + "年" + month2 + "月" + date2 + "日");

    }
}
/*
2017年11月13日
1876年12月23日
*/

6.7 案例:二月天

需求:获取任意一年的二月有多少天

思路:
①键盘录入任意的年份
②设置日历对象的年、月、日

  • 年:来自于键盘录入
  • 月:设置为3月,月份是从0开始的,所以设置的值是2
  • 日:设置为1日

③3月1日往前推一天,就是2月的最后一天

④获取这一天输出即可

import java.util.Calendar;
import java.util.Scanner;

public class CalendarTest {
    public static void main(String[] args) {
        //键盘录入任意年份
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入年份");
        int year = sc.nextInt();

        //设置日历对象的年/月/日
        Calendar c = Calendar.getInstance();
        c.set(year, 2, 1);

//        ③3月1日往前推一天,就是2月的最后一天
        c.add(Calendar.DATE, -1);
        int date = c.get(Calendar.DATE);
        System.out.println(year + "年的二月份有" + date + "天");


    }
}

/*请输入年份
2020
2020年的二月份有29天
*/

二十二.异常

22.1异常概述

异常:就是程序出现了不正常的情况

image-20201118143909935

Error:严重问题,不需要处理

Exception:称为异常类,它表示程序本身可以处理的问题

  • RuntimeException:在编译期是不检查的,出现问题后,需要我们回来修改代码

  • 非RuntimeException:编译期就必须处理的,否则程序不能通过编译,就更不能正常运行了

22.1 JVM的默认处理方案

如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理

  • 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
  • 程序停止执行

22.3 异常处理

如果程序出现了问题,我们需要自己来处理,有两种方案:

  • try … catch …

  • throws

image-20201118144803302.png

public class exceptionDemo01 {
    public static void main(String[] args) {
        System.out.println("开始");
        method();
        System.out.println("结束");
    }
    public static void method() {
        try {


            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);//new ArrayIndexOutOfBoundsException();
        }catch (ArrayIndexOutOfBoundsException e){
//  System.out.println("你访问的数组索引不存在");
            e.printStackTrace();
        }
    }
}
/* 
开始
你访问的数组索引不存在
结束
*/

22.4 Throwable 的成员方法

image-20201118150124573

public class exceptionDemo01 {
    public static void main(String[] args) {
        System.out.println("开始");
        method();
        System.out.println("结束");
    }
    public static void method() {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);
        }catch (ArrayIndexOutOfBoundsException e){
//            System.out.println(e.getMessage());
//            System.out.println(e.toString());
            e.printStackTrace();
        }
    }
}

22.5 编译时异常和运行时异常的区别

Java中的异常被分为两大类:编译时异常运行时异常,也被称为受检异常非受检异常所有的RuntimeException类及其子类被称为运行时异常,其他的异常都是编译时异常

  • 编译时异常:必须显示处理,否则程序就会发生错误,无法通过编译

  • ,运行时异常:无需显示处理,也可以和编译时异常一样处理

22.6 异常处理之throws

虽然我们通过try …catch…可以对异常进行处理,但是并不是所有的情况我们都有权限进行异常的处理也就是说,有些时候可能出现的异常是处理不了的

针对这种情况,Java提供了throws的处理方案

格式:

throw 异常类名

注意:这个格式是跟在方法的括号后面的

22.7 自定义异常

格式:

public class 异常类名 extends Exception{
	无参构造
	带参构造
}

范例:

public class scoreException extends Exception {
	public scoreException () {}
	public scoreException (string message) {
		super (message);
	}
}
public class ScoreException extends Exception {
    public ScoreException () {}
    public ScoreException (String message) {
        super (message);
    }
}
public class Teacher {
    public void checkScore(int score) throws ScoreException{
        if (score < 0 || score > 100) {
            throw new ScoreException("你给的分数有误,应该在0-100之间");
        }else {
            System.out.println("分数正常");
        }
    }
}
import java.util.Scanner;

public class Teachertest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入分数: ");
        int score = sc.nextInt();

        Teacher t = new Teacher();
        try {
            t.checkScore(score);
        } catch (ScoreException e) {
            e.printStackTrace();
        }
    }

}
/*
请输入分数: 
98
分数正常
-------------------------------------
请输入分数: 
455
com.java2.ArrayText.ScoreException: 你给的分数有误,应该在0-100之间
	at com.java2.ArrayText.Teacher.checkScore(Teacher.java:6)
	at com.java2.ArrayText.Teachertest.main(Teachertest.java:13)
*/

22.8 throws 和 throw的区别

throws

  • 用在方法声明后面,跟的是异常类名

  • 表示抛出异常,由该方法的调用者来处理

  • 表示出现异常的一种可能性,并不一定会发生这些异常

throw

  • 用在方法体内,跟的是异常对象名

  • 表示抛出异常,由方法体内的语句处理

  • 执行throw一定抛出了某种异常

二十三.集合进阶

23.1.Collection

1.1 集合知识回顾

集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变

1.2 集合类体系结构

单列集合

张三
李四
王五

双列集合

name1张三
name2李四
name3王五

image-20201118155925807.png

image-20201118160758769

1.3.Collection集合概述和使用

Collection集合概述

  • 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素

  • JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现

创建Collection集合的对象

  • 多态的方式
  • 具体的实现类ArrayList

collection的基本使用add

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo01 {
    public static void main(String[] args) {
        //创建Collection集合的对象
        Collection<String> c = new ArrayList<String>();

        //添加元素 boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("java");

        //输出集合对象
        System.out.println(c);
    }
}

1.4.collection集合常用方法

  • ALT+7 打开一个窗口,能够看到类的所有信息
方法名说明
boolean add(E e)添加元素(可添加重复元素)永远返回的是true
boolean remove(Object o)从集合中移除指定的元素
void clear清空集合中所有的元素
boolean contains (Object o)判断集合中是否存在指定的元素
boolean isEmpty判断集合是否为空
int size()集合的长度, 也就是集合中元素的个数

boolean add(E e) 添加元素(可添加重复元素)永远返回的是true

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("world");

        //输出集合对象
        System.out.println(c);
    }
}
/*
[hello, world, world]
*/

boolean remove(Object o) 从集合中移除指定的元素

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("world");
        //boolean  remove(Object  o) 从集合中移除指定的元素
        System.out.println(c.remove("world"));

  
        //输出集合对象
        System.out.println(c);
    }
}
/*
true
[hello, world]
*/

void clear 清空集合中所有的元素

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("world");
        //boolean  remove(Object  o) 从集合中移除指定的元素
        System.out.println(c.remove("world"));

        //void clear 清空集合中所有的元素
        c.clear();

        //输出集合对象
        System.out.println(c);
    }
}
/*
true
[]
*/

boolean contains (Object o) 判断集合中是否存在指定的元素

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("world");

        System.out.println(c.contains("world"));

        //输出集合对象
        System.out.println(c);
    }
}
/*
true
[hello, world, world]
*/

boolean isEmpty 判断集合是否为空

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("world");
	
        System.out.println(c.isEmpty());

        //输出集合对象
        System.out.println(c);
    }
}
/*
false
[hello, world, world]
*/

int size() 集合的长度, 也就是集合中元素的个数

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("world");

        System.out.println(c.size());

        //输出集合对象
        System.out.println(c);
    }
}
/* 
3
[hello, world, world]
*/

1.5.Collection 集合的遍历

lterator: 迭代器,集合的专用遍历方式

  • lterator iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
  • 迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖于集合而存在的

lterator中的常用方法

  • E next():返回迭代中的下一个元素

  • boolean hasNext(): 如果迭代具有更多元素,则返回true

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

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("java");
//lterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
        Iterator<String> it = c.iterator();

 /*       System.out.println(it.next());
        System.out.println(it.next());
        System.out.println(it.next());
        System.out.println(it.next());*///NoSuchELementException :表示被请求的元素不存在

        while (it.hasNext()){
//            System.out.println(it.next());
            String s = it.next();
            System.out.println(s);
        }
    }
}

清晰版

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

public class CollectionDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //boolean add(E e)
        c.add("hello");
        c.add("world");
        c.add("java");
//lterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
        Iterator<String> it = c.iterator();
        while (it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
    }
}

1.6.集合的使用步骤

image-20201119095227447

1.7.案例: Collection集合储存学生对象并遍历

需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合

思路:

①定义学生类

②创建Collection集合对象

③创建学生对象

④把学生添加到集合

⑤遍历集合(迭代器方式)

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

public class CollectionlistTest {
    public static void main(String[] args) {
        //创建Collection集合对象
        Collection<Student> c = new ArrayList<Student>();
        //创建学生对象
        Student s1 = new Student("张三", 30);
        Student s2 = new Student("李四", 26);
        Student s3 = new Student("王五", 33);

        //把学生添加到集合
        c.add(s1);
        c.add(s2);
        c.add(s3);
        Iterator<Student> it = c.iterator();
        while (it.hasNext()) {
            Student st = it.next();
            System.out.println(st.getName() + ", " + st.getAge());
        }
    }
}
/*
张三, 30
李四, 26
王五, 33
*/
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

23.2 List

2.1 List集合的概述和特点

List集合概述

  • 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
  • 与Set集合不同,列表通常允许重复的元素

List集合特点

  • 有序:存储和取出的元素顺序一致
  • 可重复:存储的元素可以重复
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("你");
        list.add("吃了");
        list.add("几个");
        list.add("菜啊?");

        //迭代器的方法遍历
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String s = it.next();
            System.out.print(s);
        }
    }
}
/*
你吃了几个菜啊?
*/

2.2 List集合特有的方法

image-20201119105023442

add


import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");

        list.add(1,"text");
        System.out.println(list);
    
    }
}
/*
[hello, text, java, world]
*/

remove

import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");

        System.out.println(list.remove(1));
        System.out.println(list);
    }
}
/*
java
[hello, world]
*/

set

import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");

        System.out.println(list.set(1,"c++"));
        System.out.println(list);
    }
}
/*
java
[hello, c++, world]
*/

get

import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");

        System.out.println(list.get(2));
        System.out.println(list);
    }
}
/*
world
[hello, java, world]
*/

遍历集合

import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");

        for (int i=0; i<list.size();i++){
            String s = list.get(i);
            System.out.println(s);

        }
    }
}

2.3 案例:List集合储存学生对象并遍历

需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合

思路:

①定义学生类

②创建List

③集合对象创建学生对象

④把学生添加到集合

⑤遍历集合(迭代器方式,for循环方式)

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

public class ListDemo01 {
    public static void main(String[] args) {
        //创建list集合对象
        List<Student> list = new ArrayList<Student>();
        //创建学生对象
        Student s1 = new Student("张三",30);
        Student s2 = new Student("李四",35);
        Student s3 = new Student("王五",25);

        //把学生添加到集合
        list.add(s1);
        list.add(s2);
        list.add(s3);
        System.out.println("----迭代器方式遍历----");
 //-----------------------迭代器方式----------------
        Iterator<Student> it = list.listIterator();
        while (it.hasNext()){
            Student s = it.next();
            System.out.println(s.getName()+", "+s.getAge());
        }
 //----------------for循环方式遍历-------------------
        System.out.println("----for循环方式遍历----");
        for (int i=0; i<list.size();i++){
            Student s = list.get(i);
            System.out.println(s.getName()+", "+s.getAge());
        }
    }
}
/*
----迭代器方式遍历----
张三, 30
李四, 35
王五, 25
----for循环方式遍历----
张三, 30
李四, 35
王五, 25
*/
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

2.4并发修改异常

ConcurrentModificationException

  • 当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。

产生原因

  • 迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致。

解决方案

  • 用for循环遍历,然后用集合对象做对应的操作即可。
public class ListDemo02 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String s = it.next();
            if (s.equals("world")){
                list.add("javaee");
            }
        }
        //输出集合对象
        System.out.println(list);
    }
}
/*
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.java2.listText.ListDemo02.main(ListDemo02.java:20)
*/

修改异常后

import java.util.ArrayList;
import java.util.List;
/*
  ConcurrentModificationException当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。
 */
public class ListDemo02 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");

        for (int i=0; i<list.size();i++){
            String s =list.get(i);
            if (s.equals("world")){
                list.add("javaee");
            }
        }
        System.out.println(list);
    }
}
//[hello, java, world, javaee]

2.5.Listlterator

Listlterator:列表迭代器

  • 通过List集合的listlterator())方法得到,所以说它是List集合特有的迭代器

  • 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置

Listlterator中的常用方法:

方法说明
E next():返回迭代中的下一个元素
boolean hasNext():如果迭代具有更多元素,则返回true
E previous():返回列表中的上一个元素
boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元
void add(E e):将指定的元素插入列表

不常用了解即可

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorDemo03 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();

        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");
        //通过list集合的listIterator()方法得到
        ListIterator<String> lit = list.listIterator();
        while (lit.hasNext()){
            String s = lit.next();
            System.out.println(s);
        }
        System.out.println("-----------");
        //逆向遍历
        while (lit.hasPrevious()){
            String s = lit.previous();
            System.out.println(s);
        }
    }
}

列表迭代器

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorDemo03 {
    public static void main(String[] args) {
        //穿件集合对象
        List<String> list = new ArrayList<String>();
        //添加元素
        list.add("hello");
        list.add("java");
        list.add("world");
		//列表迭代器
        ListIterator<String> lit = list.listIterator();
        while (lit.hasNext()){
            String s = lit.next();
            if (s.equals("world")){
                lit.add("javase");
            }
        }
        System.out.println(list);
    }
}
/*
[hello, java, world, javase]
*/

2.6.增强for循环

增强for:简化数组和Collection集合的遍历

  • 实现lterable接口的类允许其对象成为增强型for语句的目标
  • 它是JDK5之后出现的,其内部原理是一个lterator迭代器

增强for的格式

  • 格式:

for(元素数据类型变量名:数组或者Collection集合){

//在此处使用变量即可,该变量就是元素

}

  • 范例:
int[] arr = {1,2,3,4,5};

for(int i:arr){
	System.out.println(i);
}
import java.util.ArrayList;
import java.util.List;

public class ForDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        for (int i : arr) {
            System.out.println(i);
        }
        System.out.println("----");

        String[] strArray = {"hello", "world", "java"};
        for (String s : strArray) {
            System.out.println(s);
        }
        System.out.println("----");

        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        for (String s : list) {
            System.out.println(s);
        }
        //内部原理是一个Iterator迭代器 抛出异常
        for (String s :list){
            if (s.equals("world")){
                list.add("javase");//ConcurrentModificationException
            }
        }
    }
/*
1
2
3
4
5
----
hello
world
java
----
hello
world
java
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.java2.listText.ForDemo.main(ForDemo.java:28)

Process finished with exit code 1

*/

2.7.案例:List集合储存学生对象用三种方式遍历

需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合

思路:

①定义学生类

②创建List集合对象

③创建学生对象

④把学生添加到集合

⑤遍历集合

​ 迭代器:集合特有的遍历方式

​ 普通for:带有索引的遍历方式

​ 增强for:最方便的遍历方式

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

public class ListDemo05 {
    public static void main(String[] args) {
        //创建list集合对象
        List<Student> list = new ArrayList<Student>();
        //创建学生对象
        Student s1 = new Student("张三", 30);
        Student s2 = new Student("李四", 35);
        Student s3 = new Student("王五", 25);

        //把学生添加到集合
        list.add(s1);
        list.add(s2);
        list.add(s3);
//            迭代器:集合特有的遍历方式
        Iterator<Student> it = list.iterator();
        while (it.hasNext()) {
            Student s = it.next();
            System.out.println(s.getName() + ", " + s.getAge());
        }
        System.out.println("---------");
//         普通for:带有索引的遍历方式
        for (int i = 0;i<list.size();i++){
            Student s = list.get(i);
            System.out.println(s.getName() + ", " + s.getAge());

        }
        System.out.println("-------");
//            增强for:最方便的遍历方式
        for (Student s:list){
            System.out.println(s.getName() + ", " + s.getAge());
        }
    }
}
/*
张三, 30
李四, 35
王五, 25
---------
张三, 30
李四, 35
王五, 25
-------
张三, 30
李四, 35
王五, 25
*/
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

2.8.数据结构

数据结构是计算机存储、组织数据的方式。是指相互之间存在一种或多种特定关系的数据元素的集合通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率

2.8.1 常见数据结构之栈

在这里插入图片描述

2.8.2 常见数据结构之队列

在这里插入图片描述

2.8.3 常见数据结构之数组

查询快,增删慢,添加效率低

在这里插入图片描述

2.8.4常见数据值链表

格式特点

在这里插入图片描述

描述

在这里插入图片描述

储存

在这里插入图片描述

添加b插入ac之间

在这里插入图片描述

删除数据BD之间的数据c

在这里插入图片描述

增删快 ,查询慢(对比数组)

在这里插入图片描述

2.9.List集合子类特点

List集合常用子类:ArrayList,LinkedList

  • ArrayList:底层数据结构是数组,查询快,增删慢

  • LinkedList:底层数据结构是链表,查询慢,增删快

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

public class ListDemo06 {
    public static void main(String[] args) {
        //创建集合对象
        ArrayList<String> array = new ArrayList<String>();

        array.add("hello");
        array.add("world");
        array.add("java");
        //遍历
        for (String s :array){
            System.out.println(s);
        }
        System.out.println("-----迭代器-----");
        Iterator<String> it = array.iterator();
        while (it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("----for循环索引-----");
        for(int i = 0; i<array.size(); i++){
            String s = array.get(i);
            System.out.println(s);
        }
        
        System.out.println("--------");
        LinkedList<String> linkedList = new LinkedList<String>();

        linkedList.add("hello");
        linkedList.add("world");
        linkedList.add("java");
        for (String s : linkedList){
            System.out.println(s);
        }
    }
}

2.10.案例:ArrayList集合储存学生对象用三种方式遍历

需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合

思路:

①定义学生类

②创建ArrayList集合对象

③创建学生对象

④把学生添加到集合

⑤遍历集合

​ 迭代器:集合特有的遍历方式

​ 普通for:带有索引的遍历方式

​ 增强for:最方便的遍历方式

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

public class ArrayListDemo01 {
    public static void main(String[] args) {
        //创建arraylist集合对象
        ArrayList<Student> array = new ArrayList<Student>();

        //创建学生对象
        Student s1 = new Student("张三", 30);
        Student s2 = new Student("李四", 35);
        Student s3 = new Student("王五", 25);
        //把学生添加到集合
        array.add(s1);
        array.add(s2);
        array.add(s2);
        System.out.println("---迭代器----");
        Iterator<Student> it = array.iterator();
            while (it.hasNext()){
                Student s = it.next();
                System.out.println(s.getName()+", "+s.getAge());
            }
        System.out.println("---普通for---");
            for (int i = 0;i<array.size();i++){
                Student s = array.get(i);
                System.out.println(s.getName()+", "+s.getAge());
            }
        System.out.println("---增强for--");
        for (Student s : array){
            System.out.println(s.getName()+", "+s.getAge());

        }
    }
}

2.11.LinkedList集合的特有功能

在这里插入图片描述

public void addFirst(E e):在该列表开头插入指定的元素
public void addLast(E e):将指定的元素追加到此列表的末尾

import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> lk = new LinkedList<String>();

         lk.add("hello");
         lk.add("world");
         lk.add("java");
//public void addFirst(E e):在该列表开头插入指定的元素
//public void addLast(E e):将指定的元素追加到此列表的末尾
        lk.addFirst("javase");
        lk.addLast("javaee");
        System.out.println(lk);

    }
}
/*
[javase, hello, world, java, javaee]
*/

public E getFirst():返回此列表中的第一个元素
public E getLast():返回此列表中的最后—个元素

import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> lk = new LinkedList<String>();

         lk.add("hello");
         lk.add("world");
         lk.add("java");

//public E getFirst():返回此列表中的第一个元素
//public E getLast():返回此列表中的最后—个元素
        System.out.println(lk.getFirst());
        System.out.println(lk.getLast());

    }
}
/*
hello
java
*/

public E removeFirst():从此列表中册除并返回第一个元素
public E removeLast():从此列表中删除并返回最后一个元素

import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> lk = new LinkedList<String>();

         lk.add("hello");
         lk.add("world");
         lk.add("java");
//public E removeFirst():从此列表中册除并返回第一个元素
//public E removeLast():从此列表中删除并返回最后一个元素
        System.out.println(lk.removeFirst());
        System.out.println(lk.removeLast());
        System.out.println(lk);
    }
}
/*
hello
java
[world]
*/

23.3.Set

3.1.Set集合的概述和特点

Set集合特点

  • 不包含重复元素的集合

  • 没有带索引的方法,所以不能使用普通for循环遍历

Set集合练习

  • 存储字符串并遍历

HashSet:对集合的迭代顺序不做任何保证

import java.util.HashSet;
import java.util.Set;

public class SetDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        Set<String> set = new HashSet<String>();
        //添加元素
        set.add("hello");
        set.add("java");
        set.add("world");
        for (String s : set) {
            System.out.println(s);

        }
    }
}
/*
java
world
hello
*/

3.2.哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

Object类中有一个方法可以获取对象的哈希值

  • public int hashCode):返回对象的哈希码值

对象的哈希值特点

  • 同一个对象多次调用hashCode(方法返回的哈希值是相同的

  • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

public class HashDemo {
    public static void main(String[] args) {
        Student s1 =new Student("风清扬",65);
        //同一个对象多次调用hashCode()方法返回的哈希值是相同的
        System.out.println(s1.hashCode());
        System.out.println(s1.hashCode());
        System.out.println("----");

        //默认情况下不同对象的哈希值是不相同的
//        通过方法重写可以实现不同对象哈希值是相同的
        Student s2 =new Student("风清扬",65);
        System.out.println(s2.hashCode());
        System.out.println("------");
        System.out.println("hello".hashCode());
        System.out.println("java".hashCode());

    }
}
/*
21685669
21685669
----
2133927002
------
99162322
3254818
*/

3.3 HashSet集合概述和特点

HashSet集合特点

  • 底层数据结构是哈希表

  • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致

  • 没有带索引的方法,所以不能使用普通for循环遍历

  • 由于是Set集合,所以是不包含重复元素的集合

HashSet集合练习

  • 存储字符串并遍历
import java.util.HashSet;

public class HashSetDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        HashSet<String> hs = new HashSet<String>();
        //添加元素
        hs.add("hello");
        hs.add("world");
        hs.add("java");
        //遍历
        for (String s :hs){
            System.out.println(s);
        }
    }
}
/*
world
java
hello
*/

在这里插入图片描述

HashSet集合存储元素:

要保证元素唯一性,需要重写hashCode()equals()

3.4 常见数据结构之哈希表

哈希表

  • JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
  • JDK8以后,在长度比较长的时候,底层实现了优化

在这里插入图片描述

3.5.案例:HashSet集合储存学生对象并遍历

需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合

要求:学生对象的成员变量值相同,我们就认为是同一个对象

思路:

①定义学生类

②创建HashSet集合对象

③创建学生对象

④把学生添加到集合

⑤遍历集合(增强for)

⑥在学生类中重写两个方法

​ hashCode()和equals()自动生成即可

import java.util.HashSet;

public class HashSetDemo03 {
    public static void main(String[] args) {
        //创建HashSet集合对象
        HashSet<Student> hs = new HashSet<Student>();
        //创建学生对象
        Student s1 = new Student("杨过",27);
        Student s2 = new Student("王重阳",92);
        Student s3 = new Student("郭靖",51);
        Student s4 = new Student("郭靖",51);

        //把学生添加到集合
        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        hs.add(s4);
        //遍历集合(增强for)
        for (Student s : hs){
            System.out.println(s.getName()+", "+s.getAge());
        }
    }
}
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

3.6.LinkedHashSet集合概述和特点

LinkedHashSet集合特点

  • 哈希表和链表实现的Set接口,具有可预测的迭代次序

  • 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的

  • 由哈希表保证元素唯一,也就是说没有重复的元素

LinkedHashSet集合练习·
存储字符串并遍历

import java.util.LinkedHashSet;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();
        //添加元素
        linkedHashSet.add("hello");
        linkedHashSet.add("world");
        linkedHashSet.add("java");

        linkedHashSet.add("java");
        for (String s : linkedHashSet){
            System.out.println(s);
        }
    }
}

3.7 TreeSet集合概述和特点

TreeSet集合特点

  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法

    • TreeSet():根据其元素的自然排序进行排序

    • TreeSet(Comparator comparator):根据指定的比较器进行排序

  • 没有带索引的方法,所以不能使用普通for循环遍历

  • 由于是Set集合,所以不包含重复元素的集合

TreeSet集合练习

  • 存储整数并遍历心
import java.util.TreeSet;

public class TreeSetDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Integer> ts = new TreeSet<Integer>();

        //添加元素
        ts.add(10);
        ts.add(40);
        ts.add(30);
        ts.add(50);
        ts.add(20);
        for (Integer i : ts) {
            System.out.println(i);

        }
    }
/*
10
20
30
40
50
*/

3.8.自然排序Comparable的使用

  • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法

  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

结论

  • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的

  • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法

  • 重写方法时,一定要注意排序规则必须按照要求的主要条件次要条件来写

import java.util.TreeSet;

public class TreeSetDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Student> ts = new TreeSet<Student>();

        Student s1 = new Student("xishi", 29);
        Student s2 = new Student("lisi", 15);
        Student s3 = new Student("zhangsan", 30);
        Student s4 = new Student("wangwu", 33);
        Student s5 = new Student("2bbb", 33);
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        //添加元素
        for (Student s : ts) {
            System.out.println(s.getName() + ", " + s.getAge());

        }
    }
}
/*
lisi, 15
xishi, 29
zhangsan, 30
2bbb, 33
wangwu, 33
*/
public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
//        return -1;
//        int num =s.age - this.age;//降序!
        int num = this.age - s.age;//升序
//      年龄相同时,按照姓名的字母顺序排序
        int num2 = num == 0 ? this.name.compareTo(s.name) : num;
        return num2;
    }
}
 //理解:按照age升序排列,若age相同,则按照姓名的字母顺序排列。注意主要条件是age,次要条件是name。
 //理解:this理解为较大的元素,s理解为较小的元素;若返回this.age – s.age ,则按照升序存储,若返回 s.age – this.age ,则按照降序存储;若返回0,则不添加。 

3.9.比较器排序Comparator的使用

  • 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

结论

  • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的

  • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(To1,T o2)方法

  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写、

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                int num = s1.getAge() - s2.getAge();
                int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
                return num2;
            }
        });
        Student s1 = new Student("xishi", 29);
        Student s2 = new Student("lisi", 15);
        Student s3 = new Student("zhangsan", 30);
        Student s4 = new Student("wangwu", 33);
        Student s5 = new Student("2bbb", 33);
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        //添加元素
        for (Student s : ts) {
            System.out.println(s.getName() + ", " + s.getAge());
        }
    }
}
public class Student  {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

3.10.案例成绩排序

需求:用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合

要求:按照总分从高到低出现

思路:

①定义学生类

②创建TreeSet集合对象,通过比较器排序进行排序

③创建学生对象

④把学生对象添加到集合

⑤遍历集合

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDem01 {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                //主要条件
                int num = s2.getSum() - s1.getSum();
                //次要条件
                int num2 = num == 0 ? s2.getChinese() - s1.getChinese() : num;
                int num3 = num2==0?s2.getName().compareTo(s1.getName()):num2;
                return num3;
            }
        });
        //创建学生对象
        Student s1 = new Student("张三", 98, 100);
        Student s2 = new Student("李四", 95, 95);
        Student s3 = new Student("王五", 100, 93);
        Student s4 = new Student("柳树", 100, 97);
        Student s5 = new Student("黑色", 92, 98);

        Student s6 = new Student("白色", 99, 98);
        Student s7 = new Student("赵云", 99, 98);

        //把学生对象添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        ts.add(s7);
//        遍历集合
        for (Student s : ts) {
            System.out.println(s.getName() + ": " + s.getChinese() + ", " + s.getMath() + ", " + s.getSum());
        }
    }
}
/*  
张三: 98, 100, 198
柳树: 100, 97, 197
赵云: 99, 98, 197
白色: 99, 98, 197
王五: 100, 93, 193
李四: 95, 95, 190
黑色: 92, 98, 190
*/

3.11.不重复的随机数

需求:编写一个程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出

思路:

①创建Set集合对象

②创建随机数对象

③判断集合的长度是不是小于10

​ 是:产生一个随机数,添加到集合

​ 回到3继续

④遍历集合

hashset实现的

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class SetDemo01 {
    public static void main(String[] args) {
//        创建Set集合对象
        Set<Integer> set = new HashSet<Integer>();
        //创建随机数对象
        Random r = new Random();
        while (set.size()<10){
            //产生一个随机数,添加到集合
            int number = r.nextInt(20)+1;
            set.add(number);
        }
        //遍历集合
        for(Integer i :set){
            System.out.println(i);
        }
    }
}
/* 

*/

TreeSet

import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetDemo01 {
    public static void main(String[] args) {
//        创建Set集合对象
        Set<Integer> set = new TreeSet<Integer>();

        //创建随机数对象
        Random r = new Random();
        while (set.size()<10){
            //产生一个随机数,添加到集合
            int number = r.nextInt(20)+1;
            set.add(number);
        }
        //遍历集合
        for(Integer i :set){
            System.out.println(i);
        }
    }
}

23.4.泛型

4.1.泛型概述

泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型,它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢? 顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型

这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口

泛型定义格式:

  • <类型>:指定一种类型的格式。这里的类型可以看成是形参
  • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
  • 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型

泛型的好处:

  • 把运行时期的问题提前到了编译期间
  • 避免了强制类型转换
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class GenericDemo {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<String>();
        //添加元素
        c.add("hello");
        c.add("world");
        c.add("java");

//        遍历集合
        Iterator<String>it = c.iterator();
        while (it.hasNext()){
//            Object obj = it.next();
//            System.out.println(obj);
            String s = it.next();
            System.out.println(s);
        }
    }
}
/* 
hello
world
java
*/

4.2.泛型类

泛型类的定义格式:

  • 格式:修饰符 class 类名<类型> { }
  • 范例:public class Generic < T > { }
    此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//测试类
public class GenericDEmo02 {
    public static void main(String[] args) {
        Student01 s = new Student01();
        s.setName("林青霞");
        System.out.println(s.getName());

        Teacher t =new Teacher();
        t.setAge(30);
        System.out.println(t.getAge());
        System.out.println("--------");

      Generic01<String> g1 = new Generic01<String>();
         g1.setT("林青霞");
        System.out.println(g1.getT());

        Generic01<Integer> g2 = new Generic01<Integer>();
        g2.setT(30);
        System.out.println(g2.getT());
    }
}
//泛型类
public class Generic<T> {

  private T t; 
  
  public T getT() {
  return t; 
  }
  
  public void setT(T t) {
   this.t = t; 
   } 
}

4.3.泛型方法

泛型方法的定义格式:

  • 格式:修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
  • 范例:public < T > void show(T t) { }

带有泛型方法的类:

public class Generic {
	 public <T> void show(T t) { 
	 	System.out.println(t); 
	 } 
}

测试类

public class GenericDemo { 
	public static void main(String[] args) { 
		Generic g = new Generic(); 
		g.show("林青霞"); 
		g.show(30); 
		g.show(true); 
		g.show(12.34); 
	} 
}

4.4.泛型接口

泛型接口的定义格式:

  • 格式:修饰符 interface 接口名<类型> { }
  • 范例:public interface Generic { }

泛型接口:

//接口
public interface GenericDemo03<T> {
    void show (T t);

}

泛型接口实现类:

//实现类
public class Genericlmpl<T> implements GenericDemo03<T>{
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

测试类:

//            测试类
public class GenericDemo01 {
    public static void main(String[] args) {
        GenericDemo03<String> g1 = new Genericlmpl<String>();
        g1.show("林青霞");
        GenericDemo03<Integer> g2 = new Genericlmpl<Integer>();
        g2.show(50);
    }
}
/* 
林青霞
50
*/

4.5.类型通配符

为了表示各种泛型List的父类,可以使用类型通配符

  • 类型通配符:<?>
  • List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
  • 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中

如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限

  • 类型通配符上限:<? extends 类型>
  • List<? extends Number>:它表示的类型是Number或者其子类型

除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限

  • 类型通配符下限:<? super 类型>
  • List<? super Number>:它表示的类型是Number或者其父类型
import java.util.ArrayList;
import java.util.List;

public class GenericDemo {
    public static void main(String[] args) {
        //类型通配符,<?>
        List<?> list1 = new ArrayList<Object>();
        List<?> list2 = new ArrayList<Number>();
        List<?> list3 = new ArrayList<Integer>();
        System.out.println("---------");
//        类型通配符上限:<? extends 类型>
//        左边是最高类型 右边是他的子类型,不可高过左边
        List<? extends Number> list4 = new ArrayList<Number>();
        List<? extends Number> list5 = new ArrayList<Integer>();
//  报错 List<? extends Number> list6 = new ArrayList<Object>();
        System.out.println("------");
        //左边是最低类型,右边是父类,不可低于左边
        List<?super Number> list7 = new ArrayList<Object>();
        List<?super Number> list8 = new ArrayList<Number>();
//报错   List<?super Number> list9 = new ArrayList<Integer>();

    }
}

4.6.可变参数

可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了

  • 格式:修饰符 返回值类型 方法名(数据类型… 变量名) { }
  • 范例:public static int sum(int… a) { }

可变参数注意事项

  • 这里的变量其实是一个数组
  • 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public class ArgsDemo01 { 
	public static void main(String[] args) { 
		System.out.println(sum(10, 20)); 
		System.out.println(sum(10, 20, 30)); 
		System.out.println(sum(10, 20, 30, 40)); 
		System.out.println(sum(10,20,30,40,50)); 
		System.out.println(sum(10,20,30,40,50,60)); 
		System.out.println(sum(10,20,30,40,50,60,70)); 
		System.out.println(sum(10,20,30,40,50,60,70,80,90,100)); 
	} 
	 // public static int sum(int b,int... a) {
	 // return 0; 
	 // } 
	 public static int sum(int... a) { 
		 int sum = 0; 
		 for(int i : a) { 
		 	sum += i; 
		 }
		 return sum; 
	 } 
}

4.7.可变参数的使用

Arrays工具类中有一个静态方法:

  • public static < T> List< T> asList(T… a):返回由指定数组支持的固定大小的列表
  • 返回的集合不能做增删操作,可以做修改操作

List接口中有一个静态方法:

  • public static < E> List< E> of(E… elements):返回包含任意数量元素的不可变列表
  • 返回的集合不能做增删改操作

Set接口中有一个静态方法:

  • public static < E> Set< E> of(E… elements) :返回一个包含任意数量元素的不可变集合
  • 在给元素的时候,不能给重复的元素
  • 返回的集合不能做增删操作,没有修改的方法

public static < T> List< T> asList(T… a):返回由指定数组支持的固定大小的列表

import java.util.Arrays;
import java.util.List;

public class ArgsDemo02 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "java");
//        list.add("javaee");//UnsupportedOperationException不支持请求操作
//        list.remove("world"); //UnsupportedOperationException不支持请求操作
        list.set(1,"javaee");
        System.out.println(list);
    }
}
//返回的集合不能做增删操作,可以做修改操作

public static < E> List< E> of(E… elements):返回包含任意数量元素的不可变列表 --------------java8么有

23.5.Map

5.1. Map集合概述和使用

Map集合的概述:

interface Map<K,V> K:键的类型;V:值的类型
  • 将键映射到值的对象; 不能包含重复的键; 每个键可以映射到最多一个值

  • 举例:学生的学号和姓名

    ​ itheima001 林青霞

    ​ itheima002 张曼玉

    ​ itheima003 王祖贤

创建Map集合的对象

  • 多态的方式
  • 具体实现类HashMap
import java.util.HashMap;
import java.util.Map;

public class MapDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        //V put(K key, V value) 将指定的值与该映射中的指定键相关联
        map.put("dota2","卡尔");
        map.put("dota1","蓝猫");
        map.put("dota","米波");
        map.put("dota","火猫");
        //输出集合对象
        System.out.println(map);
    }
}
/*
{dota=火猫, dota1=蓝猫, dota2=卡尔} 重复的key会覆盖
*/

5.2 Map集合的基本功能

在这里插入图片描述

V remove(Objectkey) 根据键删除键值对元素

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<String,String>();
        map.put("张无忌","小昭");
        map.put("郭靖","黄蓉");
        map.put("瑞克","莫提");
        System.out.println(map.remove("郭靖"));
        System.out.println(map.remove("郭襄"));
        System.out.println(map);
    }
}
/*
黄蓉
null
{瑞克=莫提, 张无忌=小昭}
*/

void clear():移除所有的键值对元素

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        map.clear();
        System.out.println(map);
    }
}
/*
{}
*/

booLean containsKey(object key):判断集合是否包含指定的键

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        
        System.out.println(map.containsKey("郭靖"));
        System.out.println(map.containsKey("郭襄"));
        
        System.out.println(map);
    }
}
/*
true
false
{瑞克=莫提, 郭靖=黄蓉, 张无忌=小昭}
*/

boolean containsValue(0bject value):判断集合是否包含指定的值

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        System.out.println(map.containsValue("小昭"));
        System.out.println(map);
    }
}
/*
true
{瑞克=莫提, 郭靖=黄蓉, 张无忌=小昭}
*/

booLean isEmpty():判断集合是否为空

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        
        System.out.println(map.isEmpty());
        
        System.out.println(map);
    }
}
/*false
{瑞克=莫提, 郭靖=黄蓉, 张无忌=小昭}
*/

int size( ):集合的长度,也就是集合中键值对的个数

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        
        System.out.println(map.size());
        
        System.out.println(map);
    }
}
/*
3
{瑞克=莫提, 郭靖=黄蓉, 张无忌=小昭}
*/

5.3.Map集合的获取功能

在这里插入图片描述

V get(object key):根据键获取值

import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        
        System.out.println(map.get("瑞克"));
        
        System.out.println(map);
    }
}
//莫提

Set keySet():获取所有键的集合

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        Set<String> keySet = map.keySet();
        for (String  key :keySet){
            System.out.println(key);
        }
    }
}
/*
瑞克
郭靖
张无忌
*/

Collection values():获取所有值的集合

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MapDemo02 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
    }
}
/*
莫提
黄蓉
小昭
*/

5.4.Map集合的遍历(方式1)

我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合

  1. 把所有的丈夫给集中起来
  2. 遍历丈夫的集合,获取到每一个丈夫
  3. 根据丈夫去找对应的妻子

转换为Map集合中的操作:

  • 获取所有键的集合。用keySet()方法实现
  • 遍历键的集合,获取到每一个键。用增强for实现
  • 根据键去找值。用get(Object key)方法实现
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        //添加元素
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");
        Collection<String> values = map.values();
        //获取所有键的集合,用keySet()方法实现
        Set<String> keySet = map.keySet();
        //遍历键的集合,获取到每一个键.用增强for实现
        for (String key : keySet) {
            //根据键去找值.用get(Object key)方法实现
            String value = map.get(key);
            System.out.println(key + ", " + value);
        }
    }
}
/*
瑞克, 莫提
郭靖, 黄蓉
张无忌, 小昭
*/

5.5.Map集合的遍历(方式2)

我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合 :

  1. 获取所有结婚证的集合

  2. 遍历结婚证的集合,得到每一个结婚证

  3. 根据结婚证获取丈夫和妻子

转换为Map集合中的操作;

  1. 获取所有键值对对象的集合
    * Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
  2. 遍历键值对对象的集合,得到每一个键值对对象
    * 用增强for实现,得到每一个Map.Entry
  3. 根据键值对对象获取键和值
    用getKey()得到键
    用getValue()得到值
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        //添加元素
        map.put("张无忌", "小昭");
        map.put("郭靖", "黄蓉");
        map.put("瑞克", "莫提");

        //获取所有键值对对象的集合
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        //遍历键值对对象的集合,得到每一个键值对对象
        for (Map.Entry<String, String> me : entrySet) {
            //根据键值对对象获取键和值
            String key = me.getKey();
            String value = me.getKey();
            System.out.println(key + ", " + value);
        }
    }
}
/* 
瑞克, 瑞克
郭靖, 郭靖
张无忌, 张无忌
*/

5.6.案例HashMap集合储存学生对象并遍历

需求:创建一个HashMap集合,键是学号(String),值是学生对象(Student)。存储三个键值对元素,并遍历

思路:

①定义学生类

②创建HashMap集合对象

③创建学生对象

④把学生添加到集合

⑤遍历集合

​ 方式1:键找值

​ 方式2:键值对对象找键和值

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapDemo {
    public static void main(String[] args) {
        //创建HashMap集合对象
        HashMap<String, Student> hm = new HashMap<String, Student>();

        //创建学生对象
        Student s1 = new Student("郭靖", 45);
        Student s2 = new Student("杨过", 19);
        Student s3 = new Student("张无忌", 22);
        //把学生添加到集合
        hm.put("射雕英雄", s1);
        hm.put("神雕侠侣", s2);
        hm.put("倚天屠龙", s3);
        //方式1:键找值
        Set<String> keySet = hm.keySet();
        for (String key : keySet) {
            Student value = hm.get(key);
            System.out.println(key + ", " + value.getName() + ", " + value.getAge());

            }
        System.out.println("--------------");
        //方式2:键值对对象找键和值
        Set<Map.Entry<String, Student>> entrySet = hm.entrySet();
        for (Map.Entry<String, Student> me : entrySet) {
            String key = me.getKey();
            Student value = me.getValue();
            System.out.println(key + ", " + value.getName() + ", " + value.getAge());
        }
    }
}
/*
神雕侠侣, 杨过, 19
倚天屠龙, 张无忌, 22
射雕英雄, 郭靖, 45
----------------
神雕侠侣, 杨过, 19
倚天屠龙, 张无忌, 22
射雕英雄, 郭靖, 45
*/

5.7.案例:HashMap集合存储学生对象并遍历

案例需求:
创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象

思路:

①定义学生类

②创建HashMap集合对象

③创建学生对象

④把学生添加到集合

⑤遍历集合

⑥在学生类中重写两个方法

​ hashCode)

​ equals()

import java.util.HashMap;
import java.util.Set;

public class HashMapDemo {
    public static void main(String[] args) {
        //创建HashMap集合对象
        HashMap<Student, String> hm = new HashMap<>();
        //创建学生对象
        Student s1 = new Student("郭靖", 45);
        Student s2 = new Student("杨过", 19);
        Student s3 = new Student("张无忌", 22);
        Student s4 = new Student("张无忌", 22);
        //把学生添加到集合
        hm.put(s1, "射雕英雄");
        hm.put(s2, "神雕侠侣");
        hm.put(s3, "倚天屠龙");
        hm.put(s4, "测试测试");
        //方式1:键找值
        Set<Student> keySet = hm.keySet();
        for (Student key : keySet) {
            String value = hm.get(key);
            System.out.println(value + ", " + key.getName() + ", " + key.getAge());
        }
    }
}

5.8.案例:ArrayList集合存储HashMap元素并遍历

案例需求:
创建一个ArrayList集合,存储三个元素,每一个元素都HashMap
每一个HashMap的键和值都是String,并遍历。

思路:

1:创建ArrayList集合

2:创建HashMap集合,并添加键值对元素

3:把HashMap作为元素添加到ArrayList集合

4:遍历ArrayList集合

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

/*   给出如下的数据:
            第一个HashMap集合的元素:
            孙策    大乔
            周瑜    小乔
            第二个HashMap集合的元素:
            郭靖    黄蓉
            杨过    小龙女
            第三个HashMap集合的元素:
            令狐冲    任盈盈
            林平之    岳灵珊*/
public class ArrayListIudeHashMapDemo {
    public static void main(String[] args) {
        //创建ArrayList集合
        ArrayList<HashMap<String, String>> array = new ArrayList<>();
        //:创建HashMap集合,并添加键值对元素
        HashMap<String, String> hm1 = new HashMap<>();
        hm1.put("孙策", "大乔");
        hm1.put("周瑜", "小乔");
        array.add(hm1);

        HashMap<String, String> hm2 = new HashMap<>();
        array.add(hm1);
        hm2.put("郭靖", "黄蓉");
        hm2.put("杨过", "小龙女");
        array.add(hm2);

        HashMap<String, String> hm3 = new HashMap<>();
        array.add(hm1);
        hm3.put("令狐冲", "任盈盈");
        hm3.put("林平之", "岳灵珊");
        array.add(hm3);

        //遍历ArrayList集合
        for (HashMap<String, String> hm : array) {
            Set<String> keySet = hm.keySet();
            for (String key : keySet) {
                String value = hm.get(key);
                System.out.println(key + "," + value);
            }

        }
    }
}

5.9.案例:HashMap集合存储ArrayList元素并遍历

案例需求:
创建一个HashMap集合,存储三个键值对元素,每一个键值对元素的键是String,值是ArrayList
每一个ArrayList的元素是String,并遍历。

package com.java2.MapText;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
/*
    思路:
        1:创建HashMap集合
        2:创建ArrayList集合,并添加元素
        3:把ArrayList作为元素添加到HashMap集合
        4:遍历HashMap集合

    给出如下的数据:
        第一个ArrayList集合的元素:(三国演义)
          诸葛亮
          赵云
        第二个ArrayList集合的元素:(西游记)
          唐僧
          孙悟空
        第三个ArrayList集合的元素:(水浒传)
          武松
          鲁智深
*/
public class HashMapIncludeArraylistDemo {
    public static void main(String[] args) {
        //创建HashMap集合
        HashMap<String, ArrayList<String>> hm = new HashMap<>();
        //创建ArrayList集合,并添加元素
        ArrayList<String> sgyy = new ArrayList<>();
        sgyy.add("诸葛亮");
        sgyy.add("赵云");
//      把ArrayList作为元素添加到HashMap集合
        hm.put("三国演义", sgyy);

        ArrayList<String> xyj = new ArrayList<>();
        xyj.add("唐僧");
        xyj.add("孙悟空");
//      把ArrayList作为元素添加到HashMap集合
        hm.put("西游记", xyj);

        ArrayList<String> shz = new ArrayList<>();
        shz.add("武松");
        shz.add("鲁智深");
//      把ArrayList作为元素添加到HashMap集合
        hm.put("水浒传", shz);

        //遍历HashMap集合
        //得到键的集合
        Set<String> keySet = hm.keySet();
        //遍历键的集合得到 键key
        for (String key : keySet) {
            //根据键得到 值value
            ArrayList<String> value = hm.get(key);
            System.out.println(key+": ");
            //因为值在ArrayList里所有要遍历取得值
            for (String s:value){
                System.out.println("\t"+s);
            }
        }
    }
}

5.10 .统计字符串中每个字符出现的次数

案例需求:
键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”

/*
    思路:
        1:键盘录入一个字符串
        2:创建HashMap集合,键是Character,值是Integer
        3:遍历字符串,得到每一个字符
        4:拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值
            如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储
            如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符和对应的值
        5:遍历HashMap集合,得到键和值,按照要求进行拼接
        6:输出结果
 */
public class HashMapDemo03 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入字符串: ");
        String line = sc.nextLine();
//      2:创建HashMap集合,键是Character,值是Integer
//        HashMap<Character, Integer> hm = newHashMap<>();
        TreeMap<Character, Integer> hm = new TreeMap<>();//对键进行排序

//      遍历字符串,得到每一个字符
        for (int i = 0; i < line.length(); i++) {

//      public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的
            char key = line.charAt(i);

//      4:拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值
            Integer value = hm.get(key);

//      如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储
            if (value == null) {
                hm.put(key,1);
            }else{

//      如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符和对应的值
                value++;
                hm.put(key,value);
            }
        }
//      5:遍历HashMap集合,得到键和值,按照要求进行拼接
        StringBuilder sb = new StringBuilder();
        Set<Character> keySet = hm.keySet();
        for (Character key :keySet){
            Integer value = hm.get(key);
            sb.append(key).append("(").append(value).append(")");

        }
        String result = sb.toString();
        //输出结果
        System.out.println(result);
    }
}

23.6.Collections

6.1.Collections概述和使用

Collections类的概述

  • 是针对集合操作的工具类

Collections类的常用方法:

public static <T extends Comparable<? super T>> void sort(List<T> list):将指定的列表按升序排序
//Collections.sort(....);
    
public static void reverse(List<?> list):反转指定列表中元素的顺序

public static void shuffle(List<?> list):使用默认的随机源随机排列指定的列表
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsDemo01 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(30);
        list.add(20);
        list.add(50);
        list.add(10);
        list.add(40);

/// public static <T extends Comparable<? super T>> void sort(List<T> list):将指定的列表按升序排序
        Collections.sort(list);
// public static void reverse(List<?> list):反转指定列表中元素的顺序
        Collections.reverse(list);
// public static void shuffle(List<?> list):使用默认的随机源随机排列指定的列表
        Collections.shuffle(list);
        System.out.println(list);
    }
}

6.2.案例:ArrayList储存学生对象并排序

案例需求:

ArrayList存储学生对象,使用Collections对ArrayList进行排序
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/*
    思路:
        1:定义学生类
        2:创建ArrayList集合对象
        3:创建学生对象
        4:把学生添加到集合
        5:使用Collections对ArrayList集合排序
        6:遍历集合
 */
public class CollectionsDemo {
    public static void main(String[] args) {
        //创建Arraylist集合对象
        ArrayList<Student01> array = new ArrayList<>();
        //创建学生对象
        Student01 s1 = new Student01("郭靖", 45);
        Student01 s2 = new Student01("杨过", 24);
        Student01 s3 = new Student01("韦小宝", 22);
        Student01 s4 = new Student01("张无忌", 22);
        //把学生添加到集合
        array.add(s1);
        array.add(s2);
        array.add(s3);
        array.add(s4);
        //5:使用Collections对ArrayList集合排序
        //sort (List<T> list, Comparator<? super T> c)比较器
        Collections.sort(array, new Comparator<Student01>() {
            @Override
            public int compare(Student01 s1, Student01 s2) {
  //按照年龄从小到大排序,年龄相同,按照姓名的字母顺序排序
                int num = s1.getAge() - s2.getAge();
                int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
                return num2;
            }
        });
        //遍历集合
        for (Student01 s : array) {
            System.out.println(s.getName() + ", " + s.getAge());
        }
    }
}

6.3.案例:模拟斗地主

案例需求:
通过程序实现斗地主过程中的洗牌,发牌和看牌

import java.util.ArrayList;
import java.util.Collections;

/*
    思路:
        1:创建一个牌盒,也就是定义一个集合对象,用ArrayList集合实现
        2:往牌盒里面装牌
        3:洗牌,也就是把牌打撒,用Collections的shuffle()方法实现
        4:发牌,也就是遍历集合,给三个玩家发牌
        5:看牌,也就是三个玩家分别遍历自己的牌
 */
public class PokerDemo {
    public static void main(String[] args) {
//        1:创建一个牌盒,也就是定义一个集合对象,用ArrayList集合实现
        ArrayList<String> array = new ArrayList<>();

//        2:往牌盒里面装牌
        /*
            ♦2,♦3,♦4...♦K,♦A
            ♣2,...
            ♥2,...
            ♠2,...
            小王,大王
         */
        //定义花色数组
        String[] colors = {"♦", "♣", "♥", "♠"};
        //定义一个点数数组
        String[] numbers = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
        for (String color : colors) {
            for (String number : numbers) {
                array.add(color + number);
            }
        }
        array.add("小王");
        array.add("大王");
//      3:洗牌,也就是把牌打撒,用Collections的shuffle()方法实现
        Collections.shuffle(array);

//      4:发牌,也就是遍历集合,给三个玩家发牌
        ArrayList<String> up1 = new ArrayList<>();
        ArrayList<String> up2 = new ArrayList<>();
        ArrayList<String> up3 = new ArrayList<>();
        ArrayList<String> dpArray = new ArrayList<>();
        for (int i = 0; i < array.size(); i++) {
            String porker = array.get(i);

            if (i >= array.size() - 3) {
                dpArray.add(porker);
            } else if (i % 3 == 0) {
                up1.add(porker);
            } else if (i % 3 == 1) {
                up2.add(porker);
            } else if (i % 3 == 2) {
                up3.add(porker);
            }
        }
//        5:看牌,也就是三个玩家分别遍历自己的牌
        lookpoker("玩家1", up1);
        lookpoker("玩家2", up2);
        lookpoker("玩家3", up3);
        lookpoker("底牌", dpArray);

    }

    //看牌方法
    public static void lookpoker(String name, ArrayList<String> array) {
        System.out.println(name + "的牌是: ");
        for (String poker : array) {
            System.out.print(poker + " ");
        }
        System.out.println();
    }
}

6.4.案例:模拟斗地主升级版

案例需求 :
通过程序实现斗地主过程中的洗牌,发牌和看牌。要求:对牌进行排序

在这里插入图片描述

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

/*
思路:
    1:创建HashMap,键是编号,值是牌
    2:创建ArrayList,存储编号
    3:创建花色数组和点数数组
    4:从0开始往HashMap里面存储编号,并存储对应的牌。同时往ArrayList里面存储编号
    5:洗牌(洗的是编号),用Collections的shuffle()方法实现
    6:发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收)
    7:定义方法看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
    8:调用看牌方法
*/
public class pokerDemoUp {
    public static void main(String[] args) {
//      1:创建HashMap,键是编号,值是牌
        HashMap<Integer, String> hm = new HashMap<>();
//      2:创建ArrayList,存储编号
        ArrayList<Integer> array = new ArrayList<>();
//      3.创建花色数组和点数数组
        String[] colors = {"♦", "♣", "♥", "♠"};
        String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
//      4:从0开始往HashMap里面存储编号,并存储对应的牌。同时往ArrayList里面存储编号
        int index = 0;
        for (String number : numbers) {
            for (String color : colors) {
                hm.put(index, color + number);
                array.add(index);
                index++;
            }
        }
        hm.put(index, "小王");
        array.add(index);
        index++;
        hm.put(index, "大王");
        array.add(index);
//      5:洗牌(洗的是编号),用Collections的shuffle()方法实现
        Collections.shuffle(array);
//      6:发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收)
        TreeSet<Integer> up1 = new TreeSet<>();
        TreeSet<Integer> up2 = new TreeSet<>();
        TreeSet<Integer> up3 = new TreeSet<>();
        TreeSet<Integer> dpSet = new TreeSet<>();

        for (int i = 0; i < array.size(); i++) {
            int x = array.get(i);
            if (i >= array.size() - 3) {
                dpSet.add(x);
            } else if (i % 3 == 0) {
                up1.add(x);
            } else if (i % 3 == 1) {
                up2.add(x);
            } else if (i % 3 == 2) {
                up3.add(x);
            }
        }
//        8:调用看牌方法
        lookPoker("玩家1",up1,hm);
        lookPoker("玩家2",up2,hm);
        lookPoker("玩家2",up3,hm);
        lookPoker("底牌",dpSet,hm);
    }


    //      7:定义方法看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
    public static void lookPoker(String name, TreeSet<Integer> ts, HashMap<Integer, String> hm) {
        System.out.println(name + "的牌是: ");
        for (Integer key : ts) {
            String poker = hm.get(key);
            System.out.print(poker+",");
        }
        System.out.println();
    }
}
/*
玩家1的牌是: 
♣3,♣4,♠4,♥5,♠6,♣7,♠7,♦8,♦9,♥9,♣Q,♥Q,♠Q,♦K,♥A,♦2,♠2,
玩家2的牌是: 
♠3,♣5,♥6,♥8,♠9,♦10,♥10,♠10,♦J,♠J,♣K,♥K,♦A,♠A,♣2,小王,大王,
玩家2的牌是: 
♦3,♥3,♦5,♠5,♦6,♣6,♦7,♥7,♣8,♠8,♣9,♣10,♥J,♦Q,♠K,♣A,♥2,
底牌的牌是: 
♦4,♥4,♣J,
*/

番外:正则表达式

https://www.w3cschool.cn/regexp/zoxa1pq7.html

二十四.IO流

24.1.File

1.1.File 类概述和构造方法

File:它是文件和目录路径名的抽象表示

  • 文件和目录是可以通过File封装成对象的

  • 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的

方法名说明
File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例。
File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例。
import java.io.File;

public class FileDemo01 {
    public static void main(String[] args) {
//      File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 `File`实例。
        File f1 = new File("E:\\javastudent\\src\\com\\java2\\java.txt");
        System.out.println(f1);
//      File(String parent,  String child)从父路径名字符串和子路径名字符串创建新的 `File`实例。
        File f2 = new File("E:\\javastudent\\src\\com\\java2","java.txt");
        System.out.println(f2);
//      File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 `File`实例。
        File f3 = new File("E:\\javastudent\\src\\com\\java2");
        File f4 = new File(f3,"java.txt");
        System.out.println(f4);
    }
}
/*
E:\javastudent\src\com\java2\java.txt
E:\javastudent\src\com\java2\java.txt
E:\javastudent\src\com\java2\java.txt
*/

1.2.File类创建功能

方法名说明
public boolean createNewFile()当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件
public boolean mkdir()创建由此抽象路径名命名的目录
public boolean mkdirs()创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
import java.io.File;
import java.io.IOException;

/*
        public boolean createNewFile()当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件
        如果文件不存在,就创建文件,并返回true;
        如果文件存在,就不创建文件,并返回false

        public boolean mkdir() 创建由此抽象路径名命名的目录
        如果目录不存在,就创建文件,并返回true;
        如果目录存在,就不创建文件,并返回false

        public boolean mkdirs()创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
         如果目录不存在,就创建文件,并返回true;
        如果目录存在,就不创建文件,并返回false

        */
public class FileDemo02 {
    public static void main(String[] args) throws IOException {
    //需求1:我要在E:\专业书 目录下创建一个文件java.txt
        File f1 = new File("E:\\专业书\\java.txt");
        System.out.println(f1.createNewFile());
        System.out.println("-----");

    //需求2:我要在E:\专业书 目录下创建一个目录JavaSE
        File f2 = new File("E:\\专业书\\JavaSE");
        System.out.println(f2.mkdir());
        System.out.println("-----");

    //需求3:我要在E:\专业书 目录下创建一个多级目录JavaWEB\\HTML
        File f3 = new File("E:\\专业书\\JavaWEB\\\\HTML");
        System.out.println(f3.mkdirs());
        System.out.println("-----");

     //需求4:我要唉EE:\专业书 目录下创建一个文件javase.txt
     //创建什么就用什么方式
        File f4 = new File("E:\\专业书\\javaee.txt");
        System.out.println(f4.createNewFile());

    }
}

1.3.File类删除功能

方法名说明
public boolean delete()删除由此抽象路径名表示的文件或目录

绝对路径和相对路径的区别

  • 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。例如: E:\\javastudent\\ljava.txt
  • 相对路径:必须使用取自其他路径名的信息进行解释。例: .\\java.txt

删除目录时的注意事项:

如果一个目录中有内容(目录,文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录

  • 当前目录: 以 ". / " 或者 省略 开头
  • 上一级目录:以" . . / " 开头
import java.io.File;
import java.io.IOException;

public class FileDemo03 {
    public static void main(String[] args) throws IOException {
       //需求1在当前模块目录下创建java.txt
        File f1 = new File(".\\java.txt");
        System.out.println(f1.createNewFile());
        //需求2:删除当前模块目录下的java.txt文件
        System.out.println(f1.delete());
        System.out.println("----");
        //需求3:在当前模块下创建目录
       File f2 = new File(".\\java3");
        System.out.println(f2.mkdir());
//      需求4:删除当前模块目录下的java3目录
        System.out.println(f2.delete());
        System.out.println("----");

        //需求5:在当前模块下创建一个目录java3,然后在该目录下创建一个文件java.txt
        //创建文件前要先创建文件夹
        File f3 = new File(".\\java4");
        System.out.println(f3.mkdir());

        File f4 = new File(".\\java4\\java.txt");
        System.out.println(f4.createNewFile());
//      需求6:删除当前模块下的目录java3
        System.out.println(f4.delete());
        System.out.println(f3.delete());
    }
}

1.4.File类判断和获取功能

在这里插入图片描述

public class FileDemo04 {
    public static void main(String[] args) throws IOException {
        //创建一个file对象
        File f = new File("java.txt");
//public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
        System.out.println(f.isDirectory());
//public boolean isFile() 测试此抽象路径名表示的File是否为文件
        System.out.println(f.isFile());
//public boolean exists() 测试此抽象路径名表示的File是否存在
        System.out.println(f.exists());

//        public String getAbsoLutePath():返回此抽象路径名的绝对路径名字符串
//        public String getPath():将此抽象路径名转换为路径名字符串,返回给定的抽象路径名字符串表示
//        public String getName():返回由此抽象路径名表示的文件或目录的名称,返回的是用file封装的目录或名称
        System.out.println(f.getAbsolutePath());
        System.out.println(f.getPath());
        System.out.println(f.getName());
        System.out.println("--------");

        //public String[] list()返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
        File f2 = new File("E:\\专业书\\测试");
        String[] strArray = f2.list();
        for (String str : strArray) {
            System.out.println(str);
        }
        System.out.println("--------");
        //public File[] listFiles()返回此抽象路径名表示的目录中的文件和目录的File对象数组

        File[] fileArray = f2.listFiles();
        for (File file : fileArray) {
//            System.out.println(file);
            if (file.isFile()) {
                System.out.println(file.getName());
            }
        }
    }
 /*
false
true
true
E:\javastudent\java.txt
java.txt
java.txt
--------
123.docx
321.txt
--------
123.docx
321.txt
 */

1.5.递归

递归概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象

递归解决问题思路:

把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算

递归的注意事项:
递归一定要有出口。否则内存溢出
递归虽然有出口,但是递归的次数也不宜过多。否则内存溢出

public class DiGuiDemo {
    public static void main(String[] args) {
        int[] arr = new int[20];

        arr[0] = 1;
        arr[1] = 1;

        for (int i = 2; i < arr.length; i++) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }
        System.out.println(arr[19]);
        System.out.println(f(20));
    }

    /*
        机柜解决问题,首先就是要定义一个方法,
            定义一个方法(n):表示第n个月的兔子对数
            那么,第n-1个月的兔子对数改如何表示呢?f(n-1)
            同理,第n-2个月的兔子对数该如何表示呢?f(n-2)
     */
    //StackOverflowError当堆栈溢出发生时抛出一个应用程序递归太深。
    public static int f(int n) {
        if (n == 1 || n == 2){
            return 1;
        }else {
            return f(n - 1) + f(n - 2);
        }
    }
}
/*
6765
6765
*/
1.5.1.案例:递归求阶乘

需求:用递归求5的阶乘,并把结果在控制台输出

分析:

①阶乘:一个正整数的阶乘是所有小于及等于该数的正整数的积,自然数n的阶乘写作n!

​ 5!=54321

②递归出口:1!= 1

③递归规则:n!=n*(n-1)!

​ 5! = 5*4!

public class DiGuiDemo02 {
    public static void main(String[] args) {
        int result = jc(5);
        System.out.println("五的阶乘是: "+result);

    }
    public static int jc(int n){
        if (n==1){
            return 1;
        }else {
            return n*jc(n-1);
        }
    }
}
//120
1.5.2.案例遍历目录

需求:给定一个路径(E\itcast),请通过递归完成遍历该目录下的所有内容,并把所有文件的绝对路径输出在控制台

思路:

①根据给定的路径创建一个File对象

②定义一个方法,用于获取给定目录下的所有内容,参数为第1步创建的File对象

③获取给定的File目录下所有的文件或者目录的File数组

④遍历该File数组,得到每一个File对象

⑤判断该File对象是否是目录

​ 是:递归调用

​ 不是:获取绝对路径输出在控制台

⑥调用方法

在这里插入图片描述

24.2.字节流

2.1.IO流概述和介绍

IO流述

  • IO:输入/输出(Input/Output)
  • 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
  • IO流就是用来处理设备间数据传输问题的
  • 常见的应用:文件复制;文件上传;文件下载

IO流的分类

  • 按照数据的流向

    • 输入流:读数据
    • 输出流:写数据
  • 按照数据类型来分
    字节流
    字节输入流 ; 字节输出流
    字符流
    字符输入流 ; 字符输出流

一般来说,我们说IO流的分类是按照数据类型来分的那么这两种流都在什么情况下使用呢?

如果数据通过Window自带的记事本软件打开,我们还可以读懂里面的内容,就使用字符流,否则使用字节流。如果你不知道该使用哪种类型的流,就使用字节流

2.2 字节流写数据

字节流抽象基类

  • lnputStream:这个抽象类是表示字节输入流的所有类的超类
  • OutputStream:这个抽象类是表示字节输出流的所有类的超类
  • 子类名特点:子类名称都是以其父类名作为子类名的后缀

FileOutputStream:文件输出流用于将数据写入File

  • FileOutputStream(String name):创建文件输出流以指定的名称写入文件

使用字节输出流写数据的步骤:

  • 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
  • 调用字节输出流对象的写数据方法
  • 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo01 {
    public static void main(String[] args) throws IOException {
        //创建字节输出流对象
//FileOutputStream(Stringname):创建文件输出流以指定的名称写入文件
        FileOutputStream fos = new FileOutputStream(".\\fos.txt");
        /*
        做了三件事情;
            A:调用系统功能创建了文件
            B:创建了字节输出流对象
            c:让字节输出流对象指向创建好的文件
         */
//  void write (int b),将指定的字节写入此文件输出流
        fos.write(97);
//        fos.write(57);
//        fos.write(55);
//        最后都要释放资源
//void close ():关闭此文件输出流并释放与此流相关联的任何系统资源。
        fos.close();

    }
}

2.3.字节流写数据的三种方式

方法名说明
void write(int b)将指定的字节写入此文件输出流一次写一个字节数据
void write(byte[] b)将b.length字节丛指定的字节数组写入此文件输出流一次写一个字节数组数据
void write(byte[] b,int off,int len)将len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流一次写一个字节数组的部分数据

构造方法;

FileOutputStream(String name):创建文件输出流以指定的名称写入文件

FileOutputStream(file file):创建文件输出流以写入由指定的File对象表示的文件

import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo02 {
    public static void main(String[] args) throws IOException {
//FileOutputStream(String  name):创建文件输出流以指定的名称写入文件, 一般写这个方便
        FileOutputStream fos = new FileOutputStream(".\\fos.txt");
        //new File(name)自动封装了
//      FileOutputStream fos = new FileOutputStream(new File(".\\fos.txt"));

//FileOutputStream(file file):创建文件输出流以写入由指定的File对象表示的文件
//        FileOutputStream fos2 = new FileOutputStream(new File(".\\fos.txt"));

//  void write(int b)将指定的字节写入此文件输出流一次写一个字节数据
/*
        fos.write(97);
        fos.write(98);
        fos.write(99);
        fos.write(100);
        fos.write(101);*/

//void write(byte[] b)将b.length字节丛指定的字节数组写入此文件输出流一次写一个字节数组数据
///        byte[] bys = {97,98,99,100,101,102};
        //byte[] getBytes ():返回字符串对应的字节数组
        byte[] bys = "abcdefg".getBytes();
//        fos.write(bys);
///  void write(byte[] b,int off,int len)将len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流一次写一个字节数组的部分数据
        fos.write(bys, 1, 3);

//        释放资源
        fos.close();
    }
}

2.4.字节流写数据的两个小问题

1.字节流写数据如何实现换行

​ window: \r\n

​ linux: \n

​ mac: \r

2.字节流写数据如何实现追加写入

  • public FileOutputStream (String name , boolean append)

  • 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头

import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo03 {
    public static void main(String[] args) throws IOException {
        //创建字节流输出对象
//        FileOutputStream fos = new FileOutputStream(".\\fos.txt");
        FileOutputStream fos = new FileOutputStream(".\\fos.txt",true);

        //写数据
        for (int i = 0; i < 10; i++){
            fos.write("hello".getBytes());
            fos.write("\r\n".getBytes());
        }
        //释放资源
        fos.close();
    }
}

2.5.字节流写数据加异常处理

==finally:==在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源

特点:被finally控制的语句一定会执行,除非JVM退出

在这里插入图片描述

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo04 {
    public static void main(String[] args) throws FileNotFoundException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(".\\fos.txt");
            fos.write("hello".getBytes());

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null)
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }

    }
}

2.6.字节流读数据(一次读一个字节数据)

需求:把文件fos.txt中的内容读取出来在控制台输出

FilelnputStream:从文件系统中的文件获取输入字节

  • FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name 命名。

使用字节输入流读数据的步骤 :

① 创建字节输入流对象

② 调用字节输入流对象的读数据方法

③ 释放资源

import java.io.FileInputStream;
import java.io.IOException;

public class FileIntputStreamDemo01 {
    public static void main(String[] args) throws IOException {
        //创建字节输入流对象
        //FileInputStream(String name)
        FileInputStream fis = new FileInputStream("E:\\javastudent\\fos.txt");

//        调用字节输入流对象的读数据方法
//        int read() 从该输入流读取一个字节的数据。

/*
        //第一次读取数据
        int by = fis.read();
        System.out.println(by);
        System.out.println((char) by);
        System.out.println("-----");
        //第二次读取数据
        by = fis.read();
        System.out.println(by);
        System.out.println((char) by);
        System.out.println("-----");
        //再多读取两次
        by = fis.read();
        System.out.println(by);
        by = fis.read();
        System.out.println(by);
*/

/*        int by = fis.read();
        while (by != -1) {
            System.out.print((char) by);
            by = fis.read();
        }*/
//        优化上面的程序
        int by;
        /*
        fis.read( ):读数据
        by=fis.read():把读取到的数据赋值给by
        by != -1:判断读取到的数据是否是-1*/

        while ((by = fis.read()) != -1) {
            System.out.print((char) by);
        }
//        释放资源
        fis.close();
    }
}

2.7.案例:复制文本文件

需求:把“E:\\窗里窗外.txt”复制到模块目录下的“窗里窗外.txt"
分析:
①复制文本文件,其实就把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)

数据源:

E:\\窗里窗外.txt —读数据— InputStream —FilelnputStream

目的地:

窗里窗外.txt—写数据— OutputStream -----FileOutputStream

思路:

①根据数据源创建字节输入流对象

②根据目的地创建字节输出流对象

③读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)

④释放资源

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyTxtDemo {
    public static void main(String[] args) throws IOException {
        //根据数据源创建字节输入流对象
        FileInputStream fis = new FileInputStream("E:\\窗里窗外.txt");
        //根据目的地创建字节输出流对象
        FileOutputStream fos = new FileOutputStream("窗里窗外.txt");
        //读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
        int by;
        while ((by=fis.read())!=-1){
            fos.write(by);
        }
        //释放资源
        fos.close();
        fis.close();
    }
}

2.8字节流读数据(一次读一个字节数组数据)

需求:把文件fos.txt中的内容读取出来在控制台输出

使用字节输入流读数据的步骤:
①创建字节输入流对象
②调用字节输入流对象的读数据方法
③释放资源

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.String;

public class FileInputStreamDemo02 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("fos.txt");
//调用字节输入流对象的读数据方法
//int read (byte[] b),从该输入流读取最多b.length个字节的数据到一个字节数组
//    byte[] bys =new byte[5];
//
//        //第一次读取数据
//        int len =fis.read(bys);
//        System.out.println(len);
//        //String (byte[] bytes)
//        System.out.println(new String(bys,0,len));
//
//        //第二次读取数据
//        len =fis.read(bys);
//        System.out.println(len);
//        System.out.println(new String(bys,0,len));
//
//        //第三次读取数据
//        len =fis.read(bys);
//        System.out.println(len);
        //String (byte[] bytes, int offset, int length)
//        System.out.println(new String(bys,0,len));
        byte[] bys = new byte[1024];//1024及其整数倍
        int len;
        while ((len = fis.read(bys)) != -1) {
            System.out.println(new String(bys,0,len));

        }
    //释放资源
        fis.close();
    }
}
/*
hello
world
*/

2.9.案例复制图片

需求:把“E:\litcastlImn.jpg”复制到模块目录下的“mn.jpg"

思路:

①根据数据源创建字节输入流对象

②根据目的地创建字节输出流对象

③读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)

④释放资源

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyJpgDemo {
    public static void main(String[] args) throws IOException {
        //根据数据源创建字节输入流对象
        FileInputStream fis = new FileInputStream("D:\\Backup\\我的文档\\My Pictures\\Saved Pictures\\mn.jpg");
        //根据目的地创建字节输出流对象
        FileOutputStream fos = new FileOutputStream("mn.jpg");

        //读取数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
        byte[] bys = new byte[1024];
        int len;
        while ((len = fis.read(bys)) != -1) {
            fos.write(bys, 0, len);
        }
        //释放资源
        fos.close();
        fis.close();
    }
}

2.10.字节缓冲流

字节缓冲流:

  • BufferOutputStream: 该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用

  • BufferedInputStream: 创建Bufferedlnputstream将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节

构造方法:

  • 字节缓冲输出流: BufferedOutputStream(OutputStream out)
  • 字节缓冲输入流: BufferedInputStream(InputStream in)

为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?

  • 字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class BufferStreamDemo {
    public static void main(String[] args) throws IOException {
//        字节缓冲输出流: BufferedOutputStream(OutputStream out)
/*        FileOutputStream fos = new FileOutputStream("bos.txt");
          BufferedOutputStream bos = new BufferedOutputStream(fos);*/
/*
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
        //写数据
        bos.write("hello\r\n".getBytes());
        bos.write("world\r\n".getBytes());
        bos.close();*/
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bos.txt"));
        //读数据
        //一次读取一个字节数据
/*        int by;
        while ((by=bis.read())!=-1){
            System.out.print((char) by);
        }
        System.out.println("-----");
        bis.close();*/
//      一次读取一个字节数据的数据
        byte[] bys = new byte[1024];
        int len;
        while ((len=bis.read(bys))!=-1){
            System.out.println(new String(bys,0,len));

        }
        bis.close();
    }
}

2.11.复制视频(改成了gif图)

需求:把“E:字节流复制图片.avi”复制到模块目录下的“字节流复制图片.aviT

思路:

①根据数据源创建字节输入流对象

②根据目的地创建字节输出流对象

③读写数据,复制视频

④释放资源

1.基本字节流一依读写一个字节

/*
    四种方式实现复制视频,并记录每种方式复制视频的时间
        1.基本字节流一依读写一个字节 共耗时: 4986毫秒
        2.基本字节流一次读写一个字节数组 共耗时: 5253毫秒
        3.字节缓冲流一次读写一个字节	共耗时: 54毫秒
        4.字节缓冲流一次读写—个字节数组 共耗时: 5毫秒
*/

import java.io.*;

public class CopyVideo {
    public static void main(String[] args) throws IOException {
        //记录开始时间
        long startTime = System.currentTimeMillis();

        //复制视频
//        method1();
//        method2();
//        method3();
          method4();


        //记录结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("共耗时: " + (endTime - startTime) + "毫秒");
    }
    //        4.字节缓冲流一次读写—个字节数组
    public static void method4() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Backup\\我的文档\\My Pictures\\Saved Pictures\\上个图很难吗.gif"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("上个图很难吗.gif"));

        byte[] bys = new byte[1024];
        int len;
        while ((len=bis.read(bys))!=-1){
            bos.write(bys,0,len);

        }
        bos.close();
        bis.close();
    }
    //        3.字节缓冲流一次读写一个字节
    public static void method3() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Backup\\我的文档\\My Pictures\\Saved Pictures\\上个图很难吗.gif"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("上个图很难吗.gif"));
        int by;
        while ((by = bis.read()) != -1) {
            bos.write(by);
        }
        bos.close();
        bis.close();
    }

    public static void method2() throws IOException {
        FileInputStream fis = new FileInputStream("D:\\Backup\\我的文档\\My Pictures\\Saved Pictures\\上个图很难吗.gif");
        FileOutputStream fos = new FileOutputStream("上个图很难吗.gif");

        byte[] bys = new byte[1024];
        int len;
        while ((len = fis.read()) != -1) {
            fos.write(bys, 0, len);
        }
        fos.close();
        fis.close();
    }

    public static void method1() throws IOException {
        FileInputStream fis = new FileInputStream("D:\\Backup\\我的文档\\My Pictures\\Saved Pictures\\上个图很难吗.gif");
        FileOutputStream fos = new FileOutputStream("上个图很难吗.gif");

        int by;
        while ((by = fis.read()) != -1) {
            fos.write(by);
        }
        fos.close();
        fis.close();
    }
}

24.3.字符流

3.1.为什么会出现字符流

由于字节流操作中文不是特别的方便,所以Java就提供字符流

字符流=字节流+编码表

用字书流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?

  • 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负

3.2.编码表

基础知识:

  • 计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制丁转换之后的结果

  • 按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。这里强调一下:按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号。否则就会导致乱码现象

    字符编码:就是一套自然语言的字符与二进制数之间的对应规则(A65)

字符集:

  • 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

  • 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

ASCIl字符集:

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)

  • 基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

GBXXX字符集:

  • GB2312:简体中文码表。一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名等都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了

  • ==GBK:==最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等

  • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等

Unicode字符集:

  • 为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF32。最为常用的UTF-8编码

  • UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UJTF-8编码。它使用—至四个字节为每个字符编码

    编码规则:

    ​ 128个US-ASCII字符,只需一个字节编码

    ​ 拉丁文等字符,需要二个字节编码

    ​ 大部分常用字(含中文),使用三个字节编码

    ​ 其他极少使用的Unicode辅助字符,使用四字节编码

小结:采用何种规则编码,就要采用对应规则解码,否则就会出现乱码

3.3.字符串中的编码解码问题

编码:

  • byte[]getBytes():使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中

  • byte[]getBytes(String charsetName):使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中

解码:

  • String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String

  • String(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的String

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class StringDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //定义一个字符串
        String s = "中国";
//      byte[]getBytes():使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中

//        byte[] bys = s.getBytes();//[-28, -72, -83, -27, -101, -67]

//       byte[] bys = s.getBytes("UTF-8");//[-28, -72, -83, -27, -101, -67]
        byte[] bys = s.getBytes("GBK");//[-42, -48, -71, -6]
        System.out.println(Arrays.toString(bys));

//        String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
//        String ss = new String(bys);

//        String(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
//        String ss = new String(bys,"UTF-8");平台默认字符集
        String ss = new String(bys,"GBK");
        System.out.println(ss);
    }
}
/*
[-42, -48, -71, -6]
中国
*/

3.4.字符流中的编码解码问题

字符流抽象基类

  • Reader: 字符输入流的抽象类
  • Writer:字符输出流的抽象类

字符流中和编码解码问题相关的两个类:

  • lnputStreamReader

  • OutputStreamWriter

import java.io.*;

public class ConversionStreamDemo {
    public static void main(String[] args) throws IOException {
/*      OutputStreamWriter (OutputStream out)创建一个使用默认字符编码的outputStreamWriter。
        OutputStreamWriter (OutputStream out,String chorsetName)创建一个使用命名字符集的outputStreamWriter*/
//        FileOutputStream fos = new FileOutputStream("osw.txt");
//        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));//中国
//        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"),"UTF-8");//中国
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"), "GBK");//�й�
        osw.write("中国");
        osw.close();

/*      InputStreamReader (InputStream in)创建一个使用默认字符集的InputStreamReader。
        InputStreamReader (InputStream in,String charsetName)创建一个使用命名字符集的InputStreamReader。*/
        //GBk写的GBK的编码方式读
        InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"),"GBK");
        //一次读取一个字符数据
        int ch;
        while ((ch = isr.read()) != -1) {
            System.out.print((char)ch);
        }
        isr.close();
    }
}
/*
中国
*/

3.5字符流写数据的5中方式

方法名说明
void write(int c)写一个字符
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分
方法名说明
flush()刷新流,还可以继续写数据
close()关闭流,释放资源,但是在关闭之前会先刷新流.一旦关闭,就不能再写数据

void write(int c) 写一个字符

/*构造方法:
   OutputStreamWriter (OutputStream out):创建一个使用默认字符编码的OutputStreamWriter*/

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
    //   OutputStreamWriter (OutputStream out):创建一个使用默认字符编码的OutputStreamWriter*/
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));

        //void write(int c)写一个字符
        osw.write(97);
        //viod flush();刷新流
        osw.flush();
        osw.write(98);
        osw.flush();
        osw.write(100);
        
        osw.close();
    }
}

void write(char[] cbuf)写入一个字符数组

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
    //   OutputStreamWriter (OutputStream out):创建一个使用默认字符编码的OutputStreamWriter*/
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));

//        void write(char[] cbuf)写入一个字符数组
        char[] chs = {'a','b','c','d','e'};
        osw.write(chs);

        osw.close();
    }
}

void write(char[] cbuf, int off, int len)写入字符数组的一部分

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
    //   OutputStreamWriter (OutputStream out):创建一个使用默认字符编码的OutputStreamWriter*/
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));

        char[] chs = {'a','b','c','d','e'};
//        void write(char[] cbuf, int off, int len)写入字符数组的一部分
//          osw.write(chs,0,chs.length);
            osw.write(chs,1,3);
        osw.close();
    }
}

void write(String str) 写一个字符串

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
    //   OutputStreamWriter (OutputStream out):创建一个使用默认字符编码的OutputStreamWriter*/
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));

        char[] chs = {'a','b','c','d','e'};

//        void write(String str)  写一个字符串
            osw.write("abcdef");

        osw.close();
    }
}

void write(String str, int off, int len) 写一个字符串的一部分

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
    //   OutputStreamWriter (OutputStream out):创建一个使用默认字符编码的OutputStreamWriter*/
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));
        
        char[] chs = {'a','b','c','d','e'};
//void write(String str, int off, int len)  写一个字符串的一部分
//        osw.write("abcdef",0,"abcdef".length());
            osw.write("abcdef",1,3);

        osw.close();
    }
}

3.6.字符流读数据的两种方式

方法名说明
int read()一次读一个字符数据
int read(char[] cbuf)一次读一个字符数组数据

int read() 一次读一个字符数据

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputStreamReaderDemo {
    public static void main(String[] args)throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"));
   int ch;
        while ((ch=isr.read())!=-1){
            System.out.print((char)ch);
        }
    isr.close();
    }
}
/*
hello
world
java
*/

int read(char[] cbuf) 一次读一个字符数组数据

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"));

        char[] chs = new char[1024];
        int len;
        while ((len = isr.read(chs)) != -1) {
            System.out.print(new String(chs, 0, len));
        }
        isr.close();
    }
}
/*
hello
world
java
*/

3.7.案例复制java文件

需求:把模块目录下的student.java复制到模块目录下的 Copy.java

思路:

1:根据数据源创建字符输入流对象

2:根据目的地创建字符输出流对象

3:读写数据,复制文件

4:释放资源

import java.io.*;

public class CopyJavaDemo01 {
    public static void main(String[] args) throws IOException {
//      1.根据数据源创建字符输入流对象
        InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\javastudent\\src\\com\\java2\\ArrayText\\Student.java"));
//      2:根据目的地创建字符输出流对象
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("Copy.java"));

        //读写数据,复制文件
        //一次读写一个字符数据
/*        int ch;
        while ((ch=isr.read())!=-1){
            osw.write(ch);
        }*/
        
     //一次读写一个字符数据数组
        char[] chars = new char[1024];
        int len;
        while ((len=isr.read(chars ))!=-1) {
            osw.write(chars, 0, len);

        }
        isr.close();
        osw.close();
    }
}

3.8.案例复制java文件(改进版)

分析:

①转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类

②FileReader:用于读取字符文件的便捷类

FileReader(String fileName)

③FileWriter:用于写入字符文件的便捷类

FileWriter(String fileName)

④数据源和目的地的分析

​ 数据源: “E:\javastudent\src\com\java2\ArrayText\Student.java”–读数据----Reader — InputStreamReader— FileReader

​ 目的地: Student.java—写数据— Writer —OutputStreamWriter—FileWriter

需求:把模块目录下的student.java复制到模块目录下的 Copy.java

思路:
①根据数据源创建字符输入流对象

②根据目的地创建字符输出流对象

③读写数据,复制文件

④释放资源

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyJavaDemoUp {
    public static void main(String[] args) throws IOException {
        //根据数据源创建字符输入流对象
        FileReader fr = new FileReader("E:\\javastudent\\src\\com\\java2\\ArrayText\\Student.java");
        //根据目的地创建字符输出流对象
        FileWriter fw = new FileWriter("Copy.java");
        //读写数据,复制文件
/*        int ch;
        while ((ch=fr.read())!=-1){
            fw.write(ch);
        }*/
        char[] ch = new char[1024];
        int len;
        while ((len = fr.read(ch)) != -1){
            fw.write(ch,0,len);
        }
            //释放资源
            fr.close();
        fw.close();
    }
}

3.9.字符缓冲流

字符缓冲流:

  • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途

  • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途

构造方法:

  • BufferedWriter(Writer out)

  • BufferedReader(Reader in)

import java.io.*;

/*
构造方法:
      BufferedWriter(Writer out)

      BufferedReader(Reader in)
*/
public class BufferedStreamDemo01 {
    public static void main(String[] args) throws IOException {
        //  BufferedWriter(Writer out)
/*        BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
        bw.write("hello\r\n");
        bw.write("world\r\n");
        bw.write("java\r\n");
        bw.close();*/
//      BufferedReader(Reader in)
        BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
        //一次读一个字符数据
/*        int ch;
        while ((ch = br.read()) != -1) {
            System.out.print((char) ch);
        }*/
        
        //一次读取一个字符数据数组
        char[] chs = new char[1024];
        int len;
        while ((len = br.read(chs))!= -1) {
            System.out.println(new String(chs,0,len));
        }
        br.close();
    }
}

3.10.案例复制java文件(字符缓冲流改进版)

需求:把模块目录下的student.java复制到模块目录下的 Copy.java

思路:

①根据数据源创建字符缓冲输入流对象

②根据目的地创建字符缓冲输出流对象

③读写数据,复制文件

④释放资源

import java.io.*;

public class CopyJavaDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("E:\\javastudent\\src\\com\\Java1\\Text5\\StudentManager.java"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("StudentManager.java"));

/*        int ch;
        while ((ch=br.read())!=-1){
            bw.write(ch);
        }*/
        char[] chs = new char[1024];
        int len;
        while ((len=br.read(chs))!=-1){
            bw.write(chs,0,len);
        }


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

3.11.字符缓冲流特有功能

BufferedWriter:

  • void newLine():写一行行分隔符,行分隔符字符串由系统属性定义

BufferedReader:

  • public String readLine():读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedStreamDemo02 {
    public static void main(String[] args) throws IOException {
/*        BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

        for (int i = 0; i < 10; i++) {
            bw.write("hello" + i);
//    void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
            bw.newLine();//万用换行

            bw.flush();
        }
        bw.close();*/
        BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
//        public String readLine()读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null

        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        br.close();
    }
}

3.12.案例复制java文件(字符缓冲流特有的功能改进版)

import java.io.*;

public class CopyJavaDemo02 {
    public static void main(String[] args) throws IOException {
        //根据数据源创建字符缓冲流输入流对象
        BufferedReader br = new BufferedReader(new FileReader("E:\\javastudent\\src\\com\\Java1\\Text5\\StudentManager.java"));
        //根据目的地创建字符缓冲流输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
        //读取数据复制文件,使用字符缓冲流特有的功能实现
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);//写数据
            bw.newLine();//写换行符
            bw.flush();//刷新流
        }
        //释放资源
        br.close();
        bw.close();
    }
}

3.13.IO流小结

字节流

在这里插入图片描述

在这里插入图片描述

字符流

在这里插入图片描述

在这里插入图片描述

24.4.字符流案例

4.1.案例:集合到文件

需求:把ArrayList集合中的字符串数据写入到文本文件。要求:每一个字符串元素作为文件中的一行数据

思路:

①创建ArrayList集合

②往集合中存储字符串元素

③创建字符缓冲输出流对象

④遍历集合,得到每一个字符串数据

⑤调用字符缓冲输出流对象的方法写数据释放资源

⑥释放资源

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

public class ArrayListToTxtDemo {
    public static void main(String[] args) throws IOException {
        //创建ArrayList集合
        ArrayList<String> array = new ArrayList<>();

        //往集合中储存字符串元素
        array.add("hello");
        array.add("world");
        array.add("java");
        //创建字符缓冲输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("array.txt"));
        //遍历集合记得字符串的数据
        for (String s :array){
           //调用字符缓冲输出流对象的方法写数据
            bw.write(s);
            bw.newLine();
            bw.flush();
        }
        bw.close();
    }

4.2.案例:文件到集合

需求:把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个集合元素

思路:
①创建字符缓冲输入流对象

②创建ArrayList集合对象

③调用字符缓冲输入流对象的方法读数据

④把读取到的字符串数据存储到集合中

⑤释放资源

⑥遍历集合

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

public class TxtToArrayListDemo {
    public static void main(String[] args) throws IOException {
        //创建字符缓冲流对象
        BufferedReader br = new BufferedReader(new FileReader("array.txt"));
        ArrayList<String> array = new ArrayList<>();
        //调用字符缓冲输入流对象的方法读取数据
        String line;
        while ((line=br.readLine())!=null){
            //把读取到的字符串数据储存到集合中
            array.add(line);
        }
            br.close();
        for (String s :array ){
            System.out.println(s);
        }
    }
}

4.3.案例:点名器

需求:我有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器.

思路:

①创建字符缓冲输入流对象

②创建ArrayList集合对象

③调用字符缓冲输入流对象的方法读数据

④把读取到的字符串数据存储到集合中

⑤释放资源

⑥使用Random产生一个随机数,随机数的范围在:[O,集合的长度)

⑦把第6步产生的随机数作为索引到ArrayList集合中获取值

⑧把第7步得到的数据输出在控制台

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class CallNameDemo {
    public static void main(String[] args)throws IOException {
        //创建字符缓冲输入流对象
        BufferedReader br = new BufferedReader(new FileReader("name.txt"));
        ArrayList<String> array = new ArrayList<>();

        //调用字符缓冲输入流对象的方法读数据
        String line;
        while ((line=br.readLine())!=null){
            array.add(line);
        }
        br.close();
        Random r =new Random();
        int index = r.nextInt(array.size());
//        ⑦把第6步产生的随机数作为索引到ArrayList集合中获取值
        String name = array.get(index);
//        ⑧把第7步得到的数据输出在控制台
        System.out.println("幸运者是, "+ name);
    }
}
/*
幸运者是, 小小
*/
幽鬼
裂魂人
德鲁伊
月之女祭司
昆卡
小小
马格纳斯
哈斯卡
路西法
卡尔

4.4.案例:集合到文件(改进版)

需求:把ArrayList集合中的学生数据写入到文本文件。要求:每一个学生对象的数据作为文件中的一行数据

格式:学号,姓名,年龄,居住地 举例:itheima001,林青霞,30,西安

思路:

①定义学生类

②创建ArrayList集合

③创建学生对象

④把学生对象添加到集合中

⑤创建字符缓冲输出流对象

⑥遍历集合,得到每一个学生对象

⑦把学生对象的数据拼接成指定格式的字符串⑧调用字符缓冲输出流对象的方法写数据

⑧释放资源

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

public class ArrayLIstToFileDemo {
    public static void main(String[] args) throws IOException {
        ArrayList<Student> array = new ArrayList<>();
        Student s1 = new Student("001", "卡尔", 300, "西安");
        Student s2 = new Student("002", "昆卡", 30, "武汉");
        Student s3 = new Student("003", "蓝猫", 20, "杭州");
        array.add(s1);
        array.add(s2);
        array.add(s3);

        BufferedWriter bw = new BufferedWriter(new FileWriter("student.txt"));

        for (Student s : array) {
            //把学生对象的数据拼接成指定格式的字符串
            StringBuffer sb = new StringBuffer();
            sb.append(s.getSid()).append(", ").append(s.getName()).append(", ").append(s.getAge()).append(", ").append(s.getAddress());
            bw.write(sb.toString());
            bw.newLine();
            bw.flush();
        }
        bw.close();
    }
}
public class Student {
    private String sid;
    private String name;
    private int age;
    private String address;

    public Student() {
    }

    public Student(String sid, String name, int age, String address) {
        this.sid = sid;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getSid() {
        return sid;
    }

    public void setSid(String sid) {
        this.sid = sid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

4.5.案例:文件到集合(改进版)

需求:把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个学生对象的成员变量值

​ 举例: itheima001,林青霞,30,西安

思路:

①定义学生类

②创建字符缓冲输入流对象

③创建ArrayList集合对象

④调用字符缓冲输入流对象的方法读数据

⑤把读取到的字符串数据用split()进行分割,得到一个字符串数组

⑥创建学生对象

⑦把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值

⑧把学生对象添加到集合

⑨释放资源

⑩遍历集合

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

public class FileToArrayListDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("student.txt"));
        //创建集合
        ArrayList<Student> array = new ArrayList<>();

//        ④调用字符缓冲输入流对象的方法读数据
        String line;
        while ((line = br.readLine()) != null) {
//            把读取到的字符串数据用split()进行分割,得到一个字符串数组
            String[] strArray = line.split(",");

//            创建学生对象
            Student s = new Student();
//            把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值
            s.setSid(strArray[0]);
            s.setName(strArray[1]);
            s.setAge(Integer.parseInt(strArray[2]));
            s.setAddress(strArray[3]);
//           把学生对象添加到集合
            array.add(s);
        }
        br.close();
        for (Student s : array) {
            System.out.println(s.getSid() + ", " + s.getName() + ", " + s.getAge() + ", " + s.getAddress());
        }
    }
}
/*
001,  卡尔, 48, 西安
002,  昆卡, 30, 武汉
003,  蓝猫, 20, 杭州
*/

student.txt

001, 卡尔,48,西安
002, 昆卡,30,武汉
003, 蓝猫,20,杭州
### 4.6.案例:集合到文件(数据排序改进版)

需求:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩)。要求按照成绩总分从高到低写入文本文件

格式:  姓名,语文成绩,数学成绩,英语成绩  		举例:林青霞,98,99,100

思路:

①定义学生类

②创建TreeSet集合,通过比较器排序进行排序

③键盘录入学生数据

④创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量

⑤把学生对象添加到TreeSet集合

⑥创建字符缓冲输出流对象

⑦遍历集合,得到每一个学生对象

⑧把学生对象的数据拼接成指定格式的字符串

⑨调用字符缓冲输出流对象的方法写数据

⑩释放资源

```java
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;

public class TreeSetToFIleDemo {
    public static void main(String[] args) throws IOException {
        //创建TreeSet集合,通过比较器排序进行排序
        TreeSet<StudentTest> ts = new TreeSet<>(new Comparator<StudentTest>() {
            @Override
            public int compare(StudentTest s1, StudentTest s2) {
                //成绩总分从高到低
                int num = s2.getSum() - s1.getSum();
                //次要条件
                int num2 = num == 0 ? s2.getChinese() - s1.getChinese() : num;
                int num3 = num2 == 0 ? s2.getMath() - s1.getMath() : num2;
                int num4 = num3 == 0 ? s2.getName().compareTo(s1.getName()) : num3;
                return num4;
            }
        });
        //键盘录入学生数据
        for (int i = 0; i <3; i++) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请录入第" + (i + 1) + "个学生信息");
            System.out.println("姓名: ");
            String name = sc.nextLine();
            System.out.println("语文成绩");
            int chinese = sc.nextInt();
            System.out.println("数学成绩");
            int math = sc.nextInt();
            System.out.println("英语成绩");
            int english = sc.nextInt();
//            创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量
            StudentTest s = new StudentTest();
            s.setName(name);
            s.setChinese(chinese);
            s.setMath(math);
            s.setEnglish(english);
//            把学生对象添加到TreeSet集合
            ts.add(s);
        }
//            创建字符缓冲输出流对象
            BufferedWriter bw = new BufferedWriter(new FileWriter("ts.txt"));
//            遍历集合,得到每一个学生对象
            for (StudentTest st :ts){
//                把学生对象的数据拼接成指定格式的字符串
//                格式:  姓名,语文成绩,数学成绩,英语成绩
                StringBuilder sb = new StringBuilder();
                sb.append(st.getName()).append(", ").append(st.getChinese()).append(", ").append(st.getMath()).append(", ").append(st.getEnglish()).append(", ").append(st.getSum());
//                调用字符缓冲输出流对象的方法写数据
                bw.write(sb.toString());
                bw.newLine();
                bw.flush();
            }
            bw.close();
        }
    }
/* 
请录入第1个学生信息
姓名: 
张三
语文成绩
7
数学成绩
8
英语成绩
9
请录入第2个学生信息
姓名: 
李四
语文成绩
8
数学成绩
7
英语成绩
9
请录入第3个学生信息
姓名: 
王五12
语文成绩
14
数学成绩
12
英语成绩
2
*/
public class StudentTest {
    private String name;
    private int chinese;
    private int math;
    private int english;

    public StudentTest() {
    }

    public StudentTest(String name, int chinese, int math, int english) {
        this.name = name;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getChinese() {
        return chinese;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getEnglish() {
        return english;
    }

    public void setEnglish(int english) {
        this.english = english;
    }

    public int getSum() {
        return this.chinese + this.math+this.english;
    }
}

4.7.案例:复制单级文件夹

需求:把“E:\\专业书”这个文件夹复制到模块目录下

思路:

①创建数据源目录File对象,路径是E:\\专业书

②获取数据源目录File对象的名称(专业书)

③创建目的地目录File对象,路径名是模块名+专业书 组成\\专业书

④判断目的地目录对应的File是否存在,如果不存在,就创建

⑤获取数据源目录下所有文件的File数组

⑥遍历File数组,得到每一个File对象,该File对象,其实就是数据源文件

​ 数据源文件:E:\\专业书\\Java核心技术卷 2.pdf

⑦获取数据源文件file对象的名称(Java核心技术卷 2.pdf)

⑧创建目的地文件File对象,路径名是目的地目录+Java核心技术卷 2.pdf组成 \\专业书\\Java核心技术卷 2.pdf

⑨复制文件

​ 由于文件不仅仅是文本文件,还有图片,视频等文件,所以采用字节流复制文件

import java.io.*;

public class Demo {
    public static void main(String[] args) throws IOException{
        //1.创建数据源目录对象
        File srcFolder = new File("E:\\专业书");

        //2.获取数据源目录File对象的名称
        String srcFolderName = srcFolder.getName();

        //3.创建目的地目录File对象,路径名是模块名+srcFolderName组成
        File destFolder = new File("E:\\javastudent",srcFolderName);

        //4.判断目的地目录对应的File是否存在,如果不存在,就创建
        if (!destFolder.exists()){
            destFolder.mkdir();
        }

        //5.获取数据源目录下所有文件的File数组
        File[] listFiles = srcFolder.listFiles();

        //6.遍历File数组,得到每一个File对象,该File对象,其实就是数据源目录下的每一个数据源文件
        for (File srcFile : listFiles){
            //7.获取每一个数据源文件File对象的名称
            String srcFileName = srcFile.getName();

            //8.创建目的地文件File对象,路径名是目的地目录+数据源文件名称组成
            File destFile = new File(destFolder,srcFileName);

            //9.复制文件
            copyFile(srcFile, destFile);
        }
    }

    private static void copyFile(File srcFile, File destFile) throws IOException {
        //字节缓冲输入流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
        //字节缓冲输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));

        //读写数据
        byte[] bys = new byte[1024];
        int len;
        while ((len=bis.read(bys))!=-1){
            bos.write(bys,0,len);
        }

        //释放资源
        bis.close();
        bos.close();
    }
}

4.8.案例:复制多级文件夹

需求:把“E:\专业书”复制到E盘目录下

思路:
①创建数据源File对象,路径是E:\专业书

②创建目的地File对象,路径是E:\

③写方法实现文件夹的复制,参数为数据源File对象和目的地File对象

④判断数据源File是否是目录
是:
A:在目的地下创建和数据源File名称一样的目录

​ B:获取数据源File下所有文件或者目录的File数组

​ C:遍历该File数组,得到每一个File对象

​ D:把该File作为数据源File对象,递归调用复制文件夹的方法

​ 不是:说明是文件,直接复制,用字节流

import java.io.*;

public class CopyFoldersDemo {
    public static void main(String[] args) throws IOException{
        //创建数据源file对象,路径是E:\专业书
        File srcFile = new File("E:\\专业书");
        //创建目的地File对象,路径是D:\\
        File desFile = new File("D:\\");
//      ③写方法实现文件夹的复制,参数为数据源File对象和目的地File对象
        copyFolder(srcFile,desFile);

    }
//  复制文件夹
    private static void copyFolder(File srcFile, File desFile)throws IOException {
    //④判断数据源File是否是目录
        if (srcFile.isDirectory()){
//        在目的地下创建和数据源File名称一样的目录
            String srcFileName = srcFile.getName();
            File newFolder = new File(desFile,srcFileName);//E:\专业书
            if (!newFolder.exists()){
                newFolder.mkdir();
            }
//       B:获取数据源File下所有文件或者目录的File数组
            File[] fileArray = srcFile.listFiles();
//       C:遍历该File数组,得到每一个File对象
            for (File file :fileArray){
//       D:把该File作为数据源File对象,递归调用复制文件夹的方法
                copyFolder(file,newFolder);
            }
        }else{
//            不是:说明是文件,直接复制,用字节流
            File newfile = new File(desFile,srcFile.getName());
            copyFile(srcFile,newfile);
        }
    }

    private static void copyFile(File srcFile, File destFile) throws IOException {
        //字节缓冲输入流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
        //字节缓冲输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));

        //读写数据
        byte[] bys = new byte[1024];
        int len;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
        }

        //释放资源
        bis.close();
        bos.close();
    }
}

4.9.复制文件的异常处理

img

调用流对象的时候,抛出了异常,这时就需要需要处理异常,下面是处理异常的方式

直接抛出去

package com.testIO;

import java.io.*;

public class IOExceptionDemo {
    public static void main(String[] args) throws IOException{
        mathod();
    }
    private static void mathod() throws IOException {
        FileReader fr = new FileReader("fr.txt");
        FileWriter fw = new FileWriter("fw.txt");
        char[] chs = new char[1024];
        int len;
        while ((len = fr.read()) != -1){
            fw.write(chs,0,len);
        }
        fw.close();
        fr.close();
    }
}

最标准的写法,用try…catch…finally

package com.testIO;
import java.io.*;
public class IOExceptionDemo {
    public static void main(String[] args) {
        mathod();
    }
    private static void mathod() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader("fr.txt");
            fw = new FileWriter("fw.txt");
            char[] chs = new char[1024];
            int len;
            while ((len = fr.read()) != -1) {
                fw.write(chs, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDK7的改进方案,try()括号内生成流对象,会自动关闭

package com.testIO;
import java.io.*;
public class IOExceptionDemo {
    public static void main(String[] args) {
        mathod();
    }
    private static void mathod() {
        try (FileReader fr = new FileReader("fr.txt");
             FileWriter fw = new FileWriter("fw.txt");) {
            char[] chs = new char[1024];
            int len;
            while ((len = fr.read()) != -1) {

                fw.write(chs, 0, len);

            }

        } catch (IOException e) {

            e.printStackTrace();

        }
    }
}

JDK9的改进方案,try()括号内不那么麻烦了,但是还是需要抛出异常

package com.testIO;
import java.io.*;
public class IOExceptionDemo {
    public static void main(String[] args) throws IOException {
        mathod();

    }
    private static void mathod() throws IOException {
        FileReader fr = new FileReader("fr.txt");
        FileWriter fw = new FileWriter("fw.txt");
        try (fr; fw) {
            char[] chs = new char[1024];
            int len;
            while ((len = fr.read()) != -1) {
                fw.write(chs, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

相比之下,JDK7的改进方案是最好的

24.5.特殊操作流

5.1.标准输入输出流

System类中有两个静态的成员变量:

  • public static final lnputStreamin:标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
  • public static final PrintStream out:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标

自己实现键盘录入数据太麻烦了,所以java提供了Scanner使用

​ 标准输入流 用法

Scanner scanner = new Scanner(System.in);
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;

public class SystemInDemo {
    public static void main(String[] args) throws IOException {
        //public static final lnputStreamin:标准输入流
//        InputStream is = System.in;
//
//        int by;
//        while ((by=in.read())!=-1){
//            System.out.print((char)by);
//        }
//        InputStreamReader isr = new InputStreamReader(is);
        //使用字符流能不能够实现一次读取一行数据呢?可以
        //但是,一次读取一行数据的方法是字符缓冲输入流的特有方法

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一个字符串: ");
        String line = br.readLine();
        System.out.println("你输入的字符串是: " + line);
        System.out.println("请输入一个整数: ");
        int i = Integer.parseInt(br.readLine());
        System.out.println("你输入的整数是: " + i);
        //自己是先键盘录入数据太麻烦了,所以java提供了Scanner使用
        Scanner scanner = new Scanner(System.in);
    }
}

​ 标准输出流用法

import java.io.PrintStream;

public class SystemOutDemo {
    public static void main(String[] args) {
        //    public static final PrintStream out:标准输出流
        PrintStream ps = System.out;
//        ps.print("hello");
//        ps.print(100);
        
        ps.println(100);
        ps.println("hello");
        
         // System.out 本质是个字节输出流
        System.out.println();
    }
}

/*
100
hello
*/

5.2.打印流

打印流分类:

  • 字节打印流:PrintStream
  • 字符打印流:PrintWriter

打印流的特点:

  • 只负责输出数据,不负责读取数据
  • 有自己的特有方法
5.2.1.字节打印流(PrintStream)
  • PrintStream (String fileName):使用指定的文件名创建新的打印流

  • 使用继承父类的方法写数据(write等),查看的时候会转码;使用自己的特有方法写数据,查看的数据原样输出

import java.io.IOException;
import java.io.PrintStream;

public class PrintStramDemo {
    public static void main(String[] args) throws IOException {
        //PrintStream(String fileName):使用指定的文件名创建新的打印流
        PrintStream ps = new PrintStream("ps.txt");

        //写数据
        //字节输出流的方法
        ps.write(97);

        
        //使用特有方法写数据
        ps.print(97);
        ps.println();
        ps.print(98);

        //释放资源
        ps.close();
    }
}
5.2.2.字符打印流(PrintWriter)

在这里插入图片描述

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
//PrintWriter (String fiLeName):使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新\
        PrintWriter pw = new PrintWriter("pw.txt");

/*        pw.write("hello");
        pw.write("\r\n");
        pw.flush();
        pw.write("world");
        pw.write("\r\n");
        pw.flush();*/

       /* pw.println("hello");
        pw.flush();
        pw.println("你好");
        pw.flush();*/
//PrintWriter (Writer out,boolean autoFlush):创建一个新的PrintWriter
        PrintWriter pw2 = new PrintWriter(new FileWriter("pw.txt"),true);
        //false的话不刷新
        pw2.println("hello");
        pw2.println("world");
    }
}

5.3.案例:复制java文件(打印流改进版)

需求:把模块目录下的PrintStreamDemo.java复制到模块目录下的Copy.java
思路:
①根据数据源创建字符输入流对象

②根据目的地创建字符输出流对象

③读写数据,复制文件

④释放资源

import java.io.*;

public class CopyJavaDemo04 {
    public static void main(String[] args) throws IOException {
/*        //根据数据源创建字符输入流对
        BufferedReader br = new BufferedReader(new FileReader("demo0123.txt"));
        //根据目的地创建字符输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\javastudent\\Copy.java"));
//        读写数据,复制文件
        String line;
        while ((line=br.readLine())!=null){
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        //释放资源
        bw.close();
        br.close();*/
 //=======================改进版=====================================
        BufferedReader br = new BufferedReader(new FileReader("demo0123.txt"));
        //根据目的地创建字符输出流对象
        PrintWriter pw = new PrintWriter(new FileWriter("E:\\javastudent\\Copy.java"),true);
        String line;
        while ((line=br.readLine())!=null){
            pw.println(line);
        }
        br.close();
        pw.close();
    }
}

24.6.对象序列化流

对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化.
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流:

  • 对象序列化流:ObjectOutputStream
  • 对象反序列化流:ObjectInputStream

6.1.对象序列化流

对象序列化流:ObjectOutputStream

  • 将Java对象的原始数据类型和图形写入Outputstream。可以使用ObjectInputStream读取(重构)对象。可以通

过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象

构造方法:

  • ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream

序列化对象的方法:

  • void writeObject(Object obj):将指定的对象写入ObjectOutputStream

注意:

  • 一个对象要想被序列化,该对象所属的类必须必须实现Serializable接口
  • Serializable是一个标记接口,实现该接口,不需要重写任何方法

操作类

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class ObjectOutputStreamDemo {
    public static void main(String[] args)throws IOException {
//        ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("oos.txt"));
        //创建对象
        Dog d = new Dog("阿黄", 2);

//        void writeObject(Object obj):将指定的对象写入ObjectOutputStream
        oos.writeObject(d);
        //释放资源
        oos.close();

    }
}

对象

import java.io.Serializable;

public class Dog implements Serializable {
    private String name;
    private int age;

    public Dog() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

结果

�� sr com.java2.FileText02.Dog'q��dv�) I ageL namet Ljava/lang/String;xp   t 阿黄

6.2.对象反序列化流

对象反序列化流:ObjectlnputStream

  • ObjectlnputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象

构造方法:

  • ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream

反序列化对象的方法:

  • Object readObject(: 从ObjectInputStream读取一个对象
import java.io.*;

public class ObjectInputStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
//   ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt"));
//  Object readObject(: 从ObjectInputStream读取一个对象
        Object obj = ois.readObject();
        Dog d = (Dog) obj;
        System.out.println(d.getName() + ", " + d.getAge());
        ois.close();
    }
}
//读取了oos.txt
/*
阿黄, 2
*/

6.3.serialVersionUID&transient

用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?

  • 会出问题,抛出InvalidClassException异常

如果出问题了,如何解决呢?

  • 给对象所属的类加一个serialVersionUID

    ​ private static final long serialVersionUID = 42L;

如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

  • 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
//Exception in thread "main" java.io.InvalidClassException:
// com.java2.FileText02.Dog; local class incompatible:
// stream classdesc serialVersionUID = 2842322208996891689,
// local class serialVersionUID = 5019684140316748393
/*当序列化运行时检测到类中的以下问题之一时抛出。
        类的串行版本与从流中读取的类描述符的类型不匹配
        该类包含未知的数据类型
        该类没有可访问的无参数构造函数*/
import java.io.*;

public class ObjectStreamDemo {
    public static void main(String[] args) throws IOException,ClassNotFoundException {
//        write();
        read();
    }
    //反序列化
    private static void read() throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt"));
        Object obj  = ois.readObject();
        Dog d =(Dog) obj;
        System.out.println(d.getName()+", "+d.getAge());

    }
    //序列化
    private static void write()throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("oos.txt"));
        Dog d = new Dog("阿黄",2);
        oos.writeObject(d);
        oos.close();
    }
}
/*
阿黄, 2
*/
import java.io.Serializable;

public class Dog implements Serializable {
    private static final long serialVersionUID = 42L;//设置固定UID
    private String name;
//    private int age;
    private transient int age;//不参与序列化过程

    public Dog() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

//    @Override
//    public String toString() {
//        return "Dog{" +
//                "name='" + name + '\'' +
//                ", age=" + age +
//                '}';
//    }
}

6.4.Properties

Properties概述:

  • 是一个Map体系的集合类

  • Properties可以保存到流中或从流中加载

练习: Properties作为Map集合的使用

import java.util.Properties;
import java.util.Set;

/*
    Properties作为Map集合的使用
*/
public class PropertiesDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        Properties prop = new Properties();
        //存储元素
        prop.put("001","小强");
        prop.put("002","小白");
        prop.put("003","阿黄");

        //遍历集合
        Set<Object> keySet = prop.keySet();
        for (Object key:keySet){
            Object value = prop.get(key);
            System.out.println(key+", "+value);
        }
    }
}
6.4.1.Properties作为集合的特有方法
方法名说明
Object setProperty(Stringkey,String value)设置集合的键和值,都是String类型,底层调用Hashtable方法 put
String getProperty(String key)使用此属性列表中指定的键搜索属性
SetstringPropertyNames()从该属性列表中返回—个不可修改的键集,其中键及其对应的值是字符串
import java.util.Properties;
import java.util.Set;

public class PropertiesDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Properties prop = new Properties();
//        Object setProperty(Stringkey,String value),设置集合的键和值,都是String类型,底层调用Hashtable方法 put
        prop.setProperty("001","小强");
        prop.setProperty("002","小白");
        prop.setProperty("003","小黑");
/*
        System.out.println(prop);
*/
//        String getProperty(String key)使用此属性列表中指定的键搜索属性
        /*System.out.println(prop.getProperty("001"));*/

//        Set<String> stringPropertyNames()从该属性列表中返回—个不可修改的键集,其中键及其对应的值是字符串
        Set<String> names = prop.stringPropertyNames();
        for (String key :names){
//            System.out.println(key);
            String value = prop.getProperty(key);
            System.out.println(key+", "+value);
        }
    }
}
6.4.2.Properties和IO流集合的方法
方法名说明
void load(InputStream inStream)从输入字节流读取属性列表(键和元素对)
void load(Reader reader)从输入字符流读取属性列表(键和元素对)
void store(OutputStream out, String comments)将此属性列表(键和元素对)写入此 Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流
void store(Writer writer,String comments)将此属性列表〔键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流
/*
load 
store
*/
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class propertiesDemo03 {
    public static void main(String[] args) throws IOException {
        //把集合中的数据保存到文件
        mystore();

        //把文件中的数据加载到集合
        myLoad();
    }

    private static void myLoad() throws IOException {
        Properties prop = new Properties();
        FileReader fr =new FileReader("fw.txt");
        prop.load(fr);
        fr.close();
        System.out.println(prop);
    }

    private static void mystore() throws IOException {
        Properties prop = new Properties();
        prop.setProperty("001", "小强");
        prop.setProperty("002", "小蟑");
        prop.setProperty("003", "小螂");
        FileWriter fw = new FileWriter("fw.txt");
//        void store(Writer writer,String comments)将此属性列表〔键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流
        prop.store(fw, null);
        fw.close();
    }
}

6.5.案例:游戏次数

需求:请写程序实现猜数字小游戏只能试玩3次,如果还想玩,提示:游戏试玩已结束,想玩请充值(官网)

思路:

①写一个游戏类,里面有一个猜数字的小游戏

②写一个测试类,测试类中有main()方法,main()方法中按照下面步骤完成:

​ A:从文件中读取数据到Properties集合,用load(方法实现

​ 文件已经存在:game.txt

​ 里面有一个数据值:count=O

​ B:通过Properties集合获取到玩游戏的次数

​ C:判断次数是否到到3次了

​ 如果到了,给出提示:游戏试玩已结束,想玩请充值(官网)

​ 如果不到3次:

​ 玩游戏

​ 次教+1,重新写回文件,用Properties的store()方法实现

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class PropertiesTest {
    public static void main(String[] args) throws IOException {
        //从文件中读取数据到Properties集合,用load(方法实现
        Properties prop = new Properties();
        FileReader fr = new FileReader("game.txt");
        prop.load(fr);
        fr.close();

        //通关properties集合获取到玩游戏的次数
        String count = prop.getProperty("count");
        int number = Integer.parseInt(count);
        //判断是否到3次了
        if (number >= 3) {
            //如果到了,给出提示,游戏试玩已结束,想玩请充值(www.guanwang.com)
            System.out.println("游戏试玩已经结束,想要玩请充值(www.guanwang.com)");
        } else {
            //玩游戏
            GuessNumber.start();
            //次教+1,重新写回文件,用Properties的store()方法实现
            number++;
            prop.setProperty("count", String.valueOf(number));
            FileWriter fw = new FileWriter("game.txt");
            prop.store(fw, null);
            fw.close();
        }
    }
}
/*
游戏试玩已经结束,想要玩请充值(www.guanwang.com)
*/

game.txt 文件

count=0

玩游戏类

import java.util.Random;
import java.util.Scanner;

public class GuessNumber {
    private GuessNumber() {
    }
    public static void start() {
        //要完成猜数字的游戏,首先需要有一个要猜的数字,使用随机数生成该数字,范围1到100
        Random r = new Random();
        int number = r.nextInt(100) + 1;
        while (true) {
            //使用程序实现猜数字,每次均要输入猜测的数字值,需要使用键盘录入实现
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要猜的数字: ");
            int guessNumber = sc.nextInt();

            //比较输入的数字和系统产生的数据,需要使用分支语句,这里使用if  else if的格式,根据不同情况进行猜测结果显示
            if (guessNumber > number) {
                System.out.println("你猜的数字" + guessNumber + "大了");
            } else if (guessNumber < number) {
                System.out.println("你要猜的数字" + guessNumber + "小了");
            }else {
                System.out.println("恭喜你猜中了");
                break;
            }
        }
    }
}

二十五.多线程

5.1.实现多线程

1.1.进程

进程:是正在运行的程序

  • 是系统进行资源分配和调用的独立单位

  • 每一个进程都有它自己的内存空间和系统资源

1.2.线程

线程:是进程中的单个顺序控制流,是一条执行路径

单线程:一个进程如果只有一条执行路径,则称为单线程程序

多线程:一个进程如果有多条执行路径,则称为多线程程序

举例:

  • 记事本程序设置 (单线程)

  • 扫雷程序 (多线程)

1.3.多线程的实现方式

方式1:继承Thread类

  • 定义一个类MyThread继承Thread类在
  • MyThread类中重写run)方法
  • 创建MyThread类的对象
  • 启动线程

两个小问题:

为什么要重写run()方法?

​ 因为run()是用来封装被线程执行的代码

run()方法和start()方法的区别?

​ run():封装线程执行的代码,直接调用,相当于普通方法的调用 start():启动线程;然后由JVM调用此线程的run()方法

1.4.设置和获取现场名称

Thread类中设置和获取线程名称的方法

  • void setName(String name):将此线程的名称更改为等于参数name
  • String getName():返回此线程的名称
  • 通过构造方法也可以设置线程名称

如何获取main()方法所在的线程名称?

  • public static Thread currentThread():返回对当前正在执行的线程对象的引用
public class MyThreadDemo {
    public static void main(String[] args) {
/*        //是thread方法中的
        MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机");

//      void setName(String name):将此线程的名称更改为等于参数name
     *//*   my1.setName("高铁");
        my2.setName("飞机");*//*
        //void start ()导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();*/
//static Thread currentThread ())返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(getName()+": "+i);
        }
    }
}

1.5.线程调度

线程有两种调度模型

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些

Java使用的是抢占式调度模型

假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

Thread类中设置和获取线程优先级的方法

public final int getPriority(): 返回此线程的优先级

public final void setPriority(int newPriority):更改此线程的优先级

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();

        tp1.setName("高铁");
        tp2.setName("飞机");
        tp3.setName("汽车");
//   public final int getPriority():   返回此线程的优先级
/*        System.out.println(tp1.getPriority());//默认优先级:5
        System.out.println(tp2.getPriority());//默认优先级:5
        System.out.println(tp3.getPriority());//默认优先级:5*/

//   public final void setPriority(int newPriority):更改此线程的优先级
//        tp1.setPriority(10000);//IllegalArgumentException非法参数

/*      System.out.println(Thread.MAX_PRIORITY);//10
        System.out.println(Thread.MIN_PRIORITY);//1
        System.out.println(Thread.NORM_PRIORITY);//5*/

        //设置正确的优先级
        tp1.setPriority(5);
        tp2.setPriority(10);
        tp3.setPriority(1);

        tp1.start();
        tp2.start();
        tp3.start();
    }
}

1.6.线程控制

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的亳秒数
void join()等待这个线程死亡
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

sleep方法

public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ", " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadSleepDemo {
    public static void main(String[] args) {
        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ThreadSleep ts3 = new ThreadSleep();

        ts1.setName("曹操");
        ts2.setName("刘备");
        ts3.setName("孙权");

        ts1.start();
        ts2.start();
        ts3.start();
    }
}
/*
曹操, 0
刘备, 0
孙权, 0
孙权, 1
曹操, 1
刘备, 1
刘备, 2
孙权, 2
曹操, 2
孙权, 3
曹操, 3
刘备, 3
*/

join方法

public class ThreadJoin extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ", " + i);
        }
    }
}
public class ThreadJoinDemo {
    public static void main(String[] args) {
        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj1.setName("汤姆");
        tj2.setName("杰瑞");
        tj3.setName("狗");

        tj3.start();
        try {
            tj3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj1.start();
        tj2.start();
    }
}

守护线程

public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();

        td1.setName("关羽");
        td2.setName("张飞");

        //设置主线程为刘备
        Thread.currentThread().setName("刘备");

        // 设置守护线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();

        for (int x = 0; x < 10; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}
public class ThreadDaemon extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

Daemon方法//非守护线程结束后,守护线程会延迟结束

public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();
        ThreadDaemon td3 = new ThreadDaemon();

        td1.setName("关羽");
        td2.setName("张飞");
        td3.setName("刘备");

        // 设置守护线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();
        td3.start();
    }
}
//非守护线程结束后,守护线程会延迟结束
public class ThreadDaemon extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

1.7.线程生命周期

在这里插入图片描述

1.8.多线程的实现方式

方式2:实现Runnable接口

  • 定义一个类MyRunnable实现Runnable接口

  • 在MyRunnable类中重写run()方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

  • 启动线程

多线程的实现方案有两种

  • 继承Thread类

  • 实现Runnable接口

相比继承Thread类,实现Runnable接口的好处

  • 避免了Java单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
public class MyRunnableDemo {
    public static void main(String[] args) {
//        创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

//        创建Thread类的对象,把MyRunnable对象作为构造方法的参数
/*//        Thread ( Runnable target)
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);*/
//        Thread ( Runnable target,String name)
        Thread t1 = new Thread(my,"高铁");
        Thread t2 = new Thread(my,"飞机");

        //启动 线程
        t1.start();
        t2.start();
    }
}
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ", " + i);

        }
    }
}

5.2.线程同步

2.1.案例:卖票

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

思路:
①定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;

②在SellTicket类中重写run0方法实现卖票,代码步骤如下
A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:卖了票之后,总票数要减1
C:票没有了,也可能有人来问。所以这里用死循环让卖票的动作一直执行

③定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
A:创建SellTicket类的对象
B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

​ C:启动线程

测试类

public class SellTicketDemo {
    public static void main(String[] args) {
//        A:创建SellTicket类的对象
        SellTicket st = new SellTicket();

//        B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

//        C:启动线程
        t1.start();
        t2.start();
        t3.start();

    }
}

SellTicket类实现Runnable接口

//定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
public class SellTicket implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
       /* A:判断票数大于0,就卖票,并告知是哪个窗口卖的
          B:卖了票之后,总票数要减1
          C:票没有了,也可能有人来问。所以这里用死循环让卖票的动作一直执行*/
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
}
2.1.1.卖票案例的思考

刚才讲解了电影院卖票程序,好像没有什么问题。但是在实际生活中,售票时出票也是需要时间的,所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:每次出票时间100毫秒,用sleep()方法实现

卖票出现了问题

  • 相同的票出现了多次出现了
  • 负数的票

问题原因:

  • 线程执行的随机性导致的
2.1.2.卖票案例数据安全问题的解决

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准

是否是多线程环境

是否有共享数据

是否有多条语句操作共享数据

如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境

怎么实现呢?

  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

  • Java提供了同步代码块的方式来解决

2.1.3.同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

  • 格式:
    synchronized(任意对象){
    多条语句操作共享数据的代码
    }
  • synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端

好处:解决了多线程的数据安全问题

弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

//定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        //相同票出现很多次有负数的
        while (true) {
//            tickets = 100;
//             t1,t2,t3
            //假设t1抢到了cpu的执行权
            //假设t2抢到了cpu的执行权
            synchronized (obj) {
//             t1进来后,就会吧这段代码给锁起来
                if (tickets > 0) {
                    //通过sleep这个方法来模拟出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
//        A:创建SellTicket类的对象
        SellTicket st = new SellTicket();

//        B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

//        C:启动线程
        t1.start();
        t2.start();
        t3.start();

    }
}

2.2.同步方法

同步方法:就是把synchronized关键字加到方法上

  • 格式:

    修饰符synchronized返回值类型方法名(方法参数){ }

同步方法的锁对象是什么呢?

  • this

同步静态方法: 就是把synchronized关键字加到静态方法上

  • 格式:

修饰符static synchronized返回值类型方法名(方法参数){ }

同步静态方法的锁对象是什么呢?

  • 类名.class
public class SellTicket implements Runnable {
//    private int tickets = 100;
    private static int tickets = 100;
    private Object obj = new Object();
    private int x = 0;

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                synchronized (SellTicket.class) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                }
            } else {
                sellTicket();
            }
            x++;
        }
    }

    private static synchronized void sellTicket() {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

2.3.线程安全的类

StringBuffer

  • 线程安全,可变的字符序列

  • 从版本JDK 5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

  • 从Java 2平台v1.2开始,该类改进了List接口,使其成为JavaCollections Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable

  • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值

  • 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable

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

public class ThreadDemo {
    public static void main(String[] args) {
        //static 《T> List<T> synchronizedList (List<T> list)返回由指定列表支持的同步(线程安全)列表
        List<String> list = Collections.synchronizedList(new ArrayList<String>());
        
    }
}

2.4.Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法

  • void lock():获得锁

  • void unlock(:释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法

  • ReentrantLock(): 创建一个ReentrantLock的实例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }finally {
                lock.unlock();
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

5.3.生产消费者

3.1.生产消费者模式概述

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻所谓生产者消费者问题,实际上主要是包含了两类线程:

  • 一类是生产者线程用于生产数据

  • 一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

  • 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

  • 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

在这里插入图片描述

为了体现生产和消费过 程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中Object类的等待和唤醒方法:

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的notify())方法或notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

3.2.生产者消费者案例

生产者消费者案例中包含的类:

  • 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作

  • 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作

  • 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的

  • 操作测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下

    ​ ①创建奶箱对象,这是共亨数据区域

    ​ ②创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作

    ​ ③创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作

    ​ ④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递

    ​ ⑤启动线程

奶箱类(Box)

public class Box {
//    定义一个成员变量,表示第x瓶奶
    private int milk;
    //定义一个成员变量表示奶箱的状态
    private boolean state = false;
//    提供存储牛奶和获取牛奶的操作
    public synchronized void put(int milk){
        //如果有牛奶,等待消费
        if (state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有牛奶,就生产牛奶
        this.milk = milk;
        System.out.println("送奶工将第"+this.milk+"瓶奶发放入奶箱");

        //生产完毕后,修改奶箱状态
        state = true;

        //唤醒其他等待的线程
        notifyAll();
    }
    public synchronized void get(){
        //如果没有牛奶,等待生产
        if (!state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果有牛奶,就消费牛奶
        System.out.println("用户拿到第"+this.milk+"瓶奶");
        //消费完毕之后, 修改奶箱状态
        state = false;
        //唤醒其他等待的线程
        notifyAll();
    }
}

生产者类(Producer)

public class Producer implements Runnable {
    private Box b;

    public Producer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 30; i++){
          b.put(i);
        }
    }
}

消费者类(Customer)

public class Customer implements Runnable {
    private Box b;

    public Customer(Box b) {
        this.b =b;
    }

    @Override
    public void run() {
        while (true) {
            b.get();
        }
    }
}

测试类

public class BoxDemo {
    public static void main(String[] args) {
        //创建奶箱对象,这是共亨数据区域
        Box b = new Box();
//        创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
        Producer p =new Producer(b);
//          ③创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
        Customer c =new Customer(b);
//          ④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        //启动线程
        t1.start();
        t2.start();
    }
}
/*
送奶工将第1瓶奶发放入奶箱
用户拿到第1瓶奶
送奶工将第2瓶奶发放入奶箱
用户拿到第2瓶奶
送奶工将第3瓶奶发放入奶箱
用户拿到第3瓶奶
送奶工将第4瓶奶发放入奶箱
用户拿到第4瓶奶
送奶工将第5瓶奶发放入奶箱
用户拿到第5瓶奶
...
*/

二十六.网络编程

6.1.网络编程入门

1.1.网络编程概述

计算机网络

  • 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

在这里插入图片描述

网络编程

  • 在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换

1.2.网络编程的三要素

IP地址

  • 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而lP地址就是这个标识号。也就是设备的标识

端口

  • 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识

协议

  • 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

1.3.IP地址

IP地址:是网络中设备的唯一标识

IP地址分为两大类

  • IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长
    32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“1100000010101000 0o000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号".”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多

  • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题

常用命令:

  • ipconfig:查看本机IP地址

  • ping IP地址:检查网络是否连通

特殊IP地址:

  • 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用

1.4.InetAddress 的使用

为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用

lnetAddress:此类表示Internet协议(IP)地址

方法名说明
static InetAddress getByName(String host确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
String getHostName()获取此lP地址的主机名
String getHostAddress()返回文本显示中的IP地址字符串
import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
//      public static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
        InetAddress address = InetAddress.getByName("CY-20180101IHZY");//也可以是ip地址
        // InetAddress address = InetAddress.getByName("192.7.1.173");
//
//      public String getHostName()获取此lP地址的主机名
        String name = address.getHostName();
//
//      String getHostAddress()返回文本显示中的IP地址字符串
        String ip = address.getHostAddress();

        System.out.println("主机名"+name);
        System.out.println("ip地址"+ip);
    }
}
/*
主机名CY-20180101IHZY
ip地址192.7.1.173
*/

1.5.端口

端口:设备上应用程序的唯一标识

端口号:用两个字节表示的整数,它的取值范围是O65535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败.

1.6.协议

协议:计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议

  • 用户数据报协议(User Datagram Protocol)

  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输

  • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

TCP协议

  • 传输控制协议(Transmission Control Protocol)
  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”

三次握手:

TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

​ 第一次握手,客户端向服务器端发出连接请求,等待服务器确认

​ 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求

​ 第三次握手,客户端再次向服务器端发送确认信息,确认连接

  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等

在这里插入图片描述

6.2.UDP通讯程序

2.1.UDP通信原理

UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念

Java提供了DatagramSocket类作为基于UDP协议的Socket

2.2.UDP发送数据

发送数据的步骤 :

①创建发送端的Socket对象(DatagramSocket)

​ DatagramSocket ()

②创建数据,并把数据打包

​ DatagramPacket (byte[] buf, int Length,InetAddress address, int port)

③调用DatagramSocket对象的方法发送数据关闭发送端

​ void send (DatagramPacket p)

④关闭发送端

​ void close()

import java.io.IOException;
import java.net.*;

public class SendDemo {
    public static void main(String[] args) throws IOException {
//      ①创建发送端的Socket对象(DatagramSocket)
//      DatagramSocket ()构造数据报套接字并将其绑定到本地主机上的任何可用端口
        DatagramSocket ds = new DatagramSocket();
        //创建数据,并把数据打包
        byte[] bys = "hello,UDP,我来了".getBytes();
        /*int length = bys.length;
        InetAddress address = InetAddress.getByName("192.7.1.173");
        int port = 10086;
        DatagramPacket dp = new DatagramPacket(bys,length,address,port);
        */
        // 构造一个数据包,发送长度为Length的数据包到指定主机上的指定端口号
        //DatagramPacket (byte[] buf, int Length,InetAddress address, int port)
        DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("192.7.1.173"),10086);

        //调用DatagramSocket对象的方法发送数据
//      void send (DatagramPacket p)从此套接字发送数据报包
        ds.send(dp);
        //关闭发送端
        ds.close();

    }
}

2.3.UDp接受数据

接收数据的步骤

①创建接收端的Socket对象(DatagramSocket)

​ DatagramSocket (int port)

②创建一个数据包,用于接收数据

​ DatagramPacket (byte[ ] buf,int length)

③调用DatagramSocket对象的方法接收数据

​ void receive(DatagramPacket p)

④解析数据包,并把数据在控制台显示

​ byte[] getData()

​ int getLength()

⑤关闭接收端

​ void close()

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket对象(DatagramSocket)
       //DatagramSocket (int port)构造数据报套接字并将其绑定到本地主机上的指定端口
        DatagramSocket ds = new DatagramSocket(10086);
        //创建一个数据包,用于接收数据
        //DatagramPacket (byte[ ] buf,int length)构造一个DatagramPacket用于接收长度为Length数据包
        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys,bys.length);

        //调用DatagramSocket对象的方法接收数据
        ds.receive(dp);

/*        //④解析数据包,并把数据在控制台显示
        //byte[] getData()返回数据缓冲区
        byte[] datas = dp.getData();//拿到的是数据缓冲区
        ///int getLength ()返回要发送的数据的长度或接收到的数据的长度
        int len = dp.getLength();//实际发送多少数据*/
        String dataString =new String(dp.getData(),0,dp.getLength());
        System.out.println("数据是:"+dataString);

        ds.close();
    }
}

2.4.UDP通信程序练习

按照下面的要求实现程序

  • UDP发送数据: 数据来自于键盘录入,直到输入的数据是886,发送数据结束
  • UDP接收数据: 因为接收端不知道发送端什么时候停止发送,故采用死循环接收

发送

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/* UDP发送数据:
     数据来自于键盘录入,直到输入的数据是886,发送数据结束*/
public class SendDemo01 {
    public static void main(String[] args) throws IOException {
        //创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();

        //自己封装键盘录入数据
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while ((line=br.readLine())!=null){
            if ("886".equals(line)){
                break;
            }
            //创建数据,并把数据打包
            byte[] bys = line.getBytes();
            DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("192.7.1.173"),12345);
            //调用DatagramSocket对象的方法发送数据
            ds.send(dp);
        }

        //关闭发送端
        ds.close();
    }
}

接受

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/*
    UDP接收数据:
        因为接收端不知道发送端什么时候停止发送,故采用死循环接收*/
public class ReceiveDemo01 {
    public static void main(String[] args) throws IOException {
        //创建接收端的socket对象(DatagramSocket)
        DatagramSocket ds =new DatagramSocket(12345);
        while (true) {
            //创建一个数据包,用于接收数据
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);
            //调用DatagramSocket对象的方法接收数据
            ds.receive(dp);
            //解析数据包,并把数据在控制台显示
            System.out.println("数据是: " + new String(dp.getData(), 0, dp.getLength()));
        }
/*        //关闭接收端
        ds.close();*/
    }
}

6.3.TCP通讯程序

3.1.TCP通信原理

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信

Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信

Java为客户端提供了Socket类,为服务器端提供了serverSocket类

在这里插入图片描述

3.2.TPC发送数据

发送数据的步骤

①创建客户端的Socket对象(Socket)

​ Socket(String host, int port)

②获取输出流,写数据

​ OutputStream getOutputStream()

③释放资源

​ void close()

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建客户端的socket对象(Socket)
        // Socket (InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号
//        Socket s = new Socket(InetAddress.getByName("192.7.1.173"), 10000);
        //Socket (String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
        Socket s = new Socket("192.7.1.173", 10000);

        //②获取输出流,写数据
       // OutputStream getOutputStream ()返回此套接字的输出流
        OutputStream os = s.getOutputStream();
        os.write("hello,TCP,俺来了".getBytes());

        //释放资源
        s.close();
    }
}

3.3.TPC接收数据

接收数据的步骤

①创建服务器端的Socket对象(ServerSocket)

​ ServerSocket(int port)

②监听客户端连接,返回一个Socket对象

​ Socket accept()

③获取输入流,读数据,并把数据显示在控制台

​ lnputStream getlnputStream()

④释放资源

​ void close()

客户端

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建客户端的socket对象(Socket)
        // Socket (InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号
//        Socket s = new Socket(InetAddress.getByName("192.7.1.173"), 10000);
        //Socket (String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
        Socket s = new Socket("192.7.1.173", 10000);

        //②获取输出流,写数据
       // OutputStream getOutputStream ()返回此套接字的输出流
        OutputStream os = s.getOutputStream();
        os.write("hello,TCP,俺来了".getBytes());

        //释放资源
        s.close();
    }
}

服务端

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
//        ①创建服务器端的Socket对象(ServerSocket)
//        ServerSocket (int port)创建绑定到指定端口的服务器套接字
        ServerSocket ss = new ServerSocket(10000);

//        Socket accept() 侦听要连接到此套接字并接受它
        Socket s = ss.accept();
//        ②获取输入流,读数据,并把数据显示在控制台
        InputStream is =s.getInputStream();
        byte[] bys = new byte[1024];
        int len =is.read(bys);
        String data = new String(bys,0,len);
        System.out.println("数据是: "+data);
//        ③释放资源
        s.close();
        ss.close();
    }
}
/*
数据是: hello,TCP,俺来了                   
*/

3.4.TPC通信程序练习

练习1
  • 客户端:发送数据,接收服务器反馈

  • 服务器:接收数据,给出反馈

客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo02 {
    public static void main(String[] args) throws IOException {
        //创建客户端的socket对象(Socket)
        Socket s = new Socket("192.7.1.173", 10000);
        //获取输出流,写数据
        OutputStream os = s.getOutputStream();
        os.write("hello,TCP,俺来啦!".getBytes());

        //接收服务器反馈
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys,0,len);
        System.out.println("客户端: "+data);

        //释放资源
    /*    is.close();
        os.close();*/
        s.close();
    }
}
/*
客户端: 数据已经收到
*/

服务端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo02 {
    public static void main(String[] args) throws IOException {
        //创建服务器端的socket对象(ServerSocket)
        ServerSocket ss = new ServerSocket(10000);
        //监听客户端连接,返回一个Socket对象
        Socket s = ss.accept();
        //获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys,0,len);
        System.out.println("服务器"+data);

        //给出反馈
        OutputStream os = s.getOutputStream();
        os.write("数据已经收到".getBytes());
        //释放资源
        ss.close();
    }
}
/*
服务器hello,TCP,俺来啦!
*/
练习2
  • 客户端: 数据来自于键盘录入,直到输入的数据是886,发送数据结束
  • 服务器: 接收到的数据在控制台输出
import java.io.*;
import java.net.Socket;

public class ClientDemo03 {
    public static void main(String[] args) throws IOException {
        //创建客户端socket对象
        Socket s = new Socket("192.7.1.173",10000);

        //数据来自于键盘录入,直到输入的数据是886,发送数据结束
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //封装输出流对象
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line=br.readLine())!=null){
            if ("886".equals(line)){
                break;
            }
   /*         //获取输出流对象
            OutputStream os = s.getOutputStream();
            os.write(line.getBytes());*/
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        //释放资源
         s.close();
    }
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo03 {
    public static void main(String[] args) throws IOException {
        //创建服务器socket对象
        ServerSocket ss = new ServerSocket(10000);

        //监听客户的连接,返回一个对应的socket对象
        Socket s = ss.accept();

        //获取输入流
        /*InputStream is = s.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader  br = new BufferedReader(isr);*/
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
        ss.close();
    }
}
练习3
  • 客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
  • 服务器:接收到的数据写入文本文件

客户端

import java.io.*;
import java.net.Socket;

public class ClientDemo03 {
    public static void main(String[] args) throws IOException {
        //创建客户端socket对象
        Socket s = new Socket("192.7.1.173",10000);

        //数据来自于键盘录入,直到输入的数据是886,发送数据结束
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //封装输出流对象
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line=br.readLine())!=null){
            if ("886".equals(line)){
                break;
            }
   /*         //获取输出流对象
            OutputStream os = s.getOutputStream();
            os.write(line.getBytes());*/
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        //释放资源
         s.close();
    }
}

服务器

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo04 {
    public static void main(String[] args) throws IOException {
        //创建服务器Socket对象
        ServerSocket ss = new ServerSocket(10000);

        //监听客户端连接,返回一个对应的socket对象
        Socket s = ss.accept();

        //接受数据
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        //把数据写入文本文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("s.txt"));
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        bw.close();
        ss.close();
    }
}
练习4
  • 客户端:数据来自于文本文件
  • 服务器:接收到的数据写入文本文件

服务端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo04 {
    public static void main(String[] args) throws IOException {
        //创建服务器Socket对象
        ServerSocket ss = new ServerSocket(10000);

        //监听客户端连接,返回一个对应的socket对象
        Socket s = ss.accept();

        //接受数据
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        //把数据写入文本文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        bw.close();
        ss.close();
    }
}

客户端

import java.io.*;
import java.net.Socket;

public class ClientDemo04 {
    public static void main(String[] args) throws IOException {
        //创建客户端Socket对象
        Socket s = new Socket("192.7.1.173", 10000);

        //封装文本文件的数据
        BufferedReader br = new BufferedReader(new FileReader("s.txt"));
        //封装输出流写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        br.close();
        s.close();
    }
}
练习5
  • 客户端: 数据来自于文本文件,接收服务器反馈

  • 服务器: 接收到的数据写入文本文件,给出反馈

  • 出现问题:程序一直等待

  • 原因:读数据的方法是阻塞式的

  • 解决办法:自定义结束标记;使用shutdownOutput(方法(推荐)

服务端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo04 {
    public static void main(String[] args) throws IOException {
        //创建服务器Socket对象
        ServerSocket ss = new ServerSocket(3333);

        //监听客户端连接,返回一个对应的socket对象
        Socket s = ss.accept();

        //接受数据
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        //把数据写入文本文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.txt"));
        String line;
        while ((line = br.readLine()) != null) {
        /*    if ("886".equals(line)) {
                break;
            }*/
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        //给出反馈
        BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        bwServer.write("文件上传成功");
        bwServer.newLine();
        bwServer.flush();

        //释放资源
//        bwServer.close();
        ss.close();
        bw.close();
        br.close();

    }
}

客户端

import java.io.*;
import java.net.Socket;

public class ClientDemo04 {
    public static void main(String[] args) throws IOException {
        //创建客户端Socket对象
        Socket s = new Socket("192.7.1.173", 3333);

        //封装文本文件的数据
        BufferedReader br = new BufferedReader(new FileReader("s.txt"));
        //封装输出流写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

 /*       //自定义结束标记
        bw.write("886");
        bw.newLine();
        bw.flush();*/

//       public void shutdownOutput ()结束标记
        s.shutdownOutput();

        //接受反馈
        BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String data = brClient.readLine();
        System.out.println("服务器的反馈: " + data);

        //释放资源
//        brClient.close();
        s.close();
        bw.close();
        br.close();
    }
}
练习6
  • 客户端:数据来自于文本文件,接收服务器反馈

  • 服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程

ServerThread类

public class ServerThread implements Runnable {
    private Socket s;

    public ServerThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        //接收数据写到文本文件
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//            BufferedWriter bw = new BufferedWriter(new FileWriter("g.txt"));
            //解决名称冲突问题
            int count = 1;
            File file = new File("g." + count + ".java");
            while (file.exists()){
                count++;
                 file = new File("g." + count + ".java");

            }
            BufferedWriter bw = new BufferedWriter(new FileWriter(file));
            String line;
            while ((line=br.readLine())!=null){
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
            //给出反馈
            BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            bwServer.write("文件上传成功");
            bwServer.newLine();
            bwServer.flush();

            //释放资源
            s.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

服务端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo06 {
    public static void main(String[] args) throws IOException {
        //创建服务器Socket对象
        ServerSocket ss = new ServerSocket(33333);
        while (true) {
            //监听客户端连接,返回一个对应的Socket对象
            Socket s = ss.accept();
            //为每一个客户端开启一个线程
            new Thread(new ServerThread(s)).start();
        }

    }
}

客户端

import java.io.*;
import java.net.Socket;

public class ClientDemo04 {
    public static void main(String[] args) throws IOException {
        //创建客户端Socket对象
        Socket s = new Socket("192.7.1.173", 33333);

        //封装文本文件的数据
        BufferedReader br = new BufferedReader(new FileReader("game.txt"));
        //封装输出流写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

 /*       //自定义结束标记
        bw.write("886");
        bw.newLine();
        bw.flush();*/

//       public void shutdownOutput ()结束标记
        s.shutdownOutput();

        //接受反馈
        BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String data = brClient.readLine();
        System.out.println("服务器的反馈: " + data);

        //释放资源
//        brClient.close();
        s.close();
        bw.close();
        br.close();
    }
}

二十七.Lambda表达式

7.1.函数式编程思想概述

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”面向对象思想强调“必须通过对象的形式来做事情”

函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做而我们要学习的Lambda表达式就是函数式思想的体现

7.2.体验Lambda表达式

需求:启动一个线程,在控制台输出一句话:多线程程序启动了

方式1:

  • 定义一个类MyRunnable实现Runnable接口,重写run)方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable的对象作为构造参数传递

  • 启动线程

方式2:

  • 匿名内部类的方式改进

方式3:

  • Lambda表达式的方式改进

7.3.Lambda表达式的标准格式

匿名内部类中重写run(方法的代码分析

  • 方法形式参数为空,说明调用方法时不需要传递参数
  • 方法返回值类型为void,说明
  • 方法执行没有结果返回方法体中的内容,是我们具体要做的事情

在这里插入图片描述

Lambda表达式代码分析

  • ()∶里面没有内容,可以看成是方法形式参数为空

  • ->∶用箭头指向后面要做的事情

  • {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

在这里插入图片描述

组成Lambda表达式的三要素: 形式参数,箭头,代码块

Lambda表达式的格式

  • 格式:(形式参数)->{代码块}

  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可

  • ->:由英文中画线和大于符号组成,固定写法。代表指向动作

  • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

7.4.Lambda表达式的练习

练习1

  • 定义一个接口(Eatable),里面定义一个抽象方法: void eat();

  • 定义一个测试类(EatableDemo),在测试类中提供两个方法

    • 一个方法是: useEatable(Eatable e)

    • 一个方法是主方法,在主方法中调用useEatable方法

测试类

public class EatableDemo {
    public static void main(String[] args) {
        //在主方法中调用useEatable方法
        Eatable e =new EatableImpl();
        useEatable(e);

        //匿名内部类
        useEatable(new Eatable() {
            @Override
            public void eat() {
                System.out.println("天天向上,好好学习");
            }
        });
        //Lambda表示式
        useEatable( () ->{
            System.out.println("天天向上,好好学习");
        });
        }
    private static void useEatable(Eatable e){
        e.eat();
    }
}

接口

public interface Eatable {
    void eat();
}

public class EatableImpl implements Eatable{
    @Override
    public void eat() {
        System.out.println("天天向上,好好学习");
    }
}

练习2

练习2:

  • 定义一个接口(Flyable),里面定义一个抽象方法: void fly(String s);

  • 定义一个测试类(FlyabeDemo),在测试类中提供两个方法

    • 一个方法是: useFlyable(Flyablef)
    • 一个方法是主方法,在主方法中调用useFlyable方法

    测试类

public class FlyableDemo {
    public static void main(String[] args) {
        //在主方法中调用useFlyable方法
        //匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void Fly(String s) {
                System.out.println(s);
                System.out.println("去湖州安吉");
            }
        });
        System.out.println("------");
        //lambda表示
        useFlyable( (String s)->{
            System.out.println(s);
            System.out.println("去湖州安吉");
        });

    }
    private static void useFlyable(Flyable f){
        f.Fly("绿水青山");
    }
}
/*
绿水青山
去湖州安吉
------
绿水青山
去湖州安吉
*/

接口

public interface Flyable {
    void Fly(String s);
}

练习3

  • 定义一个接口(Addable),里面定义一个抽象方法: intadd(intx,int y);
  • 定义一个测试类(AddableDemo),在测试类中提供两个方法
    • 一个方法是: useAddable(Addable a)
    • 一个方法是主方法,在主方法中调用useAddable方法

测试类

public class AddableDemo {
    public static void main(String[] args) {
        useAddable((int x,int y)->{
            return x+y;
        });

    }
    private static void useAddable(Addable a){
        int sum = a.add(10,20);
        System.out.println(sum);
    }
}
//30

接口

public interface Addable {
    int add(int x,int y);
}

7.5.Lambda表达式的省略模式

省略规则:

  • 参数类型可以省略。但是有多个参数的情况下,不能只省略一个

  • 如果参数有且仅有一个,那么小括号可以省略

  • 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return

测试类

public class LambdaDemo02 {
    public static void main(String[] args) {
   /*     uesAddable((int x,int y)->{
            return x+y;
        });*/
        //参数的类型可以省略,但是有多个参数的情况下,不能只省略一个
        uesAddable((x, Y)->{
            return x+Y;
        });
        //如果参数有且仅有一个,那么小括号可以省略
        useFlyable(s->{
            System.out.println(s);
        });
        //如果代码块的语句只有一条,可以省略大括号和分号
        useFlyable(s-> System.out.println(s));

        //如果代码块的语句只有一条,且有return,那么return也省略
        uesAddable((x,y)-> x+y);

    }
    private  static void useFlyable(Flyable f){
        f.Fly("风和日丽,晴空万里");
    }
    private static void uesAddable(Addable a){
        int sum = a.add(10,20);
        System.out.println(sum);
    }
}

接口

public interface Addable {
    int add(int x,int y);
}
public interface Flyable {
    void Fly(String s);
}

7.6. Lambda表达式的注意事项

注意事项:

  • 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法

  • 必须有上下文环境,才能推导出Lambda对应的接口

    • 根据局部变量的赋值得知Lambda对应的接口:

      Runnable r = () -> System.out.println("lambda表达式");
      new Thread(r).start();
      
    • 根据调用方法的参数得知Lambda对应的接口:

      new Thread(() -> System.out.println("lambda表达式")).start();
      
public class LambdaDemo03 {
    public static void main(String[] args) {
 /*       useInter(()->{
            System.out.println("好好学习,天天向上");
        });*/
        //使用lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
        useInter(() -> System.out.println("好好学习,天天向上"));

        //必须有上下文环境,才能推导出lambda对应的接口
    /*    new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类");
            }
        }).start();*/

/*        Runnable r = () -> System.out.println("lambda表达式");
        new Thread(r).start();*/
        new Thread(() -> System.out.println("lambda表达式")).start();
    }

    private static void useInter(Inter i) {
        i.show();
    }
}
/*
好好学习,天天向上
lambda表达式
*/

7.7.Lambda表达式和匿名内部类的区别

所需类型不同

  • 匿名内部类:可以是接口,也可以是抽象类,还可以是具
  • Lambda表达式:只能是接口

使用限制不同

  • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
  • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式

实现原理不同

  • 匿名内部类:编译之后,产生一个单独的.class字节码文件

  • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

测试类

public class LambdaDemo04 {
    public static void main(String[] args) {
/*        //匿名内部类
        useInter(new Inter() {
            @Override
            public void show() {
                System.out.println("接口");
            }
        });
        useAnimal(new Animal() {
            @Override
            public void method() {
                System.out.println("抽象类");
            }
        });
        useStudent(new Student(){
            @Override
            public void study() {
                System.out.println("具体类");
            }
        });*/
        
        //Lambda 只能接口
        useInter(()-> System.out.println("接口"));
        
//        useAnimal(()->{
//            System.out.println("抽象类");
//        });
        
//        useStudent(()-> System.out.println("具体类"));



    }
    private static void useStudent(Student s){
        s.study();
    }
    private static void useAnimal(Animal a){
        a.method();
    }
    private static void useInter(Inter i){
        i.show();
    }
}

接口

public interface Inter {
    void show();
}

抽象类

public abstract class Animal {
   public abstract void method();

}

具体类

public class Student {
    public void study(){
        System.out.println("好好学习,快乐学习");
    }
}

二十八.接口组成更新

8.1.接口的组成

  • 常量

    ​ public static final

  • 抽象方法

    ​ public abstract

  • 默认方法(Java 8)

  • 静态方法(Java 8)

  • 私有方法(Java 9)

8.2.接口中默认的方法

在 java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。

接口中默认方法的定义格式:

  • 格式: public default 返回值类型 方法名(参数列表){ }

  • 范例:

     public default void show3() { }
    

接口中默认方法的注意事项:

  • 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
  • public可以省略,default不能省略

8.3.接口中的静态方法

接口中静态方法的定义格式:

  • 格式: public static 返回值类型 方法名(参数列表){}

  • 范例: public static void show(){ }

接口中静态方法的注意事项:

  • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
  • public可以省略,static不能省略
/*需求;
        1:定义一个接口Inter,里面有三个方法:一个是抽象方法,一个是默认方法,一个是静态方法
            void show( );
            default void method()i }
            public static_ void test(){}
        2:定义接口的一个实现类,
            interImpl
        3:定义测试类;
            interDemo
            在主方法中,按照多态的方式创建对象并使用*/
public class InterDemo {
    public static void main(String[] args) {
//        在主方法中,按照多态的方式创建对象并使用*/
        Inters i = new InterImpl();
        i.shows();
        i.methods();

//        i.test();
//        InterImpl.test();
        Inters.test();
        Flyable.test();//static一定要用接口/类名引用才可
    }
}
/*
show 方法执行了
Inter 中的默认方法执行了
Inter 中的静态方法执行了
Flyable中的静态方法执行了
*/

Flyable接口

public interface Flyable {
    static void test() {
        System.out.println("Flyable中的静态方法执行了");
    }
}

inters接口

public interface Inters {
    void shows();

    default void methods() {
        System.out.println("Inter 中的默认方法执行了");
    }

    static void test() {
        System.out.println("Inter 中的静态方法执行了");
    }
}

实现类

public class InterImpl implements Inters, Flyable {
    @Override
    public void shows() {
        System.out.println("show 方法执行了");
    }
}

8.4.接口中的私有方法

Java 9中新增了带方法体的私有方法,这其实在Java8中就埋下了伏笔: Java8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性

接口中私有方法的定义格式:

  • 格式1: private返回值类型方法名(参数列表){}

  • 范例1: private void show() {}

  • 格式2: private static 返回值类型 方法名 (参数列表){}

  • 范例2: private static void method(){}

接口中私有方法的注意事项:

  • 默认方法可以调用私有的静态方法和非
  • 静态方法静态方法只能调用私有的静态方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值