JavaSE进阶笔记(持续更新……)

发布于:2023-01-18 ⋅ 阅读:(974) ⋅ 点赞:(0)

Lambda表达式

Lambda表达式可以理解为匿名内部类的简化,是一种函数式编程思想,使用Lambda表达式的前提是:

  • 有一个接口
  • 接口中只有一个抽象方法

格式:

()->{}//小括号()表示抽象方法的小括号,大括号{}里面重写接口的抽象方法

举例:

有参无返回值

public class Demo{
    public static void main(String[] args){
        start(("字符串参数")->{System.out.println(s)});//Lambda表达式本质就是接口的实现类对象重写接口的抽象方法
    }
    public static void start(Inter i){
        i.method("字符串参数")
    }
}
interface Inter{
    void method(String s);
}

无参有返回值

public class Demo{
    public static void main(String[] args){
        start(()->{return 1;});
    }
    public static void start(Inter i){
        int num=i.method();
        System.out.println(num);
    }
}
interface Inter{
    int method();
}

有参有返回值

public class Demo{
    public static void main(String[] args){
        start((int a,int b,int c)->{return a+b+c;});
    }
    public static void start(Inter i){
        int sum=i.method(1,2,3);
        System.out.println(sum);
    }
}
interface Inter{
    int method(int a,int b,int c);
}

关于省略

  • 参数类型可以省略,要省略就全省略
  • 参数只有一个时,小括号可以省略
  • 方法体只有一句时,大括号可以省略
  • 当只有一句代码,且为返回值,可以省略return、分号和大括号

代码块

局部代码块:

定义在方法内部,用大括号将一段代码括起来,用于控制变量的生命周期

构造代码块:

定义在构造方法之前,用于各个构造方法中共有的属性的初始化

静态代码块:

定义在成员变量处,用于解决重复创建对象导致的代码逻辑错误

常用API

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)

System

  • System.exit():退出虚拟机

  • System.currentTimeMillis() 返回当前时间 从1970年开始到现在的毫秒数

  • System.arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数);//复制数组

 int[] arr1={1,2,3,4,5,6,7,8};
 int[] arr2=new int[10];
 System.arraycopy(arr1,2,arr2,3,2);
 //从arr1的下标2处开始复制2个数,复制到arr2中从下标3处开始粘贴

Object

  • toString:是Object中的方法,任何一个类都最终继承自Object,所以当打印类的对象时输出的是toString的返回值,可以在类中重写toString方法,打印自己想要的内容
  • equals:在Object中是用==实现的,要想比较内容需要在类中重写equals方法

BigDecimal

用于对小数进行精确计算,在java中小数运算是不精确的,例如0.1+0.2运行结果是0.30000000000004。

若要进行精确计算,创建BigDecimal类的对象时使用参数为String类型的构造方法。

小数为什么不精准?

常用方法:

方法名 说明
public BigDecimal add(另一个BigDecimal对象) 加法
public BigDecimal subtract (另一个BigDecimal对象) 减法
public BigDecimal multiply (另一个BigDecimal对象) 乘法
public BigDecimal divide (另一个BigDecimal对象) 除法
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) 除法

二分查找

前提是数组有序,定义min、max、mid,依次和中间比较

包装类

将int、short、char等基本数据类型包装成引用数据类型,类中包含对数据的一些功能操作的方法

基本类型-------------引用类型

byte--------------------Byte

short-------------------Short

int-----------------------Integer

long---------------------Long

float---------------------Float

double------------------Double

char---------------------Character

boolean----------------Boolean

以Integer为例:

创建对象

使用构造方法创建对象或使用valueOf方法

//使用构造方法创建对象
Integer i1=new Integer(100);//参数为int类型的构造方法
Integer i2=new Integer("100");//参数为String类型的构造方法
System.out.println(i1);
System.out.println(i2);
//使用Integer类中的valueOf静态方法创建对象
Integer i3=Integer.valueOf(200);//参数为int类型
Integer i4=Integer.valueOf("200");//参数为String类型
System.out.println(i3);
System.out.println(i4);
//当数值在-128到127之间时,使用valueOf进行初始化只有一个对象
System.out.println(i1==i2);//false
System.out.println(i3==i4);//true

装箱和拆箱

装箱是指基本类型转为引用类型,拆箱是指引用类型转为基本类型,java中提供自动拆箱和装箱

int a=10;
Integer i1=a;//自动装箱
int b=i1;//自动拆箱
//例子
Integer num=200;
num=num+300;//运算过程:1、Integer类型的num自动拆箱为int类型的num和300相加得500
                    //2、500自动装箱赋值给Integer类型的num
                    //3、
System.out.println(num);//500

String转int

Integer类中的parseInt静态方法

String s="171";
int i5=Integer.parseInt(s);
System.out.println(i1);
int i6=Integer.parseInt("200");
System.out.println(i6);
……………………………………………………………………………………
比较:
int i6=Integer.parseInt("200");//将字符串转为int数据
Integer i4=Integer.valueOf("200");//以字符串创建Integer对象

int转String

在数据后加“ ”或者使用String类的valueOf方法

int i1=123;
String s1=i1+"";
int i2=456;
String s2=String.valueOf(i2);

Arrays数组的工具类

  • Arrays.toString():返回指定数组的内容的字符串表示形式

  • Arrays.sort():对数组进行排序

    数组长度[1,47):插入排序

    数组长度[47,286):快速排序

    数组长度[286,无穷):归并排序(有一定顺序)或快速排序(毫无顺序)

  • Arrays.binarySearch():二分查找返回指定元素的索引

Date时间相关API

Date代表一个特定的时间,精确到毫秒,从计算机原点时间1970年1月1日 00:00:00开始

构造方法

方法名 说明
public Date() 分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒
public Date(long date) 分配一个 Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数
Date d1=new Date();//d1获取当前时间
System.out.println(d1);
Date d2=new Date(36000);//d2获取从计算机原点时间开始经过参数毫秒后的时间
System.out.println(d2);

常用方法

方法名 说明
public long getTime() 获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值
public void setTime(long time) 设置时间,给的是毫秒值
Date d3=new Date();
System.out.println(d3.getTime());//获得从计算机原点到当前时间之间的毫秒值
d3.setTime(36000000);
System.out.println(d3);//使用setTime传入毫秒值,打印出从计算机原点经过指定毫秒值后的时间

SimpleDateFormat类

用于对于日期进行格式化和解析

构造方法

方法名 说明
public SimpleDateFormat() 构造一个SimpleDateFormat,使用默认模式和日期格式
public SimpleDateFormat(String pattern) 构造一个SimpleDateFormat使用给定的模式和默认的日期格式

常用方法

方法名 说明
public final String format(Date date) 将日期格式化成日期/时间字符串
public Date parse(String source) 从给定字符串的开始解析文本以生成日期
Date d=new Date();//获取当前时间
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");//参数为指定格式
String s=simpleDateFormat.format(d);//format方法将Date转化为字符串形式
System.out.println("……………………………………………………");
String s1="2023年6月9日";
SimpleDateFormat simpleDateFormat1=new SimpleDateFormat("yyyy年MM月dd日");
Date d1=simpleDateFormat1.parse(s1);//parse方法将字符串转化为对应的日期格式

冒泡排序

递归

直接调用自己或者通过一系列语句间接调用自己的方法叫做递归方法

举例:阶乘

public static void main(String[] args) {
    System.out.println(fact(5));
}
public static int fact(int num){//递归方法,参数num为5,返回5的阶乘
    if(num==1){
        return num;//直到参数为1,递归结束,必须有这个条件,否则会死循环
    }else{
        return num*fact(num-1);//在fact中调用自己,参数num-1为4,返回4的阶乘
    }
}

集合

集合是一种长度可变的存储数据的容器,它只能存储引用类型,若要存储基本类型,需要存储它们的包装类

Collection(单列集合)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKKlg6WJ-1660454684268)(D:\黑马程序员\就业班\day05\assets\1660208308928.png)]

常用方法

方法名 说明
boolean add(E e) 添加元素
boolean remove(Object o) 从集合中移除指定的元素
boolean removeIf(Predicate<? super E>filter) 根据条件进行移除
void clear() 清空集合中的元素
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数
Collection<String> cl=new ArrayList<>();
cl.add("aa");
cl.add("bbd");
cl.add("sd");
cl.add("awr");
cl.add("ewee");
cl.add("cc");//添加元素
cl.remove("aa");//删除指定元素,若有多个相同的元素,则只删除第一个元素
System.out.println(cl);
cl.removeIf(new Predicate<String>() {
       @Override
       public boolean test(String s) {
       return s.length()==3;
    }
});//删除符合条件的元素
System.out.println(cl);
System.out.println(cl.contains("cc"));//判断是否含有指定元素
System.out.println(cl.isEmpty());//判断集合是否为空
System.out.println(cl.size());//集合的长度
cl.clear();//清空集合

List

存取是有序的,且可以存储重复的数据,有索引

特有方法

方法名 描述
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
public class Demo1 {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        System.out.println(list);
        list.add(1,"A");//在索引1处插入元素A,其余元素依次后移
        System.out.println(list);
        System.out.println(list.remove(0));//删除索引0处的元素,并将该元素返回
        System.out.println(list);
        list.set(1,"B");//将索引1处的元素修改为B
        System.out.println(list);
        System.out.println(list.get(3));//返回索引为3处的元素
    }
}

ArrayList

底层原理是数组实现的

LinkedList

底层原理是链表实现的

特有方法

方法名 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
public class Demo2 {
    public static void main(String[] args) {
        LinkedList<String> linkedList=new LinkedList<>();
        linkedList.add("a");
        linkedList.add("b");
        linkedList.add("c");
        linkedList.add("d");
        linkedList.add("e");
        System.out.println(linkedList);
        linkedList.addFirst("A");//在列表开头插入指定元素A
        System.out.println(linkedList);
        linkedList.addLast("Z");//在列表结尾插入指定元素Z
        System.out.println(linkedList);
        System.out.println(linkedList.getFirst());//返回第一个元素
        System.out.println(linkedList.getLast());//返回最后一个元素
        linkedList.removeFirst();//删除第一个元素
        linkedList.removeLast();//删除最后一个元素
        System.out.println(linkedList);
    }
}

Set

存取是无序的,不可以存储重复的数据,没有索引,不可以使用普通的for循环遍历

TreeSet

底层原理是二叉树实现的,在添加数据时会自动给数据进行排序,有两种排序方式,分别是自然排序和比较器排序,当两种排序都存在时,优先采用比较器排序

自然排序:

数据类要实现Comparable接口,并重写compareTo方法

比较器排序(Comparator):

在创建TreeSet集合对象时利用构造方法传入比较器对象,比较器对象通过Lambda表达式或匿名内部类创建,重写compareTo方法

返回值规则:

compareTo返回值为int类型

  • 如果返回值是负数,表示当前存入的元素是较小值,存在左边
  • 如果返回值是0,表示当前存入的元素与集合中的元素相等,不存入
  • 如果返回值是正数,表示当前存入的元素是较大值,存在右边
public class Test {
    public static void main(String[] args) {
        Set<Student> s=new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {//o1是集合中存在的元素,o2是当前存入的元素
                int result=o1.age-o2.age;
                return result == 0 ? o1.name.compareTo(o2.name) : result;
            }
        });
        s.add(new Student("zhangsan",11));
        s.add(new Student("xiaowang",23));
        s.add(new Student("shihao",143));
        s.add(new Student("tiandi",122));
        s.add(new Student("yewuque",12));
        s.add(new Student("huangtiandi",11));
        System.out.println(s);
    }
}
public class Student implements Comparable<Student>{//实现Comparable接口,注意泛型
    String name;
    int age;

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

    public Student() {
    }

    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 o) {//重写compareTo方法,参数是当前存入的元素
        int result=this.age-o.age;//this是集合中已经存在的元素
        return result == 0 ? this.name.compareTo(o.name) : result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

HashSet

底层原理是哈希表实现的,存储顺序和输入顺序不一致,但它不是和TreeSet一样对数据排序,而是在存储数据时根据数据的哈希值算出要存的索引位置,默认使用的是类对象的哈希值,因此在例子将Student对象添加到集合中,即使学生对象的姓名、年龄相同,也会全都存储到集合中,因为对象哈希值是不一样的,若要求姓名和年龄相同的学生是同一个人不存储,则在学生类中重写hashcold方法,让hashcode根据name和age来计算,保证学生的姓名和年龄相同时,计算的hash值一致,equals也比较name和age,当学生的姓名和年龄相同时,认为是同一个对象

public class Test {
    public static void main(String[] args) {
        HashSet<Student> h=new HashSet<>();
        h.add(new Student("叶凡",180));
        h.add(new Student("石昊",999));
        h.add(new Student("黑皇",1000));
        h.add(new Student("叶凡",180));
        h.add(new Student("伊莱克斯",18));
        h.add(new Student("徐缺",180));
        System.out.println(h);
    }
}
public class Student {
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Student() {
    }

    @Override
    public String toString() {
        return "name='" + name + '\'' +
                ", 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;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Map(双列集合)

Map是一个集合接口,一个键(key)对应一个值(value)组成键值对,其中键具有唯一性,不可以重复,值可以重复

常用方法

方法名 说明
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
public class Demo1 {
    public static void main(String[] args) {
        Map<String,Integer> map=new HashMap<>();
        map.put("叶无缺",15);
        map.put("君山烈",15);
        map.put("八神真一",220);
        map.put("叶峥嵘",50);
        map.put("空",1000);
        map.put("叶琅琊",30);
        System.out.println(map);//Map底层的存储结构使用哈希表实现的,用key的哈希值计算存储的索引位置
        map.put("叶无缺",22);//添加键相同的键值对对象时,会对集合中存在的该键所对应的值进行修改
        System.out.println(map);
        map.remove("君山烈");//根据键删除键值对元素
        System.out.println(map);
        System.out.println(map.containsKey("叶琅琊"));//判断指定键是否存在,返回值为布尔型
        System.out.println(map.containsValue(11));//判断指定值是否存在,返回值为布尔型
        System.out.println(map.isEmpty());
        System.out.println(map.size());
        map.clear();
        System.out.println(map.size());
    }
}

获取数据的方法

方法名 说明
V get(Object key) 根据键获取值
Set keySet() 获取所有键的集合
Collection values() 获取所有值的集合
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
public class Demo2 {
    public static void main(String[] args) {
        Map<String,Integer> map=new HashMap<>();
        map.put("叶无缺",15);
        map.put("君山烈",15);
        map.put("八神真一",220);
        map.put("叶峥嵘",50);
        map.put("空",1000);
        map.put("叶琅琊",30);
        System.out.println(map.get("叶无缺"));//根据键返回其对应的值
        Set<String> set=map.keySet();//获取所有键的集合
        System.out.println(set);
        Collection<Integer> collection=map.values();//获取所有值得集合
        System.out.println(collection);
        Set<Map.Entry<String,Integer>> set1=map.entrySet();//获取所有键值对对象的集合
        System.out.println(set1);
    }
}

遍历方式

public class Demo3 {
    public static void main(String[] args) {
        Map<String,Integer> map=new HashMap<>();
        map.put("叶无缺",15);
        map.put("君山烈",15);
        map.put("八神真一",220);
        map.put("叶峥嵘",50);
        map.put("空",1000);
        map.put("叶琅琊",30);
 方法一://获取键的集合(keySet),对集合用增强for循环,根据键获取值(get(Object key))
        Set<String> set=map.keySet();
        for (String s : set) {
            System.out.println(s+"--"+map.get(s));
        }
 方法二://获取键值对对象的集合,再遍历集合输出
        Set<Map.Entry<String,Integer>> set1=map.entrySet();
        System.out.println(set1);
        for (Map.Entry<String, Integer> stringIntegerEntry : set1) {
            System.out.println(stringIntegerEntry.getKey()+"--"+stringIntegerEntry.getValue());
        }
 方法三: //forEach循环
        map.forEach(new BiConsumer<String, Integer>() {
            @Override
            public void accept(String s, Integer integer) {
                System.out.println(s+"--"+integer);
            }
        });
    }
}

HashMap

迭代(遍历集合)

使用集合对象的iterator()方法获取迭代器对象

常用方法

方法名 说明
hasNext() 判断当前迭代器指向的集合的位置是否有数据
next() 返回当前迭代器指向的数据,并将迭代器指向下一位置
remove() 删除迭代器上一次调用next()的数据
ArrayList<String> al=new ArrayList<>();
al.add("aa");
al.add("aa");
al.add("bb");
al.add("aa");
al.add("aa");
al.add("cc");
//获取迭代器对象
Iterator<String> it=al.iterator();
//删除所有的aa
while (it.hasNext()){//当当前迭代器指向位置有数据时循环
    if(it.next()=="aa"){//取出迭代器当前指向数据,与"aa"比较,迭代器指向下一位置
        it.remove();//删除使用it.next()获得的数据
    }
}

增强for循环

增强for循环地底层原理是迭代器,因此无论是增强for循环还是迭代器,只要实现了Iterable接口的类(容器)才可以使用

格式

for(集合/数组中元素的数据类型 变量名 :  集合/数组名) {
	// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
}

泛型

泛型通常用尖括号里的大写字母如E T K V S U等表示,在使用时传入具体类型

在类中定义泛型

格式: 修饰符 class 类名<代表泛型的变量> { }

在类中可以定义多个泛型,中间用逗号隔开

public class Test3 {
    public static void main(String[] args) {
        Student<String,Integer> student1=new Student<>();
        student1.setName("迪丽热巴");//String
        student1.setAge(22);//int
        Student<String,String> student2=new Student<>();
        student2.setName("古力娜扎");//String
        student2.setAge("21");//String
    }
}
class Student<E,T>{
    E name;
    T age;

    public Student(E name, T age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public E getName() {
        return name;
    }

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

    public T getAge() {
        return age;
    }

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

在方法中定义泛型(极少用)

格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

在接口中定义泛型

格式:修饰符 interface 接口名<代表泛型的变量> { }
public class Test4 {
    public static void main(String[] args) {

    }
}
interface Inter<T>{     //定义接口的泛型T,show方法中使用T定义了参数t
    void show(T t,int i);
}
class C1 implements Inter<String>{      //在实现接口时具体确定了泛型类型,使用类对象调用show方法传入该类型的参数

    @Override
    public void show(String s, int i) {
        System.out.println(s+"---"+i);
    }
}
class C2<E> implements Inter<E>{       //定义类的泛型E,接口使用类C2的泛型E

    @Override
    public void show(E e, int i) {     //通过接口简介使用泛型E,本质上和接口的泛型保持一致
        System.out.println(e+"---"+i);
    }
    public void method(E e){
        System.out.println(e);      //只是使用类的泛型
    }
}


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

在方法中定义泛型(极少用)

格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

在接口中定义泛型

格式:修饰符 interface 接口名<代表泛型的变量> { }
public class Test4 {
    public static void main(String[] args) {

    }
}
interface Inter<T>{     //定义接口的泛型T,show方法中使用T定义了参数t
    void show(T t,int i);
}
class C1 implements Inter<String>{      //在实现接口时具体确定了泛型类型,使用类对象调用show方法传入该类型的参数

    @Override
    public void show(String s, int i) {
        System.out.println(s+"---"+i);
    }
}
class C2<E> implements Inter<E>{       //定义类的泛型E,接口使用类C2的泛型E

    @Override
    public void show(E e, int i) {     //通过接口简介使用泛型E,本质上和接口的泛型保持一致
        System.out.println(e+"---"+i);
    }
    public void method(E e){
        System.out.println(e);      //只是使用类的泛型
    }
}

可变参数

是定义方法参数的一种方式,参数类型确定,参数个数不确定

格式

  修饰符 返回值类型 方法名(数据类型… 变量名) {  }

注意

  • 可变参数本质是数组
  • 若一个方法有多个参数,包含可变参数,则可变参数要放在最后

应用场景:创建不可变集合

 //代码演示

Stream流

Stream是一个接口,该接口表示流

获取Stream流

  • 通过Collection集合调用stream()方法获取
 ArrayList<String> arrayList=new ArrayList<>();
  Stream<String> stream=arrayList.stream();
  HashMap<String,Integer> hashMap=new HashMap<>();
  Set<String> set=hashMap.keySet();//对于Map双列集合,先获取key集合,再通过该集合调用stream()方法获取流
  Stream<String> stream1=set.stream();
  • 通过Arrays中的静态方法和Stream接口中的静态方法获取
 //根据数组获取
  int[] arr={1,2,3,4,5,6,7};
  IntStream stream=Arrays.stream(arr);
  stream.forEach(s-> System.out.println(s));
  //根据多个元素获取	本质还是数组变流
  IntStream stream1= IntStream.of(5,4,3,2,1);
  stream1.forEach((s)->System.out.println(s));

Stream的中间操作

方法名 说明
Stream filter(Predicate predicate) 用于对流中的数据进行过滤
Stream limit(long maxSize) 返回流中最前面 指定参数个数的数据组成的流
Stream skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
Stream distinct() 返回 去掉流数据中 重复的元素后剩余数据组成的流
  //代码演示

终结Stream流

执行此操作后,就相当于这个流结束了,不能再进行其他操作

方法名 说明
void forEach(Consumer action) 对此流的每个元素执行操作
long count() 返回此流中的元素数
   //代码演示

collect收集流

collect获取流中剩余的元素,但它不提供容器,也不将数据添加到容器中,而是Collectors提供了具体的收集方式

方法名 说明
public static Collector toList() 把元素收集到List集合中
public static Collector toSet() 把元素收集到Set集合中
public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中
  //代码演示

异常

异常是指在代码执行过程中JVM虚拟机遇到了不能执行的代码,异常处理是指通过异常处理语句,使得当遇到JVM无法处理的代码时,跳过这段代码并执行下面的语句使程序可以继续运行

在写代码时,编译正常,也就是说在语法上没有错,但运行时程序中断,这时就出现了异常,使用try-catch捕获异常,例如

  public class Demo {
        public static void main(String[] args) {
            Scanner sc=new Scanner(System.in);
            try {
                int a=sc.nextInt();//要求输入整数,当输入其他时,会产生输入异常,执行catch中的语句
                System.out.println(a);
            }catch (InputMismatchException e){
                System.out.println("输入有误");
            }
            System.out.println("后续代码");
        }
    }

try-catch捕获异常

throws声明异常

throw抛出异常

File

构造方法

方法名 说明
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的File实例
File(File parent, String child) 从父文件对象和子路径名字符串创建新的File实例
 String path="D:\\黑马程序员"+File.separator+"就业班\\笔记.md";
    File file=new File(path);//方法一,参数传入抽象路径名path,变量path为路径的字符串表示
    System.out.println(file);
    ------------------------------------------------------
    String path1="D:\\黑马程序员\\就业班";
    String path2="笔记.md";
    File file1=new File(path1,path2);//方法二,参数为父路径字符串和子路径字符串
    System.out.println(file1);
    ------------------------------------------------------
    File file2=new File("D:\\黑马程序员\\就业班");
    String path3="笔记.md";
    File file3=new File(file2,path3);//方法三,参数为父路径对象和子路径字符串
    System.out.println(file);

绝对路径和相对路径

绝对路径是从盘符开始,一个文件的完整路径;相对路径在IDEA中是从工程的根目录开始的路径,在一般情况下是相对于当前文件夹

路径分隔符

在Windows中以\作为分隔符,在java中因为\表示转义,因此使用两个\为分隔符,在Java中还可以使用/作为分隔符,还能用File.separator 表示分隔符

  File file1=new File("C/a/b/c");
    File file2=new File("C\\a\\b\\c");
    File file3=new File("C"+File.separator+"a"+File.separator+"b"+File.separator+"c");

File创建

方法名 说明
public boolean createNewFile() 当该名称的文件不存在时,创建名为该名称的新文件
public boolean mkdir() 创建由此抽象路径名命名的目录(文件夹)
public boolean mkdirs() 创建由此抽象路径名命名的目录(文件夹),可一个可多个
 File file4=new File("a.txt");
    file4.createNewFile();
    File file5=new File("mymodule/a.txt");
    file5.createNewFile();//返回布尔值,如果文件存在,再次创建就会失败,返回false;如果文件目录不存在,报IO异常
    File file6=new File("aa");
    System.out.println(file6.mkdir());//如果文件存在,创建失败,返回false;mkdir只能创建一级目录
    File file7=new File("aa/bb/cc");
    file7.mkdirs();//mkdirs可以创建一级也可以创建多级目录,因此常用mkdirs
    -----------------------------------------------------------
    - createNewFile  创建的是普通文件,和后缀名是什么无关
    - mkdir和mkdirs  创建的是文件夹  和后缀名无关
    File file8=new File("aa/bb/dd.txt");
    file8.mkdirs();
    -----------------------------------------------------------
    file4.delete();//删除了a.txt
    file7.delete();//删除了cc文件夹
    System.out.println(file6.delete());//删除aa文件夹失败,只能删除空文件夹,文件夹下不能包含文件夹或文件
    -----------------------------------------------------------
    System.out.println(f.isDirectory());//是否为目录
    System.out.println(f.isFile());//是否为文件
    System.out.println(f.exists());//是否存在
    -----------------------------------------------------------
    System.out.println(f.getAbsolutePath());//绝对路径
    System.out.println(f.getPath());//相对路径
    System.out.println(f.getName());//文件名
    File[] files=f.listFiles();//获取文件数组,包含该文件夹下所有文件对象

File删除(例子在上面)

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

File判断(例子在上面)

方法名 说明
public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
public boolean isFile() 测试此抽象路径名表示的File是否为文件
public boolean exists() 测试此抽象路径名表示的File是否存在

File获取

方法名 说明
public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
public String getPath() 将此抽象路径名转换为路径名字符串
public String getName() 返回由此抽象路径名表示的文件或目录的名称
public File[] listFiles() 返回此抽象路径名表示的目录中的文件和目录的File对象数组

递归删除

解决delete()方法只能删除空文件夹的弊端,用于删除非空文件夹

  public class Test2 {
        public static void main(String[] args) {
            File file=new File("aa");
            del(file);
        }
        public static void del(File file){//传入要删除的文件夹
            File[] files=file.listFiles();//获取该文件夹下的所有文件对象
            for (File file1 : files) {//遍历每一个文件对象
                if(file1.isFile()){//如果是文件(不是文件夹),删除
                    file1.delete();
                }else {//否则,如果是文件夹(非空),递归调用del方法
                    del(file1);
                }
            }
            file.delete();//当删除文件夹下所有内容,该文件夹为空,就可以删除该文件夹了
        }
    }

递归练习:计算文件夹大小

 public class Test3 {
        public static void main(String[] args) {
            File file=new File("D:\\黑马程序员\\就业班\\xx1");
            System.out.println(getSize(file));
        }
        public static long getSize(File file){//参数是要计算大小的文件夹
            File[] files=file.listFiles();//获取文件夹目录(文件夹下文件对象的数组)
            long size=0;//定义初始大小0
            for (File file1 : files) {//循环遍历文件对象
                if(file1.isFile()){//如果是文件,计算大小加到size
                    size+=file1.length();
                }else {//否则,是文件夹,迭代调用getSize方法计算大小,加到size
                    size+=getSize(file1);
                }
            }
            return size;//返回最终计算的大小
    
        }
    }

递归练习:统计文件夹中各文件类型的数量

  public class Test4 {
        public static void main(String[] args) {
            HashMap<String,Integer> map=new HashMap<>();
            File file=new File("D:\\黑马程序员\\就业班\\xx1");
            getTypeNumber(map,file);
        }
        public static void getTypeNumber(Map<String,Integer> map,File file){
    
            File[] files=file.listFiles();
            for (File file1 : files) {
                if(file1.isFile()){//如果是文件,获取类型,加到map集合
                    String f=file1.getName();//获取文件名
                    String[] str=f.split("\\.");//点表示特殊含义,用\转义,\又用\转义   以点分割文件名
                    String end=str[1];//获取后缀名
                    Integer i=map.get(end);//获取后缀名的数量
                    if(i==null){//若为空,则从没有存过该后缀名
                        map.put(end,1);//将其添加到集合,数量为1
                    }else {
                        map.put(end,i+1);//否则,数量加1
                    }
                }else {//如果是文件夹,则递归调用getTypeNumber方法
                    getTypeNumber(map,file1);
                }
            }
            map.forEach((s, integer) -> {//最后输出
                System.out.println(s+"--"+integer);
            });
        }
    }

IO流

站在内存的角度,java程序在内存中

输入流:InputStream,Reader,数据从硬盘到内存,从硬盘读数据到内存

输出流:OutputStream,Writer,数据从内存到硬盘,从内存写数据到硬盘

字节流:以字节为单位读写数据,InputStream和OutputStream,常用与图片、音视频等

字符流:以字符为单位读写数据,Reader和Writer,常用语中文文本等

InputStream和OutputStream是抽象类,是所有字节输入输出的超类,对应的实用子类是FileInputStream和FileOutputStream

FileOutputStream(字节输出流)

使用FileOutputStream的步骤

  • 创建字节输出流的对象
  • 调用字节输出流的写数据方法
  • 结束流

构造方法(创建字节输出流对象)

方法名 说明
public FileOutputStream(File file) 创建文件输出流,参数为要写入的文件对象
public FileOutputStream(String name) 创建文件输出流,参数为要写入的文件的路径
 //创建字节输出流
    File f=new File("aa");
    FileOutputStream fos1=new FileOutputStream(f);//以参数为文件对象的方式创建
    FileOutputStream fos2=new FileOutputStream("aa");//以参数为文件路径的方式创建

写数据的方法

方法名 说明
void write(int b) 将指定的字节写入此文件输出流 一次写一个字节数据
void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据
void write(byte[] b, int off, int len) 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据
fos.write(“字符串”.getBytes()) 写入字符串,getBytes()方法将字符串转化成字节数组
fos.write(“\r\n”.getBytes()) 换行
 public class Test1 {
        public static void main(String[] args){
            try (FileOutputStream fos = new FileOutputStream("mymodule/a.txt",true)) {
                //每次创建新流,都会先将内容清空,如果文件不存在,会自动创建
                //若append设为true,则不会清空内容,而是追加
                fos.write(111);//如果是整数,实际存到里面的是该数对应的ASCII字符
                fos.write(new byte[]{44, 5, 64, 34, 54,});
                fos.write("你好".getBytes());//把字符串转换成字节数组,可以输出字符串
                fos.write("\r\n".getBytes());//换行
                byte[] b = {23, 69, 77, 78, 79, 88, 87};
                fos.write(b, 2, 3);//偏移2个即跳过两个,从第三个开始,输出长度3
                fos.close();//不关闭流,那么在其他地方不能操作该文件
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

异常处理

使用try-catch-finally方法

 public class Test4 {
        public static void main(String[] args) {
            FileOutputStream fos=null;
            try{
                fos=new FileOutputStream("mymodule/a.txt");//①FileOutputStream出现IO异常
                fos.write(99);
            }catch (IOException e){//②捕获IO异常
                e.printStackTrace();
            }finally {//无论是否异常,最终都会执行finally中的语句,因此在finally中写字节输出流的关闭语句
                try {
                    if(fos!=null){//只有流不为空时才能执行关闭操作,否则也会出现异常,需要捕获
                        fos.close();
                    }
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    } 

try-with-resources的方式释放资源

将需要释放资源的对象放到try后面的小括号内创建,这样在使用完之后会自动释放,不需要再讲关闭流语句写到finally代码块中

  public class Test5 {
        public static void main(String[] args) {
            try (FileOutputStream fos=new FileOutputStream("aa.txt")){
                fos.write(101);
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

FileInputStream(字节输入流)

构造方法

方法名 说明
FileInputStream(File file) 创建输入流对象,参数为文件对象
FileInputStream(String name) 创建输入流对象,参数为文件路径

读数据方法

方法名 说明
public abstract int read() 从输入流读取数据的下一个字节
public int read(byte[] b) 从输入流中读取一些字节数,并将它们存储到字节数组 b中 ,返回读取的长度,如果没有数据,返回-1

文件复制

public class Test3 {
    public static void main(String[] args) {
        File file1=new File("C:\\Users\\27189\\Desktop\\音乐\\生而为人.mp3");
        File file2=new File("C:\\Users\\27189\\Desktop\\音乐\\生而为人副本.mp3");
        copy(file1,file2);
    }
    public static void copy(File file1,File file2){
            //创建f1读取源文件,创建f2向目标文件中写入读取的数据
        try (FileInputStream f1=new FileInputStream(file1);FileOutputStream f2=new FileOutputStream(file2);){
            //创建byte数组
            byte[] b=new byte[1024];
            //定义len记录读取长度
            int len=0;
            while ((len=f1.read(b))!=-1){//当len为-1时表示读取结束
                //将读取到的数据写到目标文件中
                f2.write(b);
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

字节缓冲流

在内存中有个8kb的缓冲数组

构造方法

方法名 说明
BufferedOutputStream(OutputStream out) 创建字节缓冲输出流对象
BufferedInputStream(InputStream in) 创建字节缓冲输入流对象

BufferedOutputStream

使用缓冲流写数据时,先把数据写到缓冲数组中,在符合条件下才会写到硬盘中

  • 缓冲数组中数据满8kb,自动写入到硬盘
  • 使用flash方法刷新主动写入到硬盘
  • close关闭流,close方法中调用了flash方法,写入到硬盘
  • 使用try-catch捕获时自动写入到硬盘

BufferedInputStream

使用缓冲流读数据时,从硬盘中读8kb的数据到缓冲数组,然后每次从数组里读数据,直到将数组中的数据读完,便再从硬盘中读8kb的数据到缓冲数组,这样读数据时操作的是内存,极大提高了效率

字符流

字符流是为了解决使用字节流读写文件时的乱码问题,其本质还是字节流,字符流=字节流+编码表,字符流只能操作文本文件

字符串中关于编码解码的相关方法

方法名 说明
byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节
byte[] getBytes(String charsetName) 使用指定的字符集将该 String编码为一系列字节
String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串
String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来创建字符串

FileWriter(字符输出流,写数据)

构造方法

方法名 说明
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象

上面构造方法参数中的boolean append在实际代码编写中写Charset.forName(“”),双引号内写操作文件的编码格式

常用方法

方法名 说明
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) 写一个字符串的一部分
flash() 刷新流,之后还可以继续写数据
close() 关闭流,释放资源,关闭之前会先刷新流。关闭后就不能再写数据

FileWriter本身拥有缓冲区,当write数据较小时不会写入硬盘

FileRead(字符输入流,读数据)

构造方法

方法名 说明
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader

常用方法

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

字符缓冲流

将字符输入输出流封装起来了,本质还是字符输入流和字符输出流,中间有一个缓冲区

构造方法

方法名 说明
BufferedWriter(Writer out) 创建字符缓冲输出流对象
BufferedReader(Reader in) 创建字符缓冲输入流对象

特有功能

BufferedWriter

方法 说明
newLine() 换行

BufferedReader

方法 说明
readLine() 按行读,读到最后没有内容返回是null

对象操作流

IO流输入输出操作的是字节或字符,对象操作流输入输出操作的是对象,例如学生对象(属性有姓名、年龄),输出时将对象信息序列化一个字节序列(本质是二进制),序列化后就可以保存到文件中,也可以在网络中传输,输入时从文件中读取字节序列,重构对象,即将字节序列反序列化为一个对象

每次使用对象序列化流序列化一个对象时,该对象所属的类会生成一个serialVersionUID,此时若是修改类中的内容,serialVersionUID的值会发生改变,再使用对象反序列化流重构对象就会产生异常,想要解决这个问题,可以在类中增加一个serialVersionUID,将其使用final设为常量

如果对象的某个成员变量不想被序列化,可以给该变量添加transient关键字修饰

对象序列化流

一个对象想要被序列化,该类必须实现Serializable接口

构造方法

方法名 说明
ObjectOutputStream(OutputStream out) 创建一个写入指定的OutputStream的ObjectOutputStream

序列化对象的方法

方法名 说明
void writeObject(Object obj) 将指定的对象写入ObjectOutputStream

对象反序列化流

构造方法

方法名 说明
ObjectInputStream(InputStream in) 创建从指定的InputStream读取的ObjectInputStream

反序列化对象的方法

方法名 说明
Object readObject() 从ObjectInputStream读取一个对象

Properties

是一个Map体系的集合,和Map一样,但创建时没有泛型,Properties可以结合IO流向文件中读写集合中的数据

特有方法

方法名 说明
Object setProperty(String key, String value) 设置集合的键和值,都是String类型,底层调用Hashtable方法put
String getProperty(String key) 使用此属性列表中指定的键搜索属性
Set stringPropertyNames() 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
void load(Reader reader) 从输入字符流读取属性列表(键和元素对)
void store(Writer writer, String comments) 将此属性列表(键和元素对)写入此Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流

多线程

基本概念

并行和并发、进程和线程

实现多线程的方式1(继承Thread类)

定义自定义类继承Thread类,子类不能继承其他类,操作比较受限

  • 定义MyThread类(类名自己起)继承Thread类,并重写run方法
  • 在主方法中创建MyThread类的对象
  • 使用类对象调用start方法(是Thread类的方法)开启线程,在start方法中调用了run方法,但直接调用run方法不会开启线程

Thread构造方法

方法 说明
Thread() 空参数的构造方法
Thread(String name) 参数为字符串类型,表示线程的名字

Thread常用方法

方法 说明
void run() 线程开启后,此方法会被调用执行
void start() 执行此方法线程开始执行,java虚拟机会调用run方法
String getName() 获取线程的名字
void setName(String name) 设置线程的名字
static Thread currentThread() 获取当前正在执行的线程对象
static void sleep(long millis) 线程休眠,参数是线程休眠的毫秒值

实现多线程的方式2(实现Runnable接口)

定义自定义类实现Runnable接口,相较于继承Thread类的方式,对于子类更加灵活,符合面向对象设计思想

  • 定义MyRunnable类(类名自己起)实现Runnable接口,并重写run方法
  • 在主方法中创建MyRunnable类的对象
  • 创建Thread类的对象,在参数中传入MyRunnable类的对象
  • 使用Thread类的对象调用start方法开启线程

Thread构造方法

方法 说明
Thread(Runnable target) 创建新的Thread类对象,参数为Runnable接口的实现类对象
Thread(Runnable target,String name) 第二个参数为线程的名字

实现多线程的方式3(实现Callable接口)

定义自定义实现类实现Callable接口,相比上面两种方式,这种方式有返回值,与run方法对应的是call方法,返回值类型为Callable接口的泛型

  • 定义MyCallable类(类名自己起)实现Callable接口,并重写call方法
  • 在主方法中创建MyCallable类的对象
  • 创建Future的实现类FutureTask类的对象,参数为MyCallable对象
  • 创建Thread类的对象,参数为FutureTask对象
  • 使用Thread类的对象调用start方法开启线程
  • 使用FutureTask类的对象调用get方法可以获取线程结束后的结果

线程优先级

调度方式

  • 分时调度:所有线程轮流使用CPU的使用权,平均分配CPU的时间片
  • 抢占式调度:优先让优先级高的线程使用CPU的使用权,优先级相同则随机选择,优先级越高占用时间片越大

java中使用的是抢占式调度,但并不是优先级高的先执行,只是执行的概率增加了

相关方法

方法 说明
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority) 更改此线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10

守护线程

  • 用户线程:jvm会等待用户线程全部结束,最后再结束程序
  • 守护线程:当所有用户线程都结束后,由于jvm结束程序,守护线程会被结束,即使没有执行完

设置方法

方法 说明
void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,JVM将退出

线程状态

线程状态 具体含义
NEW : 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE : 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED : 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING : 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING :一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED : 一个完全运行完成的线程的状态。也称之为终止状态、结束状态

线程池

public class Demo1 {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newCachedThreadPool();//创建默认的线程池对象,池子默认是空的
        executorService.submit(()->{//向线程池中添加线程
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        executorService.shutdown();//关闭线程池
    }
}
public class Demo2 {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(10);//创建线程池,指定线程池的最大数量
        ThreadPoolExecutor pool=(ThreadPoolExecutor)executorService;
        pool.getPoolSize();//获得线程池内线程的数量
        executorService.submit(()->{
            System.out.println("线程1");
        });
        executorService.submit(()->{
            System.out.println("线程2");
        });
        System.out.println(pool.getPoolSize());
        executorService.shutdown();
    }
}

ThreadPoolExecutor

public class Demo3 {
    public static void main(String[] args) {
        //创建线程池对象,包含7个参数
        ThreadPoolExecutor pool=new ThreadPoolExecutor(2,5,100, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //    参数一:核心线程数量,大于等于0
        //    参数二:最大线程数,大于等于0,并且不能小于核心线程数量
        //    参数三:空闲线程最大存活时间,大于等于0
        //    参数四:时间单位
        //    参数五:任务队列,不能为空
        //    参数六:创建线程工厂,不能为空
        //    参数七:任务的拒绝策略,不能为空
    }
}

RejectedExecutionHandler是任务拒绝策略的接口,它存在下面几个子类

子类 说明
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法绕过线程池直接执行

案例:电影院卖票

public class SellTicket implements Runnable{//创建卖票的类
    private int tickets=100;//票的数量
    @Override
    public void run() {
        while (true){
            if(tickets<=0){//如果票数小于等于0,说明票卖完了,结束线程
                break;
            }else{
                try {
                    Thread.sleep(100);//模拟卖票消耗100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tickets--;//卖票,数量减一
                System.out.println(Thread.currentThread().getName()+"售票,还剩"+tickets);
            }
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        //需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
        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();
    }
}

产生的问题

  • 重复票:例如当还剩两张票时,窗口1执行run方法,当执行完tickets–后,CPU被窗口2抢去,窗口2执行run方法,当执行完tickets–后,tickets为0,这时窗口1和窗口2都打印0
  • 负票:当窗口2执行完tickets–后,CPU又被窗口3抢占去,窗口3执行run方法,在等待时,CPU被窗口1和2抢占,打印出0,这时因为窗口3已经进来了while循环,所以窗口3执行tickets–,然后打印出-1

上述问题产生原因:多个线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

synchronized

同步代码块

使用synchronized将操作共享数据的代码锁起来,这块代码就是同步代码块,每次只有一个线程执行这块代码

synchronized()小括号内可以传入任意对象,它就相当于锁,但多个线程必须使用同一个对象,否则每个线程有自己的锁,不能实现每次只有一个线程执行代码的需求

案例代码改进

public class SellTicket implements Runnable{//创建卖票的类
    private int tickets=100;//票的数量
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){//将可能产生问题的代码块用同步代码块框起来,对其加锁,多个线程必须使用同一把锁
                if(tickets<=0){//如果票数小于等于0,说明票卖完了,结束线程
                    break;
                }else{
                    try {
                        Thread.sleep(100);//模拟卖票消耗100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    tickets--;//卖票,数量减一
                    System.out.println(Thread.currentThread().getName()+"售票,还剩"+tickets);
                }
            }//同步代码块结束,锁也打开
        }
    }
}
public class Test2 {
    public static void main(String[] args) {
        //需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
        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();
    }
}

同步方法

同步方法就是把synchronized关键字加到方法上

同步方法锁对象:this

同步静态方法锁对象:类.class

案例代码改进

public class SellTicket implements Runnable{//创建卖票的类
    private int tickets=100;//票的数量
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            boolean issellend=sellTicketMethod();
            if(issellend){
                break;
            }
        }
    }
    public synchronized boolean sellTicketMethod(){//在方法上加上synchronized关键字,使方法为同步方法,this为锁,即调用该方法的类的对象
        if(tickets<=0){//如果票数小于等于0,说明票卖完了,结束线程
            return true;
        }else{
            try {
                Thread.sleep(100);//模拟卖票消耗100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tickets--;//卖票,数量减一
            System.out.println(Thread.currentThread().getName()+"售票,还剩"+tickets);
            return false;
        }
    }
}
public class Test3 {
    public static void main(String[] args) {
        //需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
        SellTicket st=new SellTicket();//因为三个线程用一个Runnable接口的实现类对象,所以三个线程使用同一个锁
        Thread t1=new Thread(st,"窗口1");
        Thread t2=new Thread(st,"窗口2");
        Thread t3=new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

代码的原子性

操作要么不间断地全部被执行,不受任何打扰,要么不执行

例如num++就不是原子性操作,因为它的本质有三步:

  • 获取num的值
  • 将num值加一
  • 将加一后的值赋给num

Lock锁

Lock是一个接口,使用时创建它的实现类ReentrantLock的对象,使用ReentrantLock的对象调用加锁和解锁方法

构造方法

方法名 说明
ReentrantLock() 创建一个ReentrantLock的实例对象

加锁解锁方法

方法名 说明
void lock() 获得锁
void unlock() 释放锁

案例代码改进

public class SellTicket implements Runnable{//创建卖票的类
    private int tickets=100;//票的数量
    private Object obj = new Object();
    ReentrantLock lock=new ReentrantLock();//创建lock锁的对象
    @Override
    public void run() {
        while (true){
            lock.lock();//加锁
            if(tickets<=0){//如果票数小于等于0,说明票卖完了,结束线程
                break;
            }else{
                try {
                    Thread.sleep(100);//模拟卖票消耗100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tickets--;//卖票,数量减一
                System.out.println(Thread.currentThread().getName()+"售票,还剩"+tickets);
            }
            lock.unlock();//解锁
        }
    }
}
public class Test4 {
    public static void main(String[] args) {
        //需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
        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();
    }
}

死锁

生产者和消费者问题(锁&队列)

生产者和消费者是一种设计模式

包含两类线程,生产者用于生产数据,消费者用于消费数据,二者采用共享的数据区域,生产者生产数据之后直接放置在共享区中,并不关心消费者的行为,消费者只需从共享数据区中去获取数据,并不关心生产者的行为

相关方法:Object类的等待和唤醒方法

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

案例

  • 桌子类(Desk):定义表示汉堡数量的变量,定义锁对象变量,定义标记桌子上有无汉堡的变量
  • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
    • 1.判断是否有汉堡,决定当前线程是否执行
    • 2.如果有汉堡,就进入等待状态,如果没有汉堡,继续执行,生产汉堡
    • 3.生产汉堡之后,更新桌子上汉堡状态,唤醒消费者消费汉堡
  • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
    • 1.判断是否有汉堡,决定当前线程是否执行
    • 2.如果没有汉堡,就进入等待状态,如果有汉堡,就消费汉堡
    • 3.消费汉堡后,更新桌子上汉堡状态,唤醒生产者生产汉堡
  • 测试类(Demo):里面有main方法,main方法中的代码步骤如下
    • 创建生产者线程和消费者线程对象
    • 分别开启两个线程

锁的方式

//数据共享区
public class Desk {
    //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    public static boolean flag = false;
    public static int count = 10;//汉堡包的总数量 消费者总共能吃的数量
    public static final Object lock = new Object();//锁对象
}
//生产者
public class Cooker extends Thread {
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){//生产者和消费者都使用同一个锁对象,即Desk类中定义的锁对象
                if(Desk.count == 0){//当顾客把能吃的汉堡吃完,结束
                    break;
                }else{
                    if(!Desk.flag){
                        //判断如果没有汉堡 就生产
                        System.out.println("厨师正在生产汉堡包");
                        Desk.flag = true;//设置true表示有汉堡了
                        Desk.lock.notifyAll();//唤醒等待的消费者
                    }else{
                        //判断如果有汉堡 厨师等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
//消费者
public class Foodie extends Thread {
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){//和生产者使用同一个锁
                if(Desk.count == 0){//把能吃的吃完就结束
                    break;
                }else{
                    if(Desk.flag){
                        //判断如果有汉堡
                        System.out.println("吃货在吃汉堡包");
                        Desk.flag = false;//flag设置为false表示汉堡没了
                        Desk.count--;//汉堡总量-1
                        Desk.lock.notifyAll();//唤醒厨师
                    }else{
                        //判断如果没有汉堡 就等待
                        //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
//测试类
public class Test5 {
    public static void main(String[] args) {
        Foodie f = new Foodie();
        Cooker c = new Cooker();
        f.start();
        c.start();
    }
}

代码优化,上述代码实现多线程利用的是继承Thread接口的方法,我们可以定义Desk类中的属性为成员变量,在生产者和消费者中通过创建Desk对象的方式获取它,在测试类中,创建Desk对象,将其作为生产者和消费者构造方法的参数,锁是Desk的对象,这样一来,二者用补一个锁,并且可以在测试类中定义消费者要吃的汉堡数量

队列的方式

public class TestQueue {
    public static void main(String[] args) {
        ArrayBlockingQueue q = new ArrayBlockingQueue(3);//q是锁

        MyRun1 m1 = new MyRun1(q);
        MyRun2 m2 = new MyRun2(q);

        new Thread(m1).start();
        new Thread(m2).start();
    }
}

class MyRun1 implements Runnable {
    public ArrayBlockingQueue q;

    public MyRun1(ArrayBlockingQueue q) {
        this.q = q;
    }

    @Override
    public void run() {
        //向队列里添加数据
        for (int i = 0; i < 100; i++) {
            try {
                q.put(i);//存入队列 队列满了 代码就停在这里
                System.out.println("存入了" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyRun2 implements Runnable {
    public ArrayBlockingQueue q;

    public MyRun2(ArrayBlockingQueue q) {
        this.q = q;
    }

    @Override
    public void run() {
        while (true) {
            int peek = 0;
            try {
                System.out.println("从队列里取数据");
                peek = (int) q.take();//从队列里取数据 如果没有数据 就停在这里
                System.out.println(peek);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

使用队列实现上述的消费者和生产者问题

//生产者
public class Cooker extends Thread {
    ArrayBlockingQueue arrayBlockingQueue;
    public Cooker(ArrayBlockingQueue arrayBlockingQueue){
        this.arrayBlockingQueue=arrayBlockingQueue;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {//十次是顾客总共要吃十个汉堡
            try {
                arrayBlockingQueue.put(i);//当队列为空,只做个汉堡放进去,否则等待队列有空位
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("厨师制作了第"+i+"个汉堡");
        }
    }
}
public class Foodie extends Thread {
    ArrayBlockingQueue arrayBlockingQueue;
    public Foodie(ArrayBlockingQueue arrayBlockingQueue){
        this.arrayBlockingQueue=arrayBlockingQueue;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                arrayBlockingQueue.take();//当队列中有汉堡时,取出汉堡吃,否则等待队列有汉堡
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("顾客吃了第"+i+"个汉堡");
        }
    }
}
public class Test6 {
    public static void main(String[] args) {
        ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);
        Foodie f = new Foodie(arrayBlockingQueue);
        Cooker c = new Cooker(arrayBlockingQueue);
        new Thread(f).start();
        new Thread(c).start();
    }
}

网络编程

三要素(1)IP:网络中设备的唯一标识

IPv4:32比特位,点分十进制

IPv6:128比特位,冒分十六进制

常见命令

ipconfig:查看本机IP

ping IP地址:检查网络是否连通

特殊IP

127.0.0.1:本机IP地址

InetAddress

InetAddress:此类表示Internet协议(IP)地址

相关方法

方法名 说明
static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串

public class Test1 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress address=InetAddress.getByName("192.168.27.72");//获取该主机的IP地址,参数可以是主机名,也可以是IP地址
        System.out.println(address.getHostAddress());//打印IP地址的字符串形式
        System.out.println(address.getHostName());//打印此IP地址的主机名
    }
}

三要素(2)端口:设备中应用程序的唯一标识

取值范围:0~65535

三要素(3)协议

UDP:无连接通信协议

三种通信方式

  • 单播:一对一
  • 组播:一对多
  • 广播:一对所以

TCP:面向连接通信协议

三次握手

四次挥手

UDP通信

UDP协议是一种不可靠的网络传输协议,它在通信的两端各建立一个Socket对象,但这两个Socket只是发送、接收数据的对象,并没有客户端和服务端之分

使用的类:DatagramSocket

UDP发送数据

构造方法

方法名 说明
DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端口
DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口

相关方法

方法名 说明
void send(DatagramPacket p) 发送数据报包
void close() 关闭数据报套接字
void receive(DatagramPacket p) 从此套接字接受数据报包

发送数据的步骤

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

  • 创建数据,并把数据打包

  • 调用DatagramSocket对象的方法发送数据

  • 关闭发送端

    public class Test2 {
    public static void main(String[] args) throws IOException {
    DatagramSocket socket=new DatagramSocket();//创建发送端的Socket对象
    String s=“你好”;//创建数据,并把数据打包
    byte[] b=s.getBytes(“gbk”);
    InetAddress address=InetAddress.getByName(“127.0.0.1”);//获取发送目标的IP地址
    DatagramPacket packet=new DatagramPacket(b,b.length,address,10000);//发送数据的字节数组、数组的长度、发送目标的IP地址,端口号
    socket.send(packet);//发送数据
    socket.close();//关闭发送端
    }
    }

UDP接收数据

构造方法

方法名 说明
DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包

相关方法

方法名 说明
byte[] getData() 返回数据缓冲区
int getLength() 返回要发送的数据的长度或接收的数据的长度

接收数据的步骤

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

  • 创建一个数据包,用于接收数据

  • 调用DatagramSocket对象的方法接收数据

  • 解析数据包,并把数据在控制台显示

  • 关闭接收端

    public class Test3 {
    public static void main(String[] args) throws IOException {
    DatagramSocket socket=new DatagramSocket(11111);//创建接收端的Socket对象,端口必须写死,不然别人发的时候不知道给谁发
    byte[] b=new byte[1024];
    DatagramPacket packet=new DatagramPacket(b,b.length);//接收数据的字节数据、接收多长的数据装到数组
    socket.receive(packet);//接收数据
    byte[] b1=packet.getData();
    System.out.println(new String(b1,0,b1.length,“gbk”));
    socket.close();
    }
    }

案例练习

UDP发送数据:数据来自于键盘录入,可以一直发,直到输入的数据是886,发送数据结束

UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

//发送端
public class Test3_1 {
    public static void main(String[] args) throws IOException {
        Scanner sc=new Scanner(System.in);
        DatagramSocket ds=new DatagramSocket();
        while (true){
            System.out.println("输入数据");
            String data=sc.next();
            if("886".equals(data)){
                ds.close();
                break;
            }else {
                byte[] b=data.getBytes("gbk");//将字符串转换为字节数组
                InetAddress address=InetAddress.getByName("127.0.0.1");//获取发送目标的地址
                DatagramPacket dp=new DatagramPacket(b,b.length,address,54188);//数据打包
                ds.send(dp);
            }
        }
    }
}
//接收端
public class Test3_2 {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds=new DatagramSocket(54188);
        while (true){
            byte[] b=new byte[1024];
            DatagramPacket dp=new DatagramPacket(b,b.length);
            ds.receive(dp);
            byte[] b1=dp.getData();
            System.out.println(new String(b1,0, dp.getLength(),"gbk"));
        }
    }
}

UDP通信方式—单播

两个主机之间的端对端通信,例如上面的案例

UDP通信方式—组播

用于对一组特定的主机进行通信,224.0.0.0~239.255.255.255

发送端步骤

  • 创建发送端的Socket对象(DatagramSocket)
  • 创建数据,并把数据打包(DatagramPacket)
  • 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
  • 释放资源

接收端步骤

  • 创建接收端Socket对象(MulticastSocket)

  • 创建一个箱子,用于接收数据

  • 把当前计算机绑定一个组播地址

  • 将数据接收到箱子中

  • 解析数据包,并打印数据

  • 释放资源

    // 发送端
    public class ClinetDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建发送端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket();
    String s = “hello 组播”;
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName(“224.0.1.0”);
    int port = 10000;//定义端口
    // 2. 创建数据,并把数据打包(DatagramPacket)
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
    // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
    ds.send(dp);
    // 4. 释放资源
    ds.close();
    }
    }
    // 接收端
    public class ServerDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建接收端Socket对象(MulticastSocket)
    MulticastSocket ms = new MulticastSocket(10000);
    // 2. 创建一个箱子,用于接收数据
    DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
    // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
    ms.joinGroup(InetAddress.getByName(“224.0.1.0”));
    // 4. 将数据接收到箱子中
    ms.receive(dp);
    // 5. 解析数据包,并打印数据
    byte[] data = dp.getData();
    int length = dp.getLength();
    System.out.println(new String(data,0,length));
    // 6. 释放资源
    ms.close();
    }
    }

UDP通信方式—广播

用于一个主机对整个局域网上所有主机上的数据通信

发送端

  • 创建发送端Socket对象(DatagramSocket)
  • 创建存储数据的箱子,将广播地址封装进去
  • 发送数据
  • 释放资源

接收端

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

  • 创建一个数据包,用于接收数据

  • 调用DatagramSocket对象的方法接收数据

  • 解析数据包,并把数据在控制台显示

  • 关闭接收端

    // 发送端
    public class ClientDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建发送端Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket();
    // 2. 创建存储数据的箱子,将广播地址封装进去
    String s = “广播 hello”;
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName(“255.255.255.255”);
    int port = 10000;
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
    // 3. 发送数据
    ds.send(dp);
    // 4. 释放资源
    ds.close();
    }
    }
    // 接收端
    public class ServerDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建接收端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket(10000);
    // 2. 创建一个数据包,用于接收数据
    DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
    // 3. 调用DatagramSocket对象的方法接收数据
    ds.receive(dp);
    // 4. 解析数据包,并把数据在控制台显示
    byte[] data = dp.getData();
    int length = dp.getLength();
    System.out.println(new String(data,0,length));
    // 5. 关闭接收端
    ds.close();
    }
    }

TCP通信

服务器端:一开始就接收,先运行,等待客户端连接,使用ServiceSocket类

客户端:一开始就发送,主动连接服务器,使用Socket类

使用IO流进行数据传输

TCP发送数据(客户端)

构造方法

方法名 说明
Socket(InetAddress address,int port) 创建流套接字并将其连接到指定IP指定端口号
Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号

相关方法

方法名 说明
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

public static void main(String[] args) throws IOException {
    //1创建socket对象
    //参数1和2是要连接的服务器的ip和端口
    Socket s = new Socket("192.168.27.58", 13000);
    System.out.println("连接成功");

    //获取发送数据用的字节流对象
    OutputStream os = s.getOutputStream();

    //写入数据
    os.write("hehe".getBytes());

    //os.close();
    //关闭套接字
    s.close();
}

TCP接收数据(服务器端)

构造方法

方法名 说明
ServletSocket(int port) 创建绑定到指定端口的服务器套接字

相关方法

方法名 说明
Socket accept() 监听要连接到此的套接字并接受它

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建服务器端的Socket对象(ServerSocket)
        //ServerSocket(int port) 创建绑定到指定端口的服务器套接字
        ServerSocket ss = new ServerSocket(10000);

        //这里等待客户端连接会阻塞,一旦连接返回一个处理此次连接的Socket对象
        Socket s = ss.accept();
        System.out.println("连接成功"+s.getRemoteSocketAddress());

        //获取输入流,读数据,并把数据显示在控制台
        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();
    }
}

案例练习

客户端:发送数据,接受服务器反馈

服务器:收到消息后给出反馈

public class Client {
    public static void main(String[] args) throws IOException {
        //创建Socket对象,连接服务端
        Socket socket=new Socket("127.0.0.1",25980);
        //创建输出流,向服务端发送数据
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        //字节流转换成字符流
        
        bw.write("你好");
        bw.newLine();//换行,和从文件中读取数据不同,接收网络中的数据,在服务器端只能读取到“你好”,然后等待,所以要手动输入换行
        bw.flush();//推送
        socket.shutdownOutput();
        //创建输入流,接收服务器反馈
        BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
        socket.close();
    }
}
public class Service {
    public static void main(String[] args) throws IOException {
        //创建ServiceSocket对象,被客户端连接
        ServerSocket serverSocket=new ServerSocket(25980);
        System.out.println("等待连接");
        Socket socket=serverSocket.accept();
        System.out.println("连接成功");
        //创建输入流,接收客户端发送的数据
        BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        System.out.println("等待接收数据");
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
        System.out.println("数据接收成功");
        socket.shutdownInput();
        //创建输出流,向客户端发送反馈
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        System.out.println("准备发送反馈");
        bw.write("我是客服007");
        bw.newLine();
        bw.flush();
        System.out.println("反馈发送成功");
        socket.shutdownOutput();
        socket.close();
    }
}

通信方式总结

UDP通信

发送和接收数据都创建DatagramSocket对象,将数据使用DatagramPacket打包

TCP通信

客户端发送创建Socket对象,使用Socket获取IO流

服务器端接收创建ServiceSocket对象,在使用ServiceSocket对象调用accept方法等待连接时返回Socket对象,使用Socket对象获取IO流

日志

记录程序运行中的信息,可以将日志信息写入到日志文件或数据库中

目前常用的框架是Logback

日志使用步骤

导入jar包和配置文件

编写配置文件

在代码中获取日志对象

按照日志级别设置记录日志信息

级别:debug < info < warn < error

枚举

表示一些固定的值

格式

public enum s {   
	枚举项1,枚举项2,枚举项3;
}
注意: 定义枚举类要用关键字enum

示例代码

// 定义一个枚举类,用来表示春,夏,秋,冬这四个固定值
public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER;
} 

特点

  1. 所有枚举类都是Enum的子类

  2. 我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项

  3. 每一个枚举项其实就是该枚举的一个对象

  4. 枚举也是一个类,也可以去定义成员变量

  5. 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略

  6. 枚举类可以有构造器,但必须是private的,它默认的也是private的。
    枚举项的用法比较特殊:枚举(“”);

  7. 枚举类也可以有抽象方法,但是枚举项必须重写该方法

    //枚举类JIJIE
    public enum JIJIE {
    SPRING(){
    @Override
    public void show() {
    System.out.println(this.name);
    }
    },
    SUMMER{
    @Override
    public void show() {
    System.out.println(this.name);
    }
    },
    AUTUMN{
    @Override
    public void show() {
    System.out.println(this.name);
    }
    },
    WINTER{
    @Override
    public void show() {
    System.out.println(this.name);
    }
    };
    public String name;//特点4:枚举也是一个类,也可以去定义成员变量

     JIJIE(String name) {//特点6:枚举类可以有构造器,但必须是private的,它默认的也是private的
         this.name = name;
     }
    
     JIJIE() {
     }
     public abstract void show();//特点7:枚举类也可以有抽象方法,但是枚举项必须重写该方法
    

    }
    //测试类EnumDemo
    public class EnumDemo {
    public static void main(String[] args) {
    System.out.println(JIJIE.SPRING);//特点2:可以通过"枚举类名.枚举项名称"去访问指定的枚举项
    System.out.println(JIJIE.SUMMER);
    System.out.println(JIJIE.AUTUMN);
    System.out.println(JIJIE.WINTER);
    JIJIE spring=JIJIE.SPRING;//特点3:每一个枚举项其实就是该枚举的一个对象
    String name=JIJIE.SPRING.name();//获取枚举项的名称
    int index=JIJIE.SPRING.ordinal();//返回枚举项在枚举类中的索引值
    int result=JIJIE.SPRING.compareTo(JIJIE.WINTER);//比较两个索引值,返回两个索引值的产值,前减后
    System.out.println(result);//-3
    String s=JIJIE.SPRING.toString();//返回枚举常量的名称
    JIJIE summer=Enum.valueOf(JIJIE.class,“SUMMER”);//获取指定枚举类中指定名称的枚举值
    JIJIE[] values=JIJIE.values();//获取所有的枚举项
    }
    }

常用方法

方法名 说明
String name() 获取枚举项的名称
int ordinal() 返回枚举项在枚举类中的索引值
int compareTo(E o) 比较两个枚举项,返回的是索引值的差值
String toString() 返回枚举常量的名称
static T valueOf(Class type,String name) 获取指定枚举类中的指定名称的枚举值
values() 获得所有的枚举项

类加载器(ClassLoader)

类加载器就是将类的class文件加载到内存中

类加载时机(当一个类被使用时)

  • 创建类的实例(对象)
  • 调用类的类方法
  • 访问类或者接口的类变量,或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类

分类(常用的是系统类加载器)

  • Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
  • Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
  • System class loader:系统类加载器,负责加载用户类路径上所指定的类库

System继承自Platform,Platform继承自Bootstrap

public class ClassLoaderDemo1 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //获取系统类加载器的父加载器 --- 平台类加载器
        ClassLoader classLoader1 = systemClassLoader.getParent();

        //获取平台类加载器的父加载器 --- 启动类加载器
        ClassLoader classLoader2 = classLoader1.getParent();

        System.out.println("系统类加载器" + systemClassLoader);/
        System.out.println("平台类加载器" + classLoader1); //
        System.out.println("启动类加载器" + classLoader2); //null

    }
}

方法名 说明
public static ClassLoader getSystemClassLoader() 获取系统类加载器
public InputStream getResourceAsStream(String name) 加载某一个资源文件

public class ClassLoaderDemo2 {
    public static void main(String[] args) throws IOException {
        //static ClassLoader getSystemClassLoader() 获取系统类加载器
        //InputStream getResourceAsStream(String name)  加载某一个资源文件
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //利用加载器去加载一个指定的文件
        //参数:文件的路径(放在src的根目录下,默认去那里加载)
        //返回值:字节流。
        InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

        Properties prop = new Properties();
        prop.load(is);

        System.out.println(prop);

        is.close();
    }
}

反射

获取Class类对象

public class Student {
    public String name;
    private int age;

    public Student() {//无参的公共构造方法
    }

    public Student(String name, int age) {//有参的公共构造方法
        this.name = name;
        this.age = age;
    }
    
    private Student(String name){//有参的私有构造方法
    }

    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 void study(){
        System.out.println("学生在学习");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1forName 参数为全类名
        Class c1 = Class.forName("com.heima.test1.Student");
        System.out.println(c1);

        //2通过类加载器获取  参数为全类名
        Class c2 = ClassLoader.getSystemClassLoader().loadClass("com.heima.test1.Student");

        //3直接类名.class
        Class c3 = Student.class;
        System.out.println(c3);

        //4对象.getClass();
        Class c4 = new Student().getClass();
        System.out.println(c4);
    }
}

获取构造方法

方法 说明
getConstructors() 获取所有构造方法
getDeclaredConstructors() 获取所有构造方法,包括私有构造方法
getConstructor(方法参数) 获取指定的公共构造方法
getDeclaredConstructor(方法参数) 获取指定的私有构造方法

//用上面的Student类,c1为类的Class对象
Constructor[] cs1=c1.getConstructors();//获取所有的公共构造方法
for (Constructor c : cs1) {
    System.out.println(c);
}
Constructor[] cs2=c1.getDeclaredConstructors();//获取所有构造方法,包括私有方法
for (Constructor c : cs2) {
    System.out.println(c);
}
Constructor cs3=c1.getConstructor(String.class,int.class);
//获取指定公有构造方法,括号内为参数类型.class
Constructor cs4=c1.getDeclaredConstructor(String.class);//获取指定私有构造方法

使用构造方法Constructor对象创建具体对象

方法 说明
T newInstance(Object…initargs) 根据指定的构造方法创建对象
setAccessible(boolean flag) 设置为true,表示取消访问检查

Student student=(Student)cs3.newInstance("张三",18);//传入的参数要和构造方法保持一致
------------------------------------------------------------------------------------------------
cs4.setAccessible(true);//使用私有构造方法创建对象时,要先将其设置为可访问的
Student student=(Student)cs4.newInstance("李四");

获取成员变量并使用

方法名 说明
Field[] getFields() 返回所有公共成员变量对象的数组
Field[] getDeclaredFields() 返回所有成员变量对象的数组
Field getField(String name) 返回单个公共成员变量对象,括号内为变量名
Field getDeclaredField(String name) 返回单个成员变量对象,括号内为变量名
void set(Object obj, Object value) 为变量赋值,参数1为具体对象,参数2为变量值
Object get(Object obj) 获取变量值,参数为具体对象

Field[] f1=c1.getFields();//获取所有的公共的成员变量
for (Field f : f1) {
    System.out.println(f);
}
Field[] f2=c1.getDeclaredFields();//获取所有的成员变量,包括私有成员
for (Field f : f2) {
    System.out.println(f);
}
Field f3=c1.getField("name");//获取单个公共成员变量
f3.set(student,"张三");//为Student类的name变量赋值张三
Field f4=c1.getDeclaredField("age");//获取单个私有成员变量
f4.setAccessible(true);//对私有变量赋值,也要先将其设置为可访问的
f4.set(student,18);

获取成员方法并使用

方法名 说明
Method[] getMethods() 返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name, Class<?>... parameterTypes) 返回单个公共成员方法对象 Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象
Object invoke(Object obj, Object… args) 运行方法

Method[] methods1=c1.getMethods();//获取所有公共方法,包括继承自父类的方法
for (Method method : methods1) {
    System.out.println(method);
}
Method[] methods2=c1.getDeclaredMethods();//获取所有方法,包括私有方法
for (Method method : methods2) {
    System.out.println(method);
}
//获取指定公共方法
Method method1=c1.getMethod("function2",String.class);//第一个参数是方法名,第二个是方法的参数
method1.invoke(student,"琅琊");//参数1为具体对象,参数2为方法参数的值
//获取指定私有方法
Method method2=c1.getDeclaredMethod("show");
method2.setAccessible(true);
method2.invoke(student);

xml

标签规则

  • 标签由一对尖括号和合法标识符组成
  • 标签必须成对出现

    前边的是开始标签,后边的是结束标签
  • 特殊的标签可以不成对,但是必须有结束标记
  • 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来
  • 标签需要正确的嵌套
    这是正确的: 张三
    这是错误的: 张三

语法规则

  • XML文件的后缀名为:xml
  • 文档声明必须是第一行第一列<?xml version=“1.0” encoding=“UTF-8” standalone=“yes”?> version:该属性是必须存在的
    encoding:该属性不是必须的打开当前xml文件的时候应该是使用什么字符编码表(一般取值都是UTF-8)
    standalone: 该属性不是必须的,描述XML文件是否依赖其他的xml文件,取值为yes/no
  • 必须存在一个根标签,有且只能有一个
  • XML文件中可以定义注释信息
  • XML文件中可以存在以下特殊字符
    < < 小于
    > > 大于
    & & 和号
    ’ ’ 单引号
    " " 引号
  • XML文件中可以存在CDATA区<![CDATA[ …内容… ]]>

xml解析

解析就是从xml文件中获取数据,这里使用DOM4J工具使用DOM解析方式,需导入dom4j的jar包,下载地址是https://dom4j.github.io

常见的解析工具

  • JAXP: SUN公司提供的一套XML的解析的API
  • JDOM: 开源组织提供了一套XML的解析的API-jdom
  • DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java
  • Jsoup:功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便(项目中讲解)

两种基本的xml解析方式:

  • DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象。
  • SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方式进行具体解析,每执行一行,都触发对应的事件。(了解)
  • PULL:Android内置的XML解析方式,类似SAX。(了解)

解析过程中必须记住的几个单词:Document文档 Element标签 Attribute属性

  • SaxReader对象

    返回值 方法名 说明
    SAXReader new SAXReader() 构造器
    Document Document read(String url) 加载执行xml文档

  • Document对象

    返回值 方法名 说明
    Element getRootElement() 获得根元素

  • Element对象

    返回值 方法名 说明
    List elements([String ele] ) 获得指定名称的所有子元素。可以不指定名称
    Element element([String ele]) 获得指定名称第一个子元素。可以不指定名称
    String getName() 获得当前元素的元素名
    String attributeValue(String attrName) 获得指定属性名的属性值
    String elementText(Sting ele) 获得指定名称子元素的文本值
    String getText() 获得当前元素的文本内容

    //测试类
    public class Test {
    public static void main(String[] args) throws DocumentException {
    SAXReader saxReader=new SAXReader();//创建解析器对象
    Document document=saxReader.read(“mymodule/src/notebook/student.xml”);//利用解析器把xml文件加载到内存中,并返回一个文档对象
    Element rootElement=document.getRootElement();//获取根标签,这里是students
    List list=rootElement.elements();//获取根标签的所有子标签
    List list1=rootElement.elements(“student”);//获取根标签的指定名称的所有子标签
    ArrayList list2=new ArrayList<>();//用来装学生对象
    for (Element element : list1) {//遍历所有的student,获取student下的标签和属性
    Attribute attribute=element.attribute(“id”);//获取名为id的属性
    String id=attribute.getValue();//获取属性id的值
    Element elementName=element.element(“name”);//获取名为name的标签
    String name=elementName.getText();//获取标签name的标签体
    Element elementAge=element.element(“age”);//获取名为age的标签
    String age=elementAge.getText();//获取标签age的标签体
    Student student=new Student(name,Integer.parseInt(age));//创建学生对象
    list2.add(student);//将对象添加到集合中
    String id1=element.attributeValue(“id”);//获取指定属性名的属性值,这里是id属性
    System.out.println(id1);
    String name1=element.elementText(“name”);//获取指定名称子元素的文本值
    System.out.println(name1);
    }
    for (Student student : list2) {
    System.out.println(student);
    }
    }
    }
    //xml文件

    <?xml version="1.0" encoding="UTF-8" ?> 张三 18 李四 24 //学生类 public class Student { public String name; public 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 +
                  '}';
      }
    

    }

约束

用来限定xml文件中可使用的标签以及属性,包括DTD约束和schema约束

DTD约束

编写DTD约束

  • 创建一个文件,这个文件的后缀名为.dtd
  • 看xml文件中使用了哪些元素,<!ELEMENT> 可以定义元素
  • 判断元素是简单元素还是复杂元素
    简单元素:没有子元素。
    复杂元素:有子元素的元素;

引入DTD约束(会根据约束写正确的xml文档)

  • 引入本地dtd
  • 在xml文件内部引入
  • 引入网络dtd

DTD语法

  • 定义元素:<!ELEMENT 元素名 元素类型>
    简单元素:元素类型处为EMPTY: 表示标签体为空、ANY: 表示标签体可以为空也可以不为空、PCDATA: 表示该元素的内容部分为字符串
    复杂元素:元素类型处为子元素名称,子元素间用",“或”|“隔开”,“表示子元素有序,”|"表示子元素只能出现任意一个,在子元素名称后面可以跟?、+、*、或什么也不跟,加?表示子元素可以出现0次或1次,加+表示子元素可以出现1次或多次,加星号表示子元素可以出现0次或多次,什么都不加表示可以出现1次

  • 定义属性:<!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>
    属性的类型:
    CDATA类型:普通的字符串
    属性的约束:
    // #REQUIRED: 必须的
    // #IMPLIED: 属性不是必需的
    // #FIXED value:属性值是固定的

    //dtd文件

    <!ELEMENT students (student+)> <!ELEMENT student (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)> <!ATTLIST student id CDATA #REQUIRED>

    //xml文件

    <?xml version="1.0" encoding="UTF-8" ?> 张三 11 李四 22

schema约束

schema和dtd的区别

  • schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
  • 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
  • dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
  • schema 语法更加的复杂

编写schema约束

  • 创建一个文件,这个文件的后缀名为.xsd。
  • 定义文档声明
  • schema文件的根标签为:
  • 在中定义属性:xmlns=http://www.w3.org/2001/XMLSchema
  • 在中定义属性 :targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间。
  • 在中定义属性 :elementFormDefault="qualified“,表示当前schema文件是质量良好的文件。
  • 通过element定义元素
  • 判断当前元素是简单元素还是复杂元素

引入schema约束

  • 在根标签上定义属性xmlns=“http://www.w3.org/2001/XMLSchema-instance”

  • 通过xmlns引入约束文件的名称空间

  • 给某一个xmlns属性添加一个标识,用于区分不同的名称空间
    格式为: xmlns:标识=“名称空间地址” ,标识可以是任意的,但是一般取值都是xsi

  • 通过xsi:schemaLocation指定名称空间所对应的约束文件路径
    格式为:xsi:schemaLocation = "名称空间url 文件路径“

    <?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?> 张三 23

注解

自定义注解

public @interface 注解名称 {

public 属性类型 属性名() default 默认值 ;

}

属性类型

  • 基本数据类型
  • String
  • Class
  • 注解
  • 枚举
  • 以上类型的一维数组

元注解:描述注解的注解

元注解名 说明
@Target 指定了注解能在哪里使用
@Retention 可以理解为保留时间(生命周期)
@Inherited 表示修饰的自定义注解可以被子类继承
@Documented 表示该自定义注解,会出现在API文档里面。

NIO

Buffer缓冲区

实质是一个数组

创建缓冲器对象

方法名 说明
static ByteBuffer allocate(长度) 在堆内存创建缓冲区
static ByteBuffer wrap(byte[] array) 创建一个有内容的byte类型缓冲区
static ByteBuffer allocateDirect(长度) 在系统内存创建缓冲区

public class BufferCreat {
    public static void main(String[] args) {
        //方法1:allocate()
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);//在堆内存中创建一个长度为10的缓冲区
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        
        ByteBuffer byteBuffer1=ByteBuffer.wrap(new byte[]{1,2,3,4,5});//创建一个缓冲区,初始内容为参数的byte数组内容
        System.out.println(byteBuffer1);//java.nio.HeapByteBuffer[pos=0 lim=5 cap=5]
        System.out.println(Arrays.toString(byteBuffer1.array()));//[1, 2, 3, 4, 5]
        
        ByteBuffer byteBuffer2=ByteBuffer.allocateDirect(8);//在系统内存中创建一个长度为8的缓冲区
        System.out.println(byteBuffer2);//java.nio.DirectByteBuffer[pos=0 lim=8 cap=8]
        System.out.println(Arrays.toString(byteBuffer2.array()));//报错:baoException in thread "main" java.lang.UnsupportedOperationException(为什么??????????????????????)
    }
}

缓冲区常用方法

方法名 说明
put() 给缓冲区添加元素
get(int index) 获取缓冲区中的元素
capacity() 获取缓冲区的容量
limit() 限制
position() 位置
clear() 还原缓冲区的各个指针
flip() 切换读写状态

//put()
public class BufferPut {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        byteBuffer.put((byte) 10);//存储方式1,对pos有影响
        byteBuffer.put((byte) 20);
        byteBuffer.put((byte) 30);
        byteBuffer.put((byte) 40);
        byteBuffer.put((byte) 50);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=5 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 50, 0, 0, 0, 0, 0]
        byteBuffer.put(new byte[]{1,2,3});//存储方式2,对pos有影响
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=8 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 50, 1, 2, 3, 0, 0]
        byteBuffer.put(6,(byte) 8);//存储方式3,将索引6处的数值设为8,如果已存在数据则将其覆盖,对pos没有影响
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=8 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 50, 1, 8, 3, 0, 0]
    }
}
//capacity()
public class BufferCapacity {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer.capacity());//10,获取缓冲区数组的容量,即数组长度,注意不是存储数据的长度,不可变
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 0, 0, 0, 0, 0, 0]
    }
}
//position()
public class BufferPosition {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 0, 0, 0, 0, 0, 0]
        System.out.println(byteBuffer.position());//获得pos的值,4
        byteBuffer.position(6);//设置pos值
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=6 lim=10 cap=10]
    }
}
//limit()
public class BufferLimit {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 0, 0, 0, 0, 0, 0]
        System.out.println(byteBuffer.limit());//返回lim值,10
        byteBuffer.limit(6);//限制pos的最大值为6,这时数组最大能存储6个数据,即下标0,1,2,3,4,5
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=6 cap=10]
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=6 lim=6 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));//[10, 20, 30, 40, 10, 20, 0, 0, 0, 0]
        //byteBuffer.put((byte)30); 再加就会报错
    }
}
//mark()
public class BufferMark {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
        byteBuffer.mark();//记住pos的位置
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));
        byteBuffer.reset();//恢复pos到mark标记的位置
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
    }
}
//flip()
public class BufferFlip {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));
        byteBuffer.flip();//切换读写状态,将lim设置为pos位置,将pos设置为0,可以读取已存储的数据
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=4 cap=10]
    }
}
//clear
public class BufferClear {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        byteBuffer.clear();//还原缓冲区的各个指针
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
    }
}
//get()
public class BufferGet {
    public static void main(String[] args) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        byteBuffer.put((byte)10);
        byteBuffer.put((byte)20);
        byteBuffer.put((byte)30);
        byteBuffer.put((byte)40);
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
        System.out.println(Arrays.toString(byteBuffer.array()));
        System.out.println(byteBuffer.get());//获取pos处的数据元素
        System.out.println(byteBuffer.get());
        System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=6 lim=10 cap=10]
        System.out.println(byteBuffer.get(1));//获取指定位置的数据元素
    }
}

Channel通道

通道相当于之前学习的IO流,区别是IO流分为了输入和输出,而通道既可以读也可以写

相关API

  • FileChannel:从文件中读取数据
  • DatagramChannel:读写UDP网络协议数据
  • SocketChannel:读写TCP网络协议数据
  • ServerSocketChannel:可以监听TCP连接

案例1:NIO完成文件复制

public class Demo1 {
    public static void main(String[] args) {
        try (//创建输入输出流
             FileInputStream fis=new FileInputStream("mymodule/src/nioDemo/demo1/a.txt");
             FileOutputStream fos=new FileOutputStream("mymodule/src/nioDemo/demo1/a_1.txt");){
            //获取两个管道
            FileChannel inchannel=fis.getChannel();
            FileChannel outchannel=fos.getChannel();
            //一次读、写1k的方式复制
            ByteBuffer byteBuffer= ByteBuffer.allocate(1024);//创建缓冲区对象
            while ((inchannel.read(byteBuffer))!=-1){
                byteBuffer.flip();//将lim设为pos,将pos设为0
                outchannel.write(byteBuffer);//输出有效数据
                byteBuffer.clear();//将lim设为cap,将pos设为0
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

Selector选择器

AIO

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到