JavaSE学习笔记

  • Post author:
  • Post category:java




一、tips:



1.java测算程序运行的时间:

long startTime = System.currentTimeMills();
测算的代码
...
long endTime = System.currentTimeMills();
long time = endTime - startTime;
System.out.println("实际运行的时间为:" + time + "毫秒");



2.将数组转换成字符串:

使用java.util.Arrays类中的静态方法
  Arrays.toString(arr);



一、代码注释



1. 简介:

  • java中有三种注释
  • 单行注释://
  • 多行注释:/* */
  • 文档注释:/** */



2. 特点:

  • 文档注释可以用来生成类的HTML文档,这些注释用来说明类的用途、方法的操作、参数的含义等。
  • 普通注释可以位于源文件的任何地方,但要通过javadoc.exe生成HTML文档的注释还是有特定的规则
  • 文档注释可以放在方法声明的前面,但不能放在方法中



3. 文档注释中的标签:

超链接:<a href="http://www.gduf.edu.cn"></a>
段落:<p></p>
回车:<br/>
加粗:<b></b>



4. 类或接口前的文档注释:

@author:作者
@version:程序版本
@since:从指定JDK版本开始
@see:参见可能会关心的类和接口



5. 方法或构造方法前的文档注释:

@param:对方法中某形式参数的说明信息
@return:方法的返回参数类型说明
@throws:方法可能抛出的异常说明信息
@exception:@throws
@deprecated:指示该方法已经过时,不推荐使用
@see:参见其他方法
@link:指向其他HTML文档的链接



6. 生成类的HTML文档的命令

javadoc 类名.java -encoding utf-8



一、接口

(Interface)



1.概念:

  • 接口就是一种

    公共的规范标准

    ,只要符合规范标准,大家都可以通用
  • Java中的接口更多的体现在

    对行为的抽象



2.接口的特点:

  • 接口用关键字

    Interface

    修饰

    • public `interface` 接口名{}
      
  • 类实现接口用

    implements

    表示

    • public class 类型

      implements

      接口名{}
    • 类不能用

      extends

      继承接口,应该是用

      implements

      实现接口
  • 接口实例化的方式

    • 接口不能直接实例化,即不能直接创建接口的对象。接口的实例化应该参照多态的方式,通过实现类的对象来实例化,这叫接口多态
    • 多态的形式:具体类多态,

      抽象类多态



      接口多态
    • 多态的前提:①、有继承或者实现关系;②、有方法重写;③、有(类/接口)引用指向(子/实现)类对象
  • 接口的实现类:

    • 要么重写接口中的所有抽象方法
    • 要么是抽象类



3.接口的成员特点:

  • 成员变量:

    • 只能是常量
    • 默认修饰符:

      public static final
  • 构造方法:

    • 接口没有构造方法,因为接口主要是对

      行为

      进行抽象,是没有具体存在

    • 一个类如果没有父类,默认继承自Object类

      public class InterImpl implements Inter{

      }

      等价于

      public class InterImpl extends Object Implments Inter{

      }

  • 成员方法:

    • 只能是抽象方法

    • 默认修饰符:

      public abstract

    • 关于接口的方法中,JDK8和JDK9中有一些新的特性



4.接口和类的关系:

  • 类和类的关系

    • 继承关系,只能

      单继承

      ,但是可以

      多层继承

      ,但

      不可以多继承
  • 类和接口的关系

    • 实现关系,可以

      单实现

      ,也可以

      多实现

      ,还可以在

      继承一个类的同时实现多个接口
  • 接口和接口的关系

    • 继承关系,可以

      单继承

      ,也可以

      多继承

一个类同时实现多个接口

public class InterImpl

implments

Inter1, Inter2,Inter3{

}

一个类在继承类的同时实现接口

public class InterImpl

extends

Object

implements

Inter1{

}

一个接口可以继承多个接口

public interface Inter3 extends Inter1, Inter2{

}



5.抽象类与接口的关系:

  • 成员区别

    • 抽象类 变量,常量;有构造方法;有抽象方法,也有非抽象方法
    • 接口 常量,抽象方法
  • 关系区别

    • 类与类 继承,单继承
    • 类与接口 实现,可以单实现,也可以多实现
    • 接口与接口 继承,单继承,多继承
  • 设计理念区别

    • 抽象类 对类抽象,包括

      属性、行为
    • 接口 对行为抽象,主要是

      行为



二、抽象类或接口作为形参和返回值



1.类名作为形参和返回值:

  • 方法的形参是

    类名

    ,其实需要的是

    该类的对象
  • 方法的返回值是

    类名

    ,其实返回的是

    该类的对象



2.抽象类名作为形参和返回值:

  • 方法的形参是

    抽象类名

    ,其实需要的是

    该抽象类的子类对象
  • 方法的返回值是

    抽象类名

    ,其实返回的是

    该抽象类的子类对象



3.接口名作为形参和返回值:

  • 方法的形参是

    接口名

    ,其实需要的是

    该接口的实现类对象
  • 方法的返回值是

    接口名

    ,其实返回的是

    该接口的实现类对象



三、内部类



1. 概念:

  • 内部类就是在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类



2. 内部类的定义格式:

public class 类名{
  修饰符 class 类名{
    
  }
}

public class Outer{//外部类
   public class Inner{//内部类
     
   }
}



3. 内部类的访问特点:

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类要访问内部类的成员,必须创建内部类的对象



4. 成员内部类:

  • 按照内部类在外部类中定义的位置不同,可以分为如下两种形式:

    • 在类的成员位置:成员内部类(在外部类的成员变量位置中创建的类)
    • 在类的局部位置:局部内部类(在外部类中的成员方法中创建的类)



5. 局部内部类:

  • 局部内部类是方法中定义的类,所以外界是无法直接使用,需要在方法内部创建对象并使用
  • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量



6. 匿名对象内部类:

  • 前提:存在一个类或者接口,这里的类可以是具体的类,也可以是抽象的类

  • 格式:

    new 类名或者接口名(){
    		重写方法;
    }
    
    范例:
    new Inter(){
    	public void show(){
    	}
    }
    
  • 本质:是一个

    继承了该类

    或者

    实现了该接口



    子类匿名对象



7. Math类的常用方法:

public static int abs(int a) 								返回参数的绝对值
public static double ceil(double a) 				返回大于或等于参数的最新小double值,等于一个整数
public static double floor(double a) 				返回小于或等于参数的最大double值,等于一个整数
public static int round(float a) 						按照四舍五入返回最接近参数的int
public static int max(int a,int b) 					返回两个int值中的较大值
public static int min(int a,int b) 					返回两个int值中的较小值
public static double pow(double a,double b) 返回a的b次幂的值
public static double random() 							返回值为double的正值,[0.0,1.0)



8. System类的常用方法:

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



9. Object类



①.概述:
  • Object类是类层次结构的跟,每个类都可以将Object作为超类。所有类都直接或者间接继承该类
  • 构造方法:public Object()
public class ObjectDemo {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("林青霞");
        s.setAge(30);
        System.out.println(s); //com.itheima_01.Student@3f3afe78
        System.out.println(s.toString()); //com.itheima_01.Student@3f3afe78

        /*
        public void println(Object x) { //x = s;
            String s = String.valueOf(x);
            synchronized (this) {
                print(s);
                newLine();
            }
        }

        public static String valueOf(Object obj) { //obj = x;
            return (obj == null) ? "null" : obj.toString();
        }

        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
         */
    }
}


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



10. Arrays类

public static String toString(int[] a) 返回指定数组的内容的字符串表示形式
public static void sort(int[] a) 			 按照数字顺序排列指定的数组
  • 工具类的设计思想:

    • 构造方法用

      private

      修饰,防止用类创建对象
    • 成员用

      public static

      ,用类.方法的方式调用工具类的方法



四、基本类型包装类



1. 简介:

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

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

    基本数据类型 包装类
    byte Byte
    short Short
    int Integer
    long Long
    float Float
    double Double
    char Character
    boolean Boolean
  • int类型的最大值:

    Integer.MAX_VALUE

    -2147483648

  • int类型的最小值:

    Integer.MIN_VALUE

    2147483647



2. int与String的相互转换:


  • int转换成String

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

  • String转换成int

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



3. 拆箱与装箱:

  • 装箱:把基本数据类型转换为对应的包装类类型
  • 拆箱:把包装类类型转换为对应的基本数据类型
Integer i = Integer.valueOf(100);//手动装箱
Integer i = 100;//自动装箱
Integer ii = 100;
ii = ii.intValue()+200;//手动拆箱
ii = ii + 200;//自动拆箱,再自动装箱



五、包:

(package)



1.概念:

  • 所谓包就是Java语言提供的一种 区别类名空间的机制,是类的组织方式。每个包对应一个文件夹,包中还可以再有包,成为包等级。



2.特点:

  • 同一包中的类名不能重复,不同包中的类名可以相同
  • 当源程序没有声明类所在的包时,Java将类放在默认包中,这意味着每个类使用的名字必须互不相同,否则会发生名字冲突
  • 包名应该与对应文件夹名的大小写一致
  • 包层次的根文件夹是由环境变量ClassPath来确定的
  • 在Java源文件中若没有使用package语句声明类所在的包时,则Java默认包的路径是当前文件夹,并没有包名,即无名包(

    unnamed package

    ),无名包中不能有子包



3.作用:

  • 包及子包的定义,实际上是为了解决名字空间、名字冲突,它与类的继承没有关系



4.java语言中的常用包



①、概念:

  • 由于Java语言的package是用来存放类与接口的地方,所以也把package译为“

    类库

    ”,即Java类库是以包的形式实现的。Java语言已经把功能相近的类分门别类的存放到不同的类库中(除类之外还包含有接口、异常等)。Java提供的用于程序开发的类库称为应用程序接口(

    Application Programming Interface, 即API

    )。



②、Java语言的常用包有:

  • java.lang:语言包
  • java.io: 输入输出流的文件包
  • java.util: 实用包
  • java.net:网络功能包
  • java.sql: 数据库连接包
  • java.text: 文本包



③、语言包: java.lang



1. 概念:

语言包java.lang包提供了Java语言最基础的类。每个Java程序运行时,系统都自动的引入Java.lang包,所以该包的加载是默认的



2. 主要包含的类:
  •  数据类型包装类(`The Data Type Wrapper`)
    
  •  字符串类(`String`)
    
  •  数学类(`Math`)
    
  •  系统和运行时类(`System、Runtime`)
    
  •  类操作类(`Class`)
    
  •  错误和异常处理类(`Throwable,Exception和Error`)
    
  •  线程类(`Thread`)
    
  •  过程类(`Process`)
    



④、输入输出流的文件包:java.io



1.概念:

输入输出流的文件包java.io是Java语言的标准输入输出类库,包含了Java程序与操作系统、用户界面以及其他Java程序之间的数据交换所使用的类。凡事需要完成与操作系统有关的较底层的输入输出操作的java程序,都需要使用该包



2.主要包含的类:
  • 基本输入输出流类
  • 文件输入输出流类
  • 过滤输入输出流类
  • 管道输入输出流类
  • 随机输入输出流类



⑤、实用包: java.util



1.概念:

​ 实用包java.util提供了实现各种不同实用功能的类,包括

日期类、集合类



2.主要包含的类:
  • 数据输入类(

    Scanner

    )
  • 日期类(

    Date、Calendar等

    )
  • 链表类(

    LinkedList

    )
  • 向量类(

    Vector

    )
  • 哈希表类(

    Hashtable

    )
  • 栈类(

    Stack

    )
  • 树类(

    TreeSet

    )



⑥、网络功能包:java.net



1.概念:

网络功能包java.net是Java用来实现网络功能的类库。目前已经实现的网络功能主要有底层的网络通信、访问Internet资源。开发者可以利用java.net包中的类,开发出具有网络功能的程序



2.主要包含的类:
  • 访问网络资源类(

    URL

    )
  • 套接子类(

    Socket

    )
  • 服务器端套接字类(

    ServerSocket

    )
  • 数据报打包类(

    DatagramPacket

    )
  • 数据报通信类(

    DatagramSocket

    )



⑦、数据库链接包:java.sql



1.概念:

​ 数据库连接包java.sql是实现JDBC(Java DataBase Connection, java数据库连接)的类库。利用该包可以使java程序具有访问不同种类数据库的功能,如MySQL,SQLServer,Oracle等。只要安装了合适的驱动程序,同一个Java程序不需要修改就可以访问这些不同数据库的数据。



⑧、文本包: java.text



1.概念:

Java文本包java.text 中的Format、DataFormat、SimpleDataFormat等类提供各种文本或日期格式



六、日期和时间



1. 日期类

  • 概念:
  • Date代表了一个特定的时间,精确到毫秒
方法名 说明
public Date() 分配一个Date对象,并初始化,以便代表它被分配的时间,精确到毫秒
public Date(long date) 分配一个Date对象,并将其初始化表示从标准基准时间起指定的毫秒数
打印Date对象:Sat Oct 23 16:27:30 CST 2021
  • 常用方法:
方法名 说明
public long getTime() 获取的是日期对象从1970年1月1日00:00:00到现在的毫秒值
public void setTime(long time) 设置时间,给的是毫秒值



2. SimpleDateFormat类

  • 概念:格式化和解析日期
  • 创建对象的构造方法
public SimleDateFormat(String pattern):构造一个SimpleDateFormat使用给定的模式和默认的日期格式
  • 常用方法
1.格式化(DateString)
public final String format(Date date):将日期格式化成日期/时间字符串
2.解析(StringDate)
public Date parse(String source):从给定字符串的开始解析文本以生成日期
  • 常用的模式字母对应关系:
字母 含义
y
M
d
H
m
s



3.示例代码:

package test2;

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

public class test2 {
    public static void main(String[] args) throws ParseException {
        Date date = new Date();
//        格式化:从Date到String
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String str = sdf.format(date);//按照给定模式格式化日期,返回字符串
        System.out.println(str);

//        解析:从String到Date
        String dateStr = "2021-09-10 11:11:11";//给定String类型的日期,具备一定的格式
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date2 = sdf2.parse(dateStr);//解析给定的日期字符串,返回Date日期对象
        System.out.println(date2);
    }
}


输出:
"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=9289:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test2.test2
2021102317:15:37
Fri Sep 10 11:11:11 CST 2021

Process finished with exit code 0



七、异常处理



八、容器类



1.

Collection

容器接口



①、概述:

  • 容器类是Java以类库的形式供用户开发程序时可直接使用的各种数据结构。这些数据结构通常称为容器类或称集合类
  • 构造方法中的上座率也称为

    填装因子

    ,上座率的值为0.0~1.0表示集合的饱和度。当集合中的元素个数超过了容量与上座率的乘积,容量就会自动翻倍。



②、框架图:

容器框架中的接口和实现接口的类的继承关系



③、Collection集合常用方法:

方法名 说明
boolean add(E e) 添加元素
boolean remove(Object obj) 从集合中移除指定的元素
void clear() 清空集合中的元素
boolean contains(Object o) 判断集合中是否存在指定元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,即结合中元素的个数
Iterator iterator() 返回容器的迭代器



④、Collection集合的遍历:

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


    • Iterator<E> iterator()

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


    • E next()

      : 返回迭代中的下一个元素

    • boolean hasNext()

      : 如果迭代具有更多元素,则返回true
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class test4 {
    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()){//如果还有下一个元素
            String s = it.next();//获取下一个元素
            System.out.println(s);
        }
    }
}


"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=11882:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.test4
hello
world
java

Process finished with exit code 0



2.

List

列表接口



①、概述:

  • 有序集合(也称为序列),用户可以精确控制列表中,每个元素插入的位置。用户可以通过整数索引访问元素,并搜索列表中的元素
  • 与Set集合不同,列表通常允许重复的元素
  • 实现List接口的类主要有两个:链表类LinkedList和数组列表类ArrayList,它们都是线性表



②、特点:

  • 有序:存储和取出的元素顺序一致
  • 可重复:存储的元素可以重复,也可以是空值null



③、并发修改异常:


  • ConcurrentModificationException

    :迭代器访问过程中添加了元素
  • 产生原因:

    • 迭代器遍历访问元素的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器next()方法中获取元素判断预期修改值和实际修改值不一致
  • 解决方案:

    • 用for循环遍历,然后用集合对象做对应操作即可



④、选用两种线性表的原则:

  • 如果要通过下标随机访问元素,但除了在末尾出之外,不在其他位置插入或删除元素,则应该选择ArrayList类
  • 如果需要在线性表的任意位置上进行插入或删除操作,则应选择LinkedList类



3.

ListIterator

列表迭代器



①、概述:

  • 通过List集合的

    listIterator()

    方法获取,所以说它是List集合特有的迭代器,继承自Iterator接口
  • 用于允许程序员沿任一方向遍历列表迭代器,在迭代期间修改列表,并获得列表中迭代器的当前位置
  • 不会出现并发修改异常问题



②、常用方法:

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



4.三种遍历方式:

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

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

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

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

    }

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

public class test4 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");

//      迭代器遍历
        Iterator<String> it = list.iterator();//创建迭代器对象
        while (it.hasNext()) {//如果还有下一个元素
            String s = it.next();//获取下一个元素
            System.out.println(s);
        }
        System.out.println("--------");

//        普通for循环遍历
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        System.out.println("--------");

//        增强for循环
        for (String s : list) {
            System.out.println(s);
        }
    }
}



5.

ArrayList<E>

(List的实现类)



①.概述:

  • 集合类的特点:

    ​ 提供一种存储空间可变的存储模型,存储的数据容量可发生改变

  • ArrayList:

    • 可调整大小的数组实现
    • :是一种特殊的数据类型,

      泛型
    • 扩充因子为0.5,当数组数据量达到容量的50%时自动扩容
    • 使用一维数组实现List,该类实现的是可变数组,允许所有元素,包括null
  • 例子:

    • ArrayList
    • ArrayList
    • ArrayList arr = new ArratList<>();//JDK7以后的新特性,可以根据前面声明语句<>中的类型推断出来
    • 上式等价于ArrayList arr = new ArrayList();



②.构造方法:

构造方法名 描述

ArrayList()
构造一个初始容量为十的空列表。

ArrayList(int initialCapacity)
构造具有指定初始容量的空列表。

ArrayList(Collection<? extends E> c)
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。



③.常用方法:

返回值类型 方法名 描述

void

add(int index, E element)
在此列表中的指定位置插入指定的元素。

boolean

add(E e)
将指定的元素追加到此列表的末尾。

boolean

remove(Object o)
删除指定的元素,返回删除元素是否成功

E
remove(int index) 删除指定索引处的元素,返回被删除的元素

E

set(int index, E element)
修改指定索引处的元素,返回被修改的元素

E

get(int index)
返回指定索引处的元素

int

size()
返回集合中的元素个数



6.

LinkedList<E>

(List的实现类)



①、概述:

  • LinkedList采用的是双向循环链表实现List接口



②、构造方法:

构造方法 描述
public LinkedList() 创建空的链表
public LinkedList(Collection<? extends E> c) 创建包含容器c中所有元素的链表



③、常用方法:

常用方法 描述
public void addFirst(E e) 将元素e插入到列表的开头
public void addLast(E e) 将元素e添加到列表的末尾
public E getFirst() 返回列表中的第一个元素
public E getLast() 返回列表中的最后一个元素
public E removeFirst() 返回列表中的最后一个元素
public E removeFirst() 删除并返回列表中的第一个元素
public E removeLast() 删除并返回列表中的最后一个元素



7.

Set

集合接口



①、概述:

  • Set是一个不含重复元素的集合接口,继承自Collection接口,并没有声明其他方法,它的方法都是从Collection接口继承来的。
  • Set集合中的对象不按特定的方式排序,只是简单的把对象加入到集合中,但加入的对象一定不能重复。集合中元素的顺序与元素加入集合的顺序无关。
  • 实现Set接口的两个主要类是哈希结合类

    HashSet

    及树集合类

    TreeSet



②、

HashSet

哈希集合类



1.概述:
  • 哈希集合是在元素的存储位置和元素的值k之间建立一个特定的对应关系f,使每个元素与一个唯一的存储位置相对应。
  • 哈对应关系f为哈希函数,按这种关系建立的表称为哈希表,也称散列表


2.特点:
  • 底层数据结构的哈希表
  • 对集合的迭代顺序不做任何保证,也就是说不保证存储和取出的元素顺序保持一致
  • 没有带索引的方法,所以不能使用普通for循环遍历,可以用迭代器或者增强for循环遍历
  • 由于是Set集合,所以是不包含重复元素的集合


3.哈希表的存储方式:

image-20211105155521570



4.HashSet集合添加一个元素的过程:
  • HashSet集合存储元素,要保证元素的唯一性,需要重写

    hashCode()

    方法和

    equals()

    方法,可以在定义类的时候快速生成重写方法

HashSet添加一个元素的过程



③、

LinkedHashSet<E>



1.概述:
  • 哈希表和链表实现的Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复的元素



④、

TreeSet

树集合类



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

    构造方法

    • Tree():根据其元素的自然排序进行排序
    • TreeSet(Comparator comparator):根据指定的比较器进行排序
  • 没有索引的方法,所以不能使用普通for循环遍历,可以用增强for循环或者迭代器循环遍历
  • 由于是Set集合,所以不包含重复元素的集合
  • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写


2.排序写法一:
  • Student学生类
package test3;

//实现Comparable接口
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 String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //    重写Comparator接口中的compareTo方法
    /**
     * @param s
     * @return 0 代表当前元素与前一个元素重复,则不添加到集合中
     * 1 代表当前元素比前一个元素大,则将当前元素添加到后面一个位置
     * -1 代表当前元素比前一个元素小,则将当前元素添加到前面一个位置
     */
    @Override
    public int compareTo(Student s) {
        int num = this.age - s.age;//求年龄的差值
        return num == 0 ? this.name.compareTo(s.name) : num;//如果年龄相等,则返回名字的自然排序结果
    }
}
  • TreeSetDemo树集合测试类
package test3;

import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
//        创建树结合类,使用无参构造方法
        TreeSet<Student> list = new TreeSet<Student>();

//        创建学生对象
        Student a = new Student("张三", 15);
        Student b = new Student("李四", 12);
        Student c = new Student("王五", 22);
        Student d = new Student("小六", 22);

//          将学生对象添加到树集合中
        list.add(a);
        list.add(b);
        list.add(c);
        list.add(d);

//      用增强for循环遍历树集合
        for(Student item: list){
            System.out.println(item);
        }
    }
}
输出结果:
  "C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=2416:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.TreeSetDemo
Student{name='李四', age=12}
Student{name='张三', age=15}
Student{name='小六', age=22}
Student{name='王五', age=22}

Process finished with exit code 0


3.排序方案二:
  • Student学生类:
package test3;

//实现Comparable接口implements Comparable<Student>
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 String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

//    //    重写Comparator接口中的compareTo方法
//    /**
//     * @param s
//     * @return 0 代表当前元素与前一个元素重复,则不添加到集合中
//     * 1 代表当前元素比前一个元素大,则将当前元素添加到后面一个位置
//     * -1 代表当前元素比前一个元素小,则将当前元素添加到前面一个位置
//     */
//    @Override
//    public int compareTo(Student s) {
//        int num = this.age - s.age;//求年龄的差值
//        return num == 0 ? this.name.compareTo(s.name) : num;//如果年龄相等,则返回名字的自然排序结果
//    }
}
package test3;

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

public class TreeSetDemo1 {
    public static void main(String[] args) {
//        创建树集合对象
        TreeSet<Student> list = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
//                this.age - s.age === o1.getAge() - o2.getAge()
                int num = o1.getAge() - o2.getAge();
                return num == 0 ? o1.getName().compareTo(o2.getName()) : num;
            }
        });

        //        创建学生对象
        Student a = new Student("张三", 15);
        Student b = new Student("李四", 12);
        Student c = new Student("王五", 22);
        Student d = new Student("小六", 22);

//          将学生对象添加到树集合中
        list.add(a);
        list.add(b);
        list.add(c);
        list.add(d);

//      用增强for循环遍历树集合
        for (Student item : list) {
            System.out.println(item);
        }
    }
}
"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=3349:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.TreeSetDemo1
Student{name='李四', age=12}
Student{name='张三', age=15}
Student{name='小六', age=22}
Student{name='王五', age=22}

Process finished with exit code 0



8.

Map<K,V>

映射接口



①、概述:

Map是另一种存储数据结构的对象,Map接口与List接口和Set接口有明显的区别。Map中的元素都是成对出现的,它提供了键(

Key

)到值(

value

)的映射。键决定了元素在Map中的存储位置。一个键和它对应的值构成了一个条目,真正在Map中存储的是这个条目。键很想下标,但在List中下标是整数,而在Map中键可以是任意类型的对象。如果要在Map中检索一个元素,必须提供相应的键,这样就可以通过键访问到其对应元素的值。Map中每个键都是唯一的,且每个键最多只能映射到一个值。

  • 映射接口Map常用的实现类有哈希映射HashMap和树映射TreeMap。
  • HashMap对于添加和删除映射关系效率较高,并且允许使用null值和null键
  • 树映射TreeMap的映射关系存在一定的顺序,由于TreeMap类实现的Map映射中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null



②、

Map<K,V>

接口的常用方法:

  • HashMap映射的方法大多也是继承自Map接口
常用方法 功能说明
V put(K key, V value) 以key为键,向集合中添加值为value的元素,其中key必须唯一,否则新添加的值会取代已有的值
void putAll(Map<? extends K, ? extends V> m) 将映射m中的所有映射关系赋值到调用此方法的映射中
bollean containsKey(Object key) 判断是否包含指定的键key
boolean containsValue(Object value) 判断是否包含指定的值value
V get(Object key) 返回键key所映射的值,若key不存在则返回null
Set< K > keySet() 返回该映射中所有键对象形成的Set集合
Collection < V >values() 返回该映射中所有值对象形成Collection集合
V remove(Object key) 将键为key的条目,从Map对象中删除
Set< Map.Entry< K, V>> entrySet() 返回映射中的键-值对的集合



③、

HashMap<K,V>

映射的常用构造方法:

构造方法 功能说明
public HashMap() 构造一个具有默认初始容量(16)和默认上座率(0.75)
public HashMap(int initialCapacity) 创建初始容量为initialCapacity和默认上座率为(0.75)的空HashMap对象
public HashMap(Map< ? extends K, ? extends V> m) 创建一个映射关系与指定Map相同的新HashMap对象。具有默认上座率(0.75)和足以容纳指定Map中映射关系的初始容量
  • HashMap映射的方法大多是继承自Map接口



④、

TreeMap<K,V>

映射的常用构造方法:

构造方法 功能说明
public TreeMap() 使用键的自然顺序创建一个新的空树映射
public TreeMap(Map< ? extends K, ? extends V> m) 创建一个与给定映射具有相同映射关系的新树映射,该映射根据其键的自然顺序进行排序



⑤、TreeMap<K,V>映射的常用方法:

常用方法 功能说明
public K firstKey() 返回映射中的第一个(最低)键
public K lastKey() 返回映射中的最后一个(最高)键
public SortedMap< K,V > headMap(K toKey) 返回键小于toKey的那部分映射
public SortedMap< K,V > tailMap(K fromKey) 返回键大于或等于fromKey的那部分映射
public K lowerKey(K key) 返回严格

小于

给定键key的

最大键

,如果不存在这样的键,则返回null
public K floorKey(K key) 返回严格

小于或等于

给定键key的

最大键

,如果不存在这样的键,则返回null
public higherKey(K key) 返回严格

大于

给定键key的

最小键

,如果不存在这样的键,则返回null
public K ceiling(K key) 返回严格

大于或等于

给定键key的

最小键

,如果不存在这样的键,则返回null



⑥、Map集合遍历的两种方式:

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 t1 = new Student("张三",30);
        Student t2 = new Student("李四",35);
        Student t3 = new Student("王五",33);

//        将学生添加到Map集合
        hm.put("001",t1);
        hm.put("002",t2);
        hm.put("003",t3);

//        遍历方式一:先获取键,再获取值
        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("--------");

//        遍历方式二:键值对对象获取键和值
        Set<Map.Entry<String, Student>> entrySet =  hm.entrySet();//获取键值对集合
        for(Map.Entry<String, Student> item: entrySet){
            String key = item.getKey();
            Student value = item.getValue();
            System.out.println(key + "," + value.getName() +"," + value.getAge());
        }
    }
}
"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=5689:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.HashMapDemo
001,张三,30
002,李四,35
003,王五,33
--------
001,张三,30
002,李四,35
003,王五,33

Process finished with exit code 0



9.

Collections

类的使用



①、概述:

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



②、常用方法:

常用方法 功能说明
public static < T extends Comparalbe< ? super T>> void sort(List < T > list) 将指定的列表按升序排序
public static void reverse(List< ? > list) 反转指定列表中元素的顺序
public static void shuffle(List< ? > list ) 使用默认的随机源排列指定的列表



九、泛型



1.概述

  • 是JDK5之后引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测非法的类型,它的本质是

    类型参数化

    ,也就是说操作的数据类型被指定为一个参数。顾名思义,就是

    将类型由原来的具体类型参数化,然后在调用时传入具体的类型

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

    参数多态



2.泛型定义格式:

  • <类型>:指定一种类型的格式。这里的类型可以看成是

    形参
  • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是

    形参
  • 将来具体调用时候给定的类型可以看成是

    实参

    ,并且实参的类型必须是

    引用数据类型

    ,即必须是

    类类型

    ,不能用如int、double、或char等这样的基本类型来替换类型参数T
  • 泛型类的定义:
泛型类的定义: 修饰符 class 类名<T>
泛型接口的定义:public interface 接口名<T>
泛型方法的定义:public static <T>返回值类型 方法名(T 参数) 



3.泛型的好处:

  • 很多程序员为了让程序通用,编写代码时通常使传入的值都以Object类型为主,当需要使用相应实例时,必须正确地将该实例转换为原来的类型,否则在程序运行时将会发生类型强制转换异常:

    ClassCastException

    ,因此该方式存在安全隐患。而泛型的主要优点就是能够在编译时而不是在运行时检测出错误,能够将运行时期的问题提前到了编译期间
  • 避免了强制类型转换



4.类型通配符:

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

    • 用于创建可重新赋值但不可以修改其内容的泛型对象
    • 用在方法的参数中,限制传入不想要的类型实参



①、

非受限通配:<?>

  • 等价于? extends Object


  • List<?>

    :表示元素类型未知的List,它的元素

    可以匹配任何的类型

  • 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中



②、

上限通配:<? extends 类型>


  • List<? extends Number>

    :它表示的类型是

    Number或者其子类型



③、

下限通配:<? super 类型>

  • List<? super Number>:它表示的类型是

    Number或者其父类型



④、代码示例:

package test3;

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<Object>();//编译错误,上限为Number,只能为Number类或其子类型
    List<? extends Number> list5 = new ArrayList<Number>();
    List<? extends Number> list6 = new ArrayList<Integer>();
        System.out.println("--------");
//        类型通配符下限:<? super 类型>
        List<? super Number> list7 = new ArrayList<Object>();
        List<? super Number> list8 = new ArrayList<Number>();
//        List<? super Number> list9 = new ArrayList<Integer>();//编译错误,下限为Number,只能为Number类或其父类型
    }
}



5.

注意事项


  • 一个static方法,无法访问泛型类的类型参数,所以如果static方法需要使用泛型能力,必须使其成为泛型方法

  • 对于实现了某接口的有限制泛型,也是用extends关键字,而不是implements关键字

  • 由于JVM只是在编译时对泛型进行安全检查,所以特别强调以下几点:

    • 不能使用泛型的类型参数T创建对象。如,T obj = new T();是错误的
    • 在泛型中可以用类型参数T声明一个数组,但不能使用类型参数T创建数组对象。例如,T[] a = new T[个数];是错误的
    • 不能再静态环境中使用泛型类的类型参数T。例如
    public class Test<T>
    {
      public static T obj;						//非法,使用了泛型类的类型参数T
      public static void m(T obj1){}	//非法,使用了泛型类的类型参数T
      static{													//定义静态初始化器
        T obj2;												//非法,使用了泛型类的类型参数T
      }
    }
    
    • 异常类不能是泛型的,即泛型类不能继承java.lang.Throwable类。如,public class MyException extends Exception{}是错误的



6.可变参数:

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

    • 格式:

      修饰符 返回值类型 方法名(数据类型... 变量名){}
    • 范例:public static int sum(int… a){}
  • 可变参数的注意事项:

    • 这里的变量是一个数组
    • 如果一个方法有多个参数,包含可变参数,

      可变参数要放到形参列表的最后
  • 可变参数的使用:

    • 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)

        :返回一个包含任意数量元素的不可变集合
      • 在给元素的时候,不能给重复的元素
      • 返回的集合不能做增删操作,没有修改的方法



十、IO流与文件处理

绝对路径与相对路径的区别:

  • 绝对路径:

    完整的路径名

    ,不需要任何其他的信息就可以定位它所表示的文件,例如:

    E:\\itcast\\java.txt
  • 相对路径:必须取自其他路径名的信息进行解释。例如:

    myFile\\java.txt

字符串获取其对应字节数组bytes[]的方法:byte[] getBytes(charsetName name):返回字符串对应的字节数组,使用指定编码格式

name

, 默认为

utf-8

字节数组转成字符串String的方法:new String(Byte[] bytes):返回字节数组对应的字符串

一个汉字存储:

​ GBK编码:占用2个字节

​ UTF-8编码:占用3个字节



1.File类:



①、概述:

  • java语言不仅支持

    文件管理

    ,还支持

    文件夹管理

    。在java.io包中定义了一个File类专门用来管理磁盘文件和文件夹,而不负责数据的输入输出。
  • 每个File类的对象表示

    一个磁盘文件或文件夹

    ,其对象属性中包含了文件或文件夹的相关信息,如文件名、长度、所包含文件个数等,调用它的方法可以完成对文件或文件夹的管理操作,如创建、删除等。



②、创建File类的对象:

  • 创建File对象需要给出磁盘文件或者文件夹的文件名或文件夹名
构造方法 功能说明
public File(String path) 用path参数创建File对象所对应的磁盘文件名或文件夹名及其路径
public File(String path, String name) 以path为路径,以name为文件或文件夹名创建File对象
public File(File dir, String name) 用一个已经存在代表某磁盘文件夹的File对象dir作为文件夹,以name作为文件或文件夹名来创建File对象
  • 注意事项:

    • path参数可以是绝对路径,如”d:\java\myfile\sample.java”,也可以是相对路径,如”myfile\sample.java”,path参数还可以是磁盘上的某个文件夹
    • 由于不同的操作系统使用的文件夹分隔符不同,Windows上用的是” \ “,Unix上用的是” / “。因此,java的File类提供了一个静态变量

      File.separator

      .该属性中保存了当前系统规定的文件夹分隔符,使用它可以组合成再不同操作系统下都通用的路径。如:“d:” +File.separator + “java” + File.separator + “myfile”。



③、File类中的常用方法:

  • File类中获取文件或文件夹属性的常用方法:
常用方法 功能说明

public boolean exists()
判断文件或文件夹是否存在

public boolean isFile()
判断对象是否代表有效文件

public boolean isDirectory()
判断对象是否代表有效文件夹

public String getName()
返回文件名或文件夹名

public String getPath()
返回文件或文件夹的路径

public String getAbsolutePath()
返回文件或文件夹的绝对路径字符串
public long length() 返回文件的字节数
public boolean canRead() 判断文件是否可读
public boolean canWrite() 判断文件是否可写

public String[] list()
将文件夹中所有文件名保存在字符串数组中返回

public File[] listFiles()
返回文件夹中所有文件对象保存在File对象数组中返回
public boolean equals(File f) 比较两个文件或文件夹是否相同
  • File类中对文件或文件夹操作的常用方法:
常用方法 功能说明
public boolean renameTo(File newFile) 将文件重命名成newFile对应的文件名
public boolean delete() 将当前文件删除,若删除成功返回true,否则返回false
public boolean mkdir() 创建当前文件夹的子

文件夹

,若成功返回true,否则返回false
public boolean mkdirs() 创建当前文件夹的子

文件夹

,可以创建多级文件夹,如果成功则返回true,否则返回false
public boolean creatNewFile() 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件



3.IO流:



①、概述:

  • 流是指计算机各部件之间的数据流动。
  • 按照数据的

    流向

    分:


    • 输入流

      :读数据:将数据从外设或外存(如键盘、鼠标、文件等)传递到应用程序的流。

    • 输出流

      :写数据:将数据从应用程序传递到外设或外存(如屏幕、打印机、文件等)的流。
  • 按照数据的

    类型

    分:


    • 字节流

      • 字节输入流、字节输出流

    • 字符流

      • 字符输入流、字符输出流



②、特点:

  • 一般来说,IO流的分类是按照数据类型分的

  • 如果数据通过Windows自带的记事本软件打开,可以读懂内容,则使用字符流,否则使用字节流。如果不确定,则使用字节流。虽然字节流可以操作文本数据,但是不提倡这样做,因为用字节流操作文本文件,如果文件中有汉字,可能会出现论乱码。这是因为字节流不能直接操作Unicode字符所致。因此Java语言不提倡使用字节流读写文本文件,而建议使用字符流操作文本文件。

  • 字节流每次读写8位二进制数,又被称为二进制字节流或位流

  • 字符流一次读写16位二进制数,并将其作为一个字符而不是二进制位来处理

  • Java中的字符默认使用的是16位的Unicode编码,每个字符占用2个字节

  • 流中的方法都声明异常,所以程序中调用流方法时必须处理异常,否则编译不能通过。由于所有的I/O流类都实现了AutoCloseable接口,而只有实现该接口的资源才可以使用try-with-resource语句自动关闭打开的资源。因此,在使用I/O流进行输入输出操作时,可以使用try-with-resource语句处理异常



③、流的基本类:

*	在java.io包中有四个基本类:InputStream、OutpurStream及Reader、Writer类,它们分别处理字节流和字符流
*	层次关系图:

输入输出流的层次结构图



④、字节流写数据实现换行:

  • 写完数据后加换行符:

    • Windows:

      \r\n
    • linux:

      \n
    • mac:

      \r



4.字节输入输出流:



①、InputStream流类的常用方法:

  • 由于InputStream是

    抽象类

    ,所以程序中创建的输入流对象一般是

    InputStream某个子类的对象

    ,通过调用该对象继承的read()方法就可以实现对相应外设的输入操作
常用方法 功能说明

public int read()
从输入流中的当前位置读入一个字节(8位)的二进制数据,然后以此数据为低位字节,配上8个全0的高位字节合成一个16位的整型量(0~255)返回给调用此方法的语句,若输入流中的当前位置没有数据,则返回-1

public int read(byte[] b)
从输入流中的当前位置连续读入多个字节保存在数组b中,同时返回所读到的字节数

public int read(byte[] b, int off, int len)
从输入流中的当前位置连续读入len个字节,从数组b的第off+1个元素位置处开始存放,同时返回所读到的字节数
public int available() 返回输入流中可以读取的字节数
public long skip(long n) 使位置指针从当前位置向后跳过n个字节
public void mark(int readlimit) 在当前位置处做一个标记,并且在输入流中读取readlimit个字节数后该标记消失
public void reset() 将位置指针返回到标记的位置

public void close()
关闭输入流与外设的连接并释放所占用的系统资源



②、OutputStream流类的常用方法:

  • 由于OutputStream是

    抽象类

    ,所以程序中创建的输入流对象一般是

    OutputStream某个子类的对象

    ,通过调用该对象继承的read()方法就可以实现对相应外设的输入操作
常用方法 功能说明

public void write(int b)
将参数b的低位字节写入输出流

public void write(byte[] b)
将字节数组b中的全部字节按顺序写入到输出流

public void write(byte[] b, int off, int len)
将字节数组b中的第off+1个元素开始的len个数据,顺序的写入到输出流

public void flush()
强制清空缓冲区并执行向外设写操作

public void close()
关闭输出流与外设的连接并释放所占用的系统资源



③、FileInputStream文件字节输入流:

  • 在生成FileOutputStream类的对象时,若指定的文件不存在,则创建一个新的文件,如果已存在,则清除原文件的内容。在进行文件的读写操作时会产生IOException异常,该异常必须捕获或声明抛出。
构造方法 功能说明
public FileInputStream(String name) 以名为name的文件为数据源建立文件输入流
public FileInputStream(File file) 以文件对象file为数据源建立文件输入流
public FileInputStream(FileDescriptor fdObj) 以文件描述符对象fdObj为输入端建立一个文件输入流



④、FileOutputStream文件字节输出流:

  • 无论哪个构造方法,在创建文件输入或输出流时都可能因给出的文件名不对、路径不对或文件的属性不对等,不能打开文件而造成错误,此时系统会抛出FileNotFoundException异常。执行read()和write()方法时还可能因I/O错误,系统抛出IOException异常,所以创建输入输出流并调用构造方法语句以及执行读写操作的语句应该被包含在try语句块中,并有相应的catch语句块来处理可能产生的异常。同样也可以使用自动关闭资源语句try-with-resources处理异常
构造方法 功能说明

public FileOutputStream(String name)
以指定名字的文件为接收端建立文件输出流

public FileOutputStream(String name, boolean append)
以指定名字的文件为接收端建立文件输出流,并指定写入方式,append为true时输出字节被写到文件的末尾

public FileOutputStream(File name, boolean append)
以指定File文件对象为接收端建立文件输出流,并指定写入方式,append为true时输出字节被写到文件的末尾

public FileOutputStream(File file)
以文件对象file为接收端建立文件输出流

public FileOutputStream(FileDescriptor fdObj)
以文件描述符对象fdObj建立一个文件输出流

  • FileDescriptor

    是java.io包中定义的一个类,该类不能实例化,该类中有三个静态成员:in、out、err,分别对应于

    标准输入流



    标准输出流



    标准错误流

    ,利用它们可以在标准输入流和标准输出流上建立文件输入输出流,实现

    键盘输入



    屏幕输出操作


一、每次读写一个字节的数据:
import java.io.*;

public class demo1 {
    public static void main(String[] args) {
//        文件输入流类
        FileInputStream fin = new FileInputStream(FileDescriptor.in);//从键盘输入
        FileOutputStream fout = null;//抛出文件未找到异常
        try {
            fout = new FileOutputStream("myFile.txt");
            System.out.println("输入一串字符,以#结束");
            char ch;
//从键盘中读取数据,每次读取一个字节
            while ((ch = (char) fin.read()) != '#') {
                fout.write(ch);//写入到指定文件中
            }
        } catch (FileNotFoundException e) {
            System.out.println("文件未找到!");
        } catch (IOException e) {
            System.out.println("I/O异常!");
        }
        finally {
//            如果输入流不为空,则关闭资源
            if(fin != null){//防止文件打开错误导致fin为空,避免空指针异常
                try {
                    fin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
//            如果输出流不为空,则关闭资源
            if(fout != null){
                try {
                    fout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        int len;
//        使用try-with-resources方式处理异常,自动释放资源
//        从文件中读取数据,输出到屏幕中
        try(FileInputStream fi = new FileInputStream("myFile.txt");
        FileOutputStream fo = new FileOutputStream(FileDescriptor.out);){
            while(fi.available()>0){
                len = fi.read();
                fo.write(len);
            }
        } catch (FileNotFoundException e) {
            System.out.println("文件未找到!");
        } catch (IOException e) {
            System.out.println("I/O流错误");
        }
    }
}

"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=10422:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.demo1
输入一串字符,以#结束
haowdh中国#
haowdh中国
Process finished with exit code 0


二、每次读写一个字节数组的数据:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class demo2 {
    public static void main(String[] args) {
//        创建文件输入输出流
        try (FileInputStream fin = new FileInputStream("原图.png");
             FileOutputStream fout = new FileOutputStream("复制图.png");) {
//            获取输入流文件的长度
            int len = fin.available();
//            创建字节数组
            byte[] arr = new byte[len];
//            获取原文件的大小
            System.out.println("图片的大小为:" + fin.available());
//            从文件输入流中读取数据到字节数组中
            fin.read(arr);
//            将字节数组中的数据写入到指定的文件输出流中,并且指定写入的起始位置和写入长度
            fout.write(arr,0,len);
            System.out.println("文件已被成功复制");
        } catch (IOException e) {
            System.out.println("I/O异常");
        }
    }
}


"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=12865:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.demo2
图片的大小为:593924
文件已被成功复制

Process finished with exit code 0



⑤、BufferInputStream、BufferOutputStream字节缓冲输入流:

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

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

  • 当向一个缓冲流写入数据时,系统将数据发送到缓冲区,而不是直接发送到外部设备。缓冲区自动记录数据,当缓冲区满时,系统将数据全部发送到相应的外部设备

  • 当从一个缓冲流中读取数据时,系统实际是从缓冲区中读取数据。当缓冲区空时,系统就会从相关外部设备自动读取数据,并读取尽可能多的数据填满缓冲区

  • 构造方法:

    • 字节缓冲输入流:BufferInputStream(InputStream in)
    • 字节缓冲输出流:BufferOutputStream(OutputStream out)
    • 字节缓冲流仅仅提供缓冲区,而真正读写数据还得依靠基本的字节流对象进行操作
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class demo3 {
    public static void main(String[] args) throws IOException {
//        创建指定文件的字节输入流对象
//        FileInputStream in = new FileInputStream("myFile.txt");
//        创建缓冲区字节输入流对象,传入已有的文件输入流对象
//        BufferedInputStream bis = new BufferedInputStream(in);

//        等价于
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myFile.txt"));
        int data;
//        逐个字节读取
//        while((data = bis.read())!= -1){
//            System.out.println((char)data);
//        }

//        按照字节数组读取
        byte[] bytes = new byte[1024];
        int len;//实际读取数据的字节长度
//        当读取成功时,按照实际读取长度输出字符串
        while((len = bis.read(bytes)) != -1){
            System.out.println(new String(bytes,0,len));
        }
        bis.close();
    }
}


"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=12921:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\javaSE\out\production\javaSE test3.demo3
awd

Process finished with exit code 0



⑥、字节流读写文件的四种方式的时间测算:

  • 字节缓冲流按字节数组读写文件耗时最短
import java.io.*;

//        四种方式读写方式  复制视频花费的时间
//        1.基本字节流一次读取一个字节         共耗时:13749毫秒
//        2.基本字节流一次读取一个字节数组      共耗时:27毫秒
//        3.字节缓冲流一次读取一个字节          共耗时:31毫秒
//        4.字节缓冲流一次读取一个字节数组      共耗时:8毫秒
public class time {
    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) + "毫秒");
    }

    //字节缓冲流逐个字节数组读写文件
    private static void method4() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("原视频.mp4"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("复制视频.mp4"));

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

    //字节缓冲流逐个字节读写文件
    private static void method3() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("原视频.mp4"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("复制视频.mp4"));

        int data;
        while ((data = bis.read()) != -1) {
            bos.write(data);
        }
        bis.close();
        bos.close();
    }

    //基本字节流逐个字节数组读写文件
    private static void method2() throws IOException {
        FileInputStream fin = new FileInputStream("原视频.mp4");
        FileOutputStream fout = new FileOutputStream("复制视频.mp4");
        byte[] bytes = new byte[1024];
        int len;
        while ((len = fin.read(bytes)) != -1) {
            fout.write(bytes, 0, len);
        }
        fin.close();
        fout.close();
    }

    //    基本字节流逐个字节读写文件
    private static void method1() throws IOException {
        FileInputStream fin = new FileInputStream("原视频.mp4");
        FileOutputStream fout = new FileOutputStream("复制视频.mp4");
        int data;
        while ((data = fin.read()) != -1) {
            fout.write(data);
        }
        fin.close();
        fout.close();
    }
}



5.字符输入输出流:



①、要点:

  • 一个汉字存储:

    • GBK编码:占用2个字节
    • UTF-8编码:占用3个字节
  • 由于字节流操作中文不是特别方便,所以java就提供字符流

    • 字符流 = 字节流 + 编码表
  • 用字节流赋值文本时,文本文件也会有中文,但是没有问题,原因是

    最终底层操作会自动进行字节拼接成中文
  • 如何识别中文:汉字在存储时,无论哪种编码存储,第一个字节都是负数



②、编码表:

  • 基础知识:

    • 计算机中存储的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
    • 按照某种规则,将字符存储到计算机中,成为

      编码

      。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,成为

      解码

      。注意:按照A编码存储,必须按照A编码解析,才能显示正确的文本。否则会出现乱码
    • 字符编码:就是一套自然语言的字符与二进制数之间的对应规则(A,65)
  • 字符集:

    • 是一个系统支持的所有字符的集合,包括国家文字、标点符号、图形符号、数字等
    • 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见的字符集有

      ASCII字符集



      GBXXX字符集



      Unicode字符集


  • ASCII字符集

    • ASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
    • 基本的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)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码
    • 编码规则:

      • 128个US-ASCII字符,只需要一个字节编码
      • 拉丁文等字符,需要两个字节编码
      • 大部分常用汉字(含中文),使用三个字节编码
      • 其他极少使用的Unicode辅助字符,使用四字节编码



③、字符串中的编码解码问题:

  • 编码:

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

    • String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
    • String(byte[] bytes,String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class demo5 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "中国";
//        byte[] bytes = s.getBytes();//[-28, -72, -83, -27, -101, -67]
//        byte[] bytes = s.getBytes(StandardCharsets.UTF_8);//[-28, -72, -83, -27, -101, -67]
//        byte[] bytes = s.getBytes("UTF-8");//[-28, -72, -83, -27, -101, -67]
        byte[] bytes = s.getBytes("GBK");//[-42, -48, -71, -6]
//        String ss = new String(bytes, "UTF-8");//中国
//        System.out.println(Arrays.toString(bytes));
//        String ss = new String(bytes,"GBK");//用UTF-8编码,用GBK解码--->涓浗
        
        System.out.println(ss);
    }
}



④、字符流写入数据的五种方式:


方法名
说明
void write(int c) 写一个字符
void write(char[] cbuf) 写入一个字符数组
void write(char[] cbuf, int offff, int len) 写入字符数组的一部分
void write(String str) 写一个字符串
void write(String str, int offff, int len) 写一个字符串的一部分

方法名
说明
flush() 刷新流,之后还可以继续写数据
close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据



⑤、字符流读取数据的两种方式

方法名 功能说明

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

读取到的字符

int read(char[] cbuf, int offset, int length)
一次读一个字符数组数据,返回

读取到的字符数


代码示例:
package cn.itcast.web.servlet;

import java.io.*;

public class Demo1 {
    public static void main(String[] args) throws IOException {
//        字符输入流
        InputStreamReader isr = new InputStreamReader(new FileInputStream("myFile.txt"));
//        字符输出流
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("copyFile.txt"));

      
      //根据数据源创建字符输入流对象   便携类,等价于上面的写法
	      //FileReader fr = new FileReader("myCharStream\\ConversionStreamDemo.java");
		 //根据目的地创建字符输出流对象 
				// FileWriter fw = new FileWriter("myCharStream\\Copy.java");
      
      
      
        一次读写一个字符,read()的返回值是读取到的字符,类型为int
//        int ch;
//        while ((ch = isr.read()) != -1) {
//            osw.write(ch);
//        }

//        一次读写一个字符数组,read(char[] cbuf)的返回值是读取到的字符数,类型为int
        char[] ch = new char[1024];
        int len;
        while((len = isr.read(ch)) != -1){
            osw.write(ch,0,len);
        }

        //释放资源
        isr.close();
        osw.close();
    }
}



⑥、字符缓冲流:



6.序列化和反序列化:

序列化:就是在保存数据时,保存数据的





数据类型

反序列化:就是在恢复被序列化的数据时,恢复数据的





数据类型

注意:

​ 需要让某个对象支持序列化机制,则必须让其



是可以序列化的,为了让某个类是可以序列化的,该类必须实现如下两个接口之一:


  • Serializable

    //标记接口,接口中没有方法

  • Externalizable

    //该接口有方法需要实现,因此一般推荐实现Serializable接口



7.Properties类处理配置文件:



①、概念:

专门用于处理配置文件的集合类:

配置文件的格式:

键=值

键=值

注意:

键值不需要有空格,值不需要用引号引用,默认类型是String



②、常见方法:

方法名 功能
void load (Reader reader) void load (InputStream inputstream) 以简单的线性格式从输入字符流读取属性列表(关键字和元素对)/从输入字节流读取属性列表(键和元素对)

getProperty(String key)
使用此属性列表中指定的键搜索属性

list(PrintStream out)
将此属性列表打印到指定的输出流
键值对

setProperty(String key, String value)
设置键值对到Properties对象

store(OutputStream out, String comments)
将此属性列表(键和元素对)写入此

Properties

表中,以适合于使用


load(InputStream)


方法加载到

Properties

表中的格式输出流 ,如果有中文,会转成unicode编码

store(Writer writer, String comments)
将此属性列表(键和元素对)写入此

Properties

表中,以适合使用


load(Reader)


方法的格式输出到输出字符流



十一、网络编程



1.UDP通讯程序



一、发送数据的步骤:



①、创建发送端的Socket对象(DatagramSocket)

Datagramsocket()

DatagramSocket ds = new DatagramSocket();


②、创建数据,并把数据打包

DatagramPacket(byte[] buf, int length, InetAdress, int port)

byte[] data = "helllo world,我来了".getBytes();
DatagramPacket dp = new DatagramPacket(data,data.length, InetAddress.getByName("192.168.1.103"),10086);


③、调用DatagramSocket的send()方法发送数据

ds.send(data);



④、关闭发送端

ds.close();



⑤、代码示例:
package cn.itcast.web.servlet;

import java.io.IOException;
import java.net.*;

public class SendDemo {
    public static void main(String[] args) throws IOException {
//        1.创建Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
//        2.创建数据包(DatagramPacket)
        byte[] bytes = "我要发送的数据".getBytes();
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("192.168.1.103"),10086);
//        3.调用DatagramSocket的send()方法发送数据包
        ds.send(dp);
//        4.关闭发送端
        ds.close();
    }
}



二、接收数据的步骤:



①、创建接收端的Socket对象(DatagramSocket)

需要绑定接收数据的端口

DatagramSocket(int port);



②、创建接收数据的数据包对象
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);


③、调用DatagramSocket的receive()方法接收数据并解析数据包
ds.receive(dp);
byte[] data = dp.getData();//解析数据
int len = dp.getLength();//获取实际获取数据的长度
String s = new String(data, 0, len);
System.out.println("接收到的数据为:"+s);


④、关闭接收端

ds.close();



⑤、代码示例:
package cn.itcast.web.servlet;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
//         1.创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10086);//需要指定接收数据的端口
//        2.创建接收数据的数据包
        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys, bys.length);
//        3.调用DatagramSocket的receive()方法接收数据包,并解析打印
        ds.receive(dp);//接收数据包
        byte[] data = dp.getData();//解析数据
        int len = dp.getLength();//获取实际获取数据的长度
        String s = new String(data, 0, len);
        System.out.println("接收到的数据为:"+s);
//        4.关闭接收端
        ds.close();
    }
}



三、UDP通讯程序练习

UDP发送端一直发送数据,直到发送886停止发送

UDP接收端不知道发送端何时停止发送数据,故死循环接收数据



①、发送端:

package cn.itcast.web.servlet;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class send {
    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
//        1.创建Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        String data;
        while(!"886".equals(data = in.nextLine())){
            //        2.创建数据包(DatagramPacket)
            byte[] bytes = data.getBytes();
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("192.168.1.103"),10086);
//        3.调用DatagramSocket的send()方法发送数据包
            ds.send(dp);
        }
//        4.关闭发送端
        ds.close();
    }
}



②、接收端:

package cn.itcast.web.servlet;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class receice {
    public static void main(String[] args) throws IOException {
//        1.创建Socket对象(DatagramSocket)并绑定接收数据的端口
        DatagramSocket ds = new DatagramSocket(10086);
        byte[] bytes = new byte[1024];
        int cnt = 0;
        while(true){
//        2.创建接收的数据包
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
            ds.receive(dp);
//        3.解析数据包
            String s = new String(dp.getData(), 0, dp.getLength());
            System.out.println("第" + (++cnt) + "次接收到的数据为:" + s);
        }
    }
}



2.TCP通讯程序

客户端创建套接字用Socket

服务端创建套接字用ServerSocket,再调用accept监听客户端的连接,创建出Socket对象



一、客户端和服务端的数据通讯

功能

​ 客户端:向服务端发送数据,接收服务端的反馈

​ 服务端:监听客户端的连接,向客户端发送反馈



客户端代码:
package cn.itcast.web.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
/**        客户端:发送数据,接收服务端的反馈*/
//        1.创建socket套接字对象
        Socket s = new Socket("192.168.1.103",10001);//需要指定主机地址和端口
//    2.创建字节输出流
        OutputStream os = s.getOutputStream();
        os.write("客户端发送的数据".getBytes());//写数据
//        3.创建字节输入流,接收数据,接收服务端的反馈
        InputStream is = s.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes); //通过字节输入流读数据到字节数组中
        String data = new String(bytes, 0, len);
        System.out.println("客户端接收到:" + data);
//        4.释放资源
        s.close();
    }
}

image-20220121004500466



服务端代码:
package cn.itcast.web.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        /**     服务端:接收客户端的数据,给出反馈     */
//        1.创建服务器套接字,并指定端口
        ServerSocket ss = new ServerSocket(10001);//需指定端口
//        2.监听客户端是否发送连接,
        Socket s = ss.accept();//等待客户端的连接
//        3.创建输入流对象,接收客户端的数据
        InputStream is = s.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println("服务端接收到:"+new String(bytes,0,len));
//        4.创建输出流对象,向客户端发送反馈
        OutputStream os = s.getOutputStream();
        os.write("服务端给客户端的反馈".getBytes());
//        5.释放资源
//        s.close();//可省略
        ss.close();
    }
}

image-20220121004547779



二、客户端数据由键盘输入

功能:

​ 客户端:数据由键盘输入,向服务端发送数据,直到输入886停止发送

​ 服务端:接收客户端的数据



客户端代码:
package cn.itcast.web.servlet;

import java.io.*;
import java.net.Socket;

/**
 * TCP/IP  客户端:键盘输入向服务端发送数据,直到输入886停止发送
 */
public class A_client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.1.101",10001);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//以socket的输出流
        String line;
        while((line = br.readLine()) != null){
            if("886".equals(line)) break;
//          向服务端发送数据
            OutputStream os = s.getOutputStream();
            bw.write(line);
            bw.newLine();//空行
            bw.flush();//清空缓冲区
        }
        s.close();//释放资源
    }
}


服务端代码:
package cn.itcast.web.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class A_server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10001);
        Socket s = ss.accept();//监听客户端的连接
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String line;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
        ss.close();
    }
}



三、客户端数据键盘输入,服务端接收数据写入到文件



客户端代码:
package cn.itcast.web.servlet;

import java.io.*;
import java.net.Socket;

public class B_client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.1.101", 10001);
        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;
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        s.close();
    }
}


服务端代码:
package cn.itcast.web.servlet;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class B_server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10001);
        Socket s = ss.accept();
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String line;
        BufferedWriter bw = new BufferedWriter(new FileWriter("myFile.txt"));
        while ((line = br.readLine()) != null) {
            if ("886".equals(line)) break;
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
      	bw.close();;//关闭文件输出流
        ss.close();//释放资源
    }
}



四、客户端数据从文件读入,服务端接收数据写入到文件



客户端代码:
package cn.itcast.web.servlet;

import java.io.*;
import java.net.Socket;

public class C_client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.1.101",10001);
        BufferedReader br = new BufferedReader(new FileReader("myFile.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();//释放资源
    }
}


服务端代码:
package cn.itcast.web.servlet;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class B_server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10001);
        Socket s = ss.accept();
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String line;
        BufferedWriter bw = new BufferedWriter(new FileWriter("myFile1.txt"));
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        ss.close();//释放资源
    }
}



五、客户端数据从文件读取,并读取服务端的反馈;服务端将接收的数据写入文件,并向客户端给出反馈



客户端代码:
package cn.itcast.web.servlet;

import java.io.*;
import java.net.Socket;

/**
 * 客户端:向客户端传文件,并接收服务端的反馈
 */
public class D_client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.1.103",10001);

//        客户端读取文件内容,向服务端传输
        BufferedReader br = new BufferedReader(new FileReader("send.java"));
        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.shutdownOutput();//结束标记

        //接收服务端给出的反馈
        BufferedReader is = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str = new String(is.readLine());
        System.out.println("客户端接收到:"+str);
        s.close();//释放资源
    }
}


服务端代码:
package cn.itcast.web.servlet;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class D_server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10001);
        while(true){//循环
            Socket s = ss.accept();//监听客户端的连接
            new Thread(new ServerThread(s)).start();
        }
    }
}




/*多线程*/
package cn.itcast.web.servlet;

import java.io.*;
import java.net.Socket;

public class ServerThread implements Runnable {
    Socket s;

    public ServerThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
//        接收服务端的数据
        int cnt = 0;
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            File file = new File("Copy[" + (++cnt) + "].java");
            while (file.exists()) {
                file = new File("Copy[" + (++cnt) + "].java");
            }
            BufferedWriter bw = new BufferedWriter(new FileWriter(file));
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
            bw.close();//关闭文件输入流

            BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            bwServer.write("文件上传成功");
            bwServer.newLine();
            bwServer.flush();
            s.close();//释放资源
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



七、注意事项:

TCP读取数据是阻塞式的,会一直等待输入,服务器也会一直等待数据传输过来,所以可以在客户端使用结束标记(

s.shutdownOutput()

)结束输出流



十二、多线程



1.概念:


  • 程序:

    程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码


  • 进程

    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。进程是操作系统资源分配和处理器调度的基本单位,拥有独立的代码、内部数据和运行状态。系统运行一个程序即是一个进程从创建、运行到消亡的过程。简言之,一个进程就是一个正在执行中的程序,当程序在执行时,将会被操作系统载入内存中(占有内存空间),并且开始它的工作任务(执行的时候,就是占有CPU的时间),然后就变成了所谓的进程


  • 线程

进程是操作系统资源分配和处理器调度的基本单位,每一个进程的内部数据和状态是完全独立的,即使是同一个程序产生的不进程,也比如重复许多的数据复制工作,为了减少不必要的系统负担,因此产生了线程的概念。一个进程包含一个以上的线程,一个进程中的多个线程只能使用该进程的资源和环境。

所谓线程,与进程类似,也是一个执行中的程序,但线程是一个比进程更小的执行单位。也有产生、运行、消亡的过程,也是动态的概念

线程不能独立存在,必须存在于进程中,也被称为负担轻的进程


单线程

一个进程如果只有一条执行路径,则称为单线程程序


多线程

一个进程如果有多条执行路径,则称为多线程程序

启动线程的其实是

start()方法

中调用了

start0()方法

,用了代码模式



2.实现多线程的方式一:继承Thread类



实现步骤

定义一个类MyThread继承Thread类

在MyThread类中重写run()方法

创建MyThread类的对象

启动线程



实例代码:

public class Demo {
    public static void main(String[] args) {
//        创建可开启线程的对象
        Cat cat = new Cat();
        cat.start();//开启线程
    }
}



class Cat extends Thread {//继承Thread类, Thread类实现了Runnable接口
    int num = 0;

    //    重写Runnable接口中的run抽象方法
    @Override
    public void run() {//编写当前线程中的业务逻辑
//        线程中循环执行代码,可以设置线程挂起时间
        while (true) {
            System.out.println("喵喵" + (num++));
            if (num > 8) break;//线程结束条件
            try {
                Thread.sleep(1000);//线程挂起时间,1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

控制台输出:
"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=10020:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\代码练习\out\production\线程 Demo
喵喵0
喵喵1
喵喵2
喵喵3
喵喵4
喵喵5
喵喵6
喵喵7
喵喵8

Process finished with exit code 0



思考:



一、为什么要重写run()方法?

因为run()是用来封装被线程执行的代码



二、run()方法和start()方法的区别?

run():封装线程执行的代码,直接调用,相当于普通方法的调用

start():启动线程;调用了方法start0(),然后在内部由JVM调用此线程的run()方法,此run()方法中又调用了接口参数的run方法运行业务逻辑(设计模式中的

代理模式



3.实现多线程的方式二:手动实现Runnable接口



实现代码:

package demo3;

public class demo3 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();//创建实现了Runnable接口的对象
        Thread T = new Thread(tiger);//创建线程
        T.start();//开启子线程
    }
}



//实现Runnable接口,重写run方法
class Tiger implements Runnable{
    int cnt = 0;
//    写业务逻辑
    @Override
    public void run() {
        while(true){
            System.out.println("老虎嗷嗷叫" + (++cnt));
            try {
                Thread.sleep(1000);//线程睡眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(cnt == 10){//结束循环
                break;
            }
        }
    }
}

控制台输出:
  "C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=6849:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\代码练习\out\production\线程 demo3.demo3
老虎嗷嗷叫1
老虎嗷嗷叫2
老虎嗷嗷叫3
老虎嗷嗷叫4
老虎嗷嗷叫5
老虎嗷嗷叫6
老虎嗷嗷叫7
老虎嗷嗷叫8
老虎嗷嗷叫9
老虎嗷嗷叫10

Process finished with exit code 0



4.线程结束的方式:

线程结束的情况两种情况:

  • 当线程任务执行结束之后,会自动结束
  • 在线程中设置变量来控制线程中的run方法的循环执行,在其他线程中修改这个变量(

    通知方式



示例代码:

package demo2;

public class demo2 {
    public static void main(String[] args) {
        T t = new T();
        t.start();//在main线程中开启一个子线程T
        try {
            Thread.sleep(1000 * 5);//main线程沉睡5秒
            t.setLoop(false);//(发通知)设置子线程的循环变量,结束子线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


class T extends Thread {
    boolean loop = true;//控制当前线程是否结束的变量
    int cnt = 0;

    @Override
    public void run() {
        while (loop) {
            System.out.println("线程 " + Thread.currentThread().getName() + " " + (++cnt));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

  //修改器
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}



5.线程中断(非终止):



概念:

public void interrupt()
说明:
	当线程处于就绪状态或执行状态时,给该线程设置中断标志;
  一个 (正在执行的线程)(正在睡眠的线程) 调用 interrupt() 方法,则可导致 (睡眠线程) 发生 InterruptedException异常而唤醒自己,从而进入就绪状态



示例代码:

package demo4;

public class demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new T());//创建线程
        t.start();//开启子线程
        Thread.sleep(500);//主线程在子线程启动5秒后再唤醒子线程
        System.out.println("主线程" + Thread.currentThread().getName() + " 发送中断信号唤醒 子线程" + t.getName());
        t.interrupt();//在主线程中中断子线程,唤醒子线程
    }
}

class T implements Runnable {
    int cnt = 0;

    @Override
    public void run() {
        while (cnt < 5) {
            System.out.println(Thread.currentThread().getName() + "运行了" + (++cnt) + "次");
            try {
                System.out.println(Thread.currentThread().getName() + "开始睡眠");
                Thread.sleep(1000);//睡眠时间可以很长
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "产生中断异常,睡眠线程被唤醒,切换到就绪状态,继续运行");
            }
        }
    }
}


控制台输出:
  "C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=10048:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\代码练习\out\production\线程 demo4.demo4
Thread-0运行了1Thread-0开始睡眠
主线程main 发送中断信号唤醒 子线程Thread-0
Thread-0产生中断异常,睡眠线程被唤醒,切换到就绪状态,继续运行
Thread-0运行了2Thread-0开始睡眠
Thread-0运行了3Thread-0开始睡眠
Thread-0运行了4Thread-0开始睡眠
Thread-0运行了5Thread-0开始睡眠

Process finished with exit code 0



6.线程插队:



概念:

public final void join()
  暂停当前线程的执行,等待调用该方法的线程运行结束之后再继续执行本线程
次要:
  public static void yield()
  暂停当前线程的执行,但该线程仍处于就绪状态,不转为阻塞状态,只是让出执行权,与其他线程再次竞争



示例代码:

package deno5;

class T extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(getName() + ":" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadJoin {
    public static void main(String[] args) {
        T tj1 = new T();
        T tj2 = new T();
        T tj3 = new T();
        tj1.setName("康熙");
        tj2.setName("四阿哥");
        tj3.setName("八阿哥");
        tj1.start();
        try {
            tj1.join();//暂停主线程,等子线程tj1执行完再继续主线程下面的任务
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj2.start();
        tj3.start();

    }
}

"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=10750:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\代码练习\out\production\线程 deno5.ThreadJoin
康熙:0
康熙:1
康熙:2
四阿哥:0
八阿哥:0
八阿哥:1
四阿哥:1
四阿哥:2
八阿哥:2

Process finished with exit code 0



7.守护线程(后台线程):



概念:

  • 用户线程:也叫工作线程,通常为 任务完成自动结束 和 其他线程通知方式结束
  • 守护线程:一般是为工作线程服务,当在守护线程中开启的所有的用户线程结束时,守护线程自动结束

常见的守护线程:垃圾回收机制

正常来说,在主线程中开启子线程,主线程结束运行之后,子线程不会因为主线程的结束而结束,如果子线程在被开启之前被设置成了守护线程,则子线程会因为被其守护的线程的结束而结束



示例代码:

package demo6;

public class ThreadDaemonDemo {
    public static void main(String[] args) throws InterruptedException {
        DaemonThread daemonThread = new DaemonThread();
        daemonThread.setDaemon(true);//将子线程设置成mian线程的守护线程
        daemonThread.start();//开启守护线程
        for (int i = 0; i < 4; i++) {
            System.out.println(Thread.currentThread().getName() + "执行了" + i + "次");
            Thread.sleep(1000);//main线程睡眠1秒
        }
    }
}


class DaemonThread extends Thread {
    int cnt = 0;

    @Override
    public void run() {
        while (true) {//守护线程循环执行
            System.out.println(Thread.currentThread().getName() + "执行了" + (++cnt) + "次");
            try {
                Thread.sleep(1000);//守护线程睡眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=9210:D:\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\IDEA_WorkSpace\代码练习\out\production\线程 demo6.ThreadDaemonDemo
main执行了0Thread-0执行了1Thread-0执行了2次
main执行了1次
main执行了2Thread-0执行了3次
main执行了3Thread-0执行了4Thread-0执行了5Process finished with exit code 0



8.线程的几种状态:



说法一:五种状态

image-20220222230012616



说法二:七种状态

JDK中用Thread.State枚举表示了线程的几种状态

image-20220222230328519

image-20220222230250508



9.线程同步:



问题思考:

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境
  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
    • Java提供了同步代码块的方式来解决
  • 同步的好处和弊端

    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率



方法一:同步代码块

synchronized(任意对象) { 多条语句操作共享数据的代码 }
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) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }//窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售 第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}



方法二:同步方法

修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }
public class SellTicket implements Runnable {
    private static int tickets = 100;

    private int x = 0;

    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    } 

  	// 同步方法 
    private synchronized void sellTicket() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
            tickets--;
        }
    }

    // 静态同步方法 
    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--;
        }
    }
}
  • 同步非静态方法的锁对象是什么呢?

    • this对象
  • 同步静态方法的锁对象是什么呢?

    • 类名.class



释放锁:


  • 释放锁

    的情况:

    • 当前线程的同步代码块、同步方法

      任务执行结束
    • 当前线程在同步代码块、同步方法中

      遇到break、return
    • 当前线程在同步代码块、同步方法中出现了未处理的

      Error或Exception,导致异常结束
    • 当前线程在同步代码块、同步方法中执行了线程对象的==wait()==方法、当前线程暂停,并释放锁

  • 不会释放锁

    的情况:

    • 当前线程在同步代码块、同步方法时,程序调用了==Thread.sleep()、Thread.yield()==方法暂停当前线程的执行,不会释放锁
    • 当前线程在同步代码块时,其他线程调用了该线程的==suspend()==方法将该线程挂起,该线程不会释放锁



Lock锁:

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
  
  方法名 									说明
ReentrantLock() 			创建一个ReentrantLock的实例
  
  
加锁解锁方法
  方法名 								说明
void lock() 					获得锁
void unlock() 				释放锁


代码示例:
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();
            }
        }
    }
}



10.死锁:

多个线程都占用了对方的锁资源,互不相让,导致死锁



代码示例:

package com.hspedu.syn;

/**
 * @author 韩顺平
 * @version 1.0
 * 模拟线程死锁
 */
public class DeadLock_ {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();
    }
}


//线程
class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }

    @Override
    public void run() {

        //下面业务逻辑的分析
        //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
        //4. 如果线程B 得不到 o1 对象锁,就会Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁, 下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) { // 这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入2");
                }
                
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) { // 这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入4");
                }
            }
        }
    }
}



11.生产者消费者模式:



概述:

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的

理解更加深刻。

所谓生产者消费者问题,实际上主要是包含了两类线程:

一类是生产者线程用于生产数据

一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

image-20220223161128369

方法名 											说明
void wait() 			导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify() 		唤醒正在等待对象监视器的单个线程
void notifyAll() 	唤醒正在等待对象监视器的所有线程



案例:

生产者消费者案例中包含的类:

奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作

生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作

消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作

测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下

①创建奶箱对象,这是共享数据区域

②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作

③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作

④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递

⑤启动线程



商品Box:
package demo7;

public class Box {
    //    代表第几瓶牛奶
    private int milk;

    private boolean state = false;//标记是否有牛奶

    //    拿走牛奶
    public synchronized void get() {
        if (!state) {//如果没有牛奶,则挂起线程等待生产
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("消费者拿走了第 " + milk + " 瓶牛奶");
        state = false;
        notifyAll();//唤醒其他所有睡眠的线程
    }

    //    放入牛奶
    public synchronized void put(int i) {
        if (state) {//如果有牛奶,则挂起线程等待消费
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        milk = i;
        System.out.println("生产者放入了第 " + milk + " 瓶牛奶");

        state = true;
        notifyAll();//唤醒其他所有睡眠的线程
    }
}


生产者Producer:
package demo7;

//生产者
public class Producer implements Runnable{
    private Box box;

    public Producer(Box box){
        this.box = box;
    }

    @Override
    public void run() {//生产者生产牛奶
        for(int i = 0; i < 10; i++){
            box.put(i);
        }
    }
}


消费者Customer:
package demo7;

//消费者
public class Customer implements Runnable{
    private Box box;

    public Customer(Box box){
        this.box = box;
    }

    @Override
    public void run() {
        while(true){//消费者一直拿牛奶
            box.get();
        }
    }
}


测试类:
package demo7;

public class test {
    public static void main(String[] args) {
        Box box = new Box();
        Customer customer = new Customer(box);
        Producer producer = new Producer(box);

        Thread t1 = new Thread(customer);
        Thread t2 = new Thread(producer);
        t1.start();
        t2.start();
    }
}



版权声明:本文为Hunter_Kevin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。