Java 基础

断点调试

快捷键

shift + F9:进入断点

F7:跳入方法内

F8:跳过,逐行执行代码

shift + F8:跳出方法

Ctrl+F8:设置断点

F9:执行到下一个断点

idea如何进入JDK源码

方法1:使用快捷键:Alt + shift + F7

方法2:

基础类型

高精度(大数据计算解决方案)计数类

  1. 整数型:BigInteger
  2. 浮点型:BigDecimal

底层使用字符串来计算,具体加减乘除方法百度或查API

数组

在Java中创建的数组都是引用数组,每个初始值为null

作用域

{
    int x = 12;
    {
        int x = 96; // Illegal
        // 错误,会提示x变量已经定义过了,因为Java设计者认为这样会导致程序混乱
    }
}

静态变量的应用场景

  1. 相同类型对象要使用同一块内存空间的时候,比如学生类student需要记录学生交的学费

静态方法的应用场景

  1. 当方法中不涉及成员方法和属性的时候,可以将方法提升为静态方法,提高开发效率,比如Utils类:MathArrays

小数的四射五入

强制类型转换并不会让小数四舍五入,如果想四舍五入就得使用Math.round()方法

finalize

finalize的使用场景

finalize虽然不是每次都会调用它,但是一般用此方法查询程序的bug,虽然在Java中作用不大。

就是说当对象没有引用的时候,对象就会销毁,当对象没有清理干净,可以使用finalize方法来检测出来

/**
 * 这个程序是测试finalize方法测试,
 *  finalize方法虽然不是每次都会调用,但是一般用于此方法查询bug的所在
 *      就是说当对象不使用的时候,对象就会销毁,当对象没有清理干净,可以使用finalize方法来检测出来
 * 对象释放条件是:该对象没有引用,本代码中第二本书是没有引用的,然后显示调用System.gc()gc垃圾回收器,然后没有引用的第二本书对象就被释放了
 * 如果没有finalize方法很难找到这个bug
 */
class Book {
    private boolean checkOut ;

    public Book(boolean checkOut) {
        this.checkOut = checkOut;
    }

    public void chechIn() {
        this.checkOut = true;
    }

    @Override
    protected void finalize() throws Throwable {
        if (this.checkOut) {
            System.out.println("书对象被释放了");
        }
        super.finalize();
    }
}

public class StopFinalizeDemo {
    public static void main(String[] args) throws InterruptedException {
        Book book = new Book(false);
        book.chechIn();

        new Book(true);
        System.gc();
        Thread.sleep(1000);
    }
}

可变参数

本质

  1. 本身就是数组,也可以让数组作为实参传过去
  2. 可变参数放在形参的最后,不然会报错
  3. 一个方法参数中,只能有一个可变参数
public class VariableParameters {
    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 4, 5, 6, 7};
        VarParameters(array);   // 本质就是数组
    }

    public static void VarParameters(int... args) {
        for (int arg : args) {
            System.out.println(arg);
        }
    }

    public static void VarParameters(String str, int... args) {  // 可变参数放在最后
        for (int arg : args) {
            System.out.println(arg);
        }
    }
}

main方法中args

public class Demo {
    public static void main(String[] args) {
        for(String arg : args)
            System.out.print(arg + " ")
    }
}

args接收的是在运行改程序时,接受的参数,比如

java Demo 1, 2, 3
# 输出: 1 2 3

静态代码块和普通代码块

static代码块和普通代码块区别?

  1. static代码会在类被加载的时候执行,且只执行一次
  2. 普通代码块是每次创建类的时候都会执行

类在什么时候会被加载?

  1. new的时候会被加载
  2. 创建子类的时候,父类也会被加载
  3. 使用类的静态属性或静态方法的时候

如果static代码块优先级一致,那么会先执行谁?

执行顺序一致,那么会按照编写顺序来执行

如果普通代码块优先级一致,那么会先执行谁?

执行顺序一致,那么会按照编写顺序来执行

父类和子类中都有static代码块和普通代码块,那么执行顺序是?

确定 父类和子类都有static代码块和普通代码块,且构造方法中都有输出

执行顺序:

  1. 父类的static代码执行,按照代用(编写)顺序来执行
  2. 子类的static代码执行,按照代用(编写)顺序来执行
  3. 父类的普通代码块和普通属性
  4. 父类构造方法
  5. 子类普通代码块和普通属性
  6. 子类的构造方法

说下下面代码会输出什么

public class Demo {
    public static void main(String[] args) {
        new B("小红");
    }
}

class A {
    private static int age = getAge(); 
    private String job = getJob(); 
    private String name;

    static {
        System.out.println("A 类的static方法被调用了");
    }

    {
        System.out.println("A 类的普通代码块被调用了");
    }


    public A(String name) {
        this.name = name;
        System.out.println("A()");
        System.out.println("name = " + this.name);
    }

    public static int getAge() {
        System.out.println("获取年龄22");
        return 22;
    }
    public String getJob() {
        System.out.println("获取了工作");
        return "教师";
    }

}

class B extends A {
    private static String address = getAddress(); 

    static {
        System.out.println("B 类的static方法被调用了"); 
    }

    {
        System.out.println("B 类的普通代码块被调用了");
    }

    public B(String name) {
        super(name);
        System.out.println("B()"); 
    }

    public static String getAddress() {
        System.out.println("获取了地址");
        return "河北省";
    }
}

小结:父类和子类都有static代码块和普通代码块的时候,会先执行父类static代码块->子类static代码块->父类普通代码块->父类构造->子类普通代码块-子类构造

所以在构造方法中隐藏了

A {
    super();
    // 和执行普通代码块方法
}

final修饰

  1. 修饰类的时候,此类不能被继承
  2. 修饰方法的时候,此方法不能被重写
  3. 修饰变量的时候,此变量不能被改变,对引用来说不能修改指向

还有一种情况,这样写也是可以的

public class A {
    private static final String a;
    private final String b;
    static {
        b = "Hello";
    }
    public A {
        a = "HelloA"; //对于普通属性这样可以
    }
    {
        a = "Hello B"; // 这样也可以,但是不能和构造同时出现
    }
}

final配合static使用会出现什么情况

final 和 static配合使用类就不会被加载

class BBB {
    public final static int num = 10000;
    static {
        System.out.println("BBB 静态代码块被执行了");
    }
}
// BBB.num这样,就不会加载BBB这个类,静态代码块也不会执行

抽象类

抽象类应用场景是什么

当父类的某个方法需要声明,但是不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

举例?

比如说有一个动物类,但是每个动物吃方法的方式不同,不能具体实现,这个时候就可以使用抽象方法

接口

JDK1.8之后接口可以有static方法和默认方法

  1. static方法
  2. 默认方法,就是说加上default关键字

接口特性

  1. 接口中所有方法都是public

  2. 抽象类可以不实现接口中的方法

  3. 接口中的属性只能是fianl static修饰

  4. 接口不能继承其他类,但是可以继承多个接口

    interface A {
    }
    
    interface B {
    }
    
    interface C extends A,B {
    }
    

如果接口有多个方法,想自由实现某一个方法,如何操作

接口有多个方法,但是想自由实现某一个方法,这样的需求,实现方法就是:每个类加上default关键字,但是加上default的方法必须先来实现才行

/**
 * 测试如何选择性的实现接口的某个方法
 */
public class Demo1 {
    public static void main(String[] args) {
        new AAA() {
            @Override
            public void m1() {
                System.out.println("实现m1");
            }
        }.m2();
    }
}

interface AAA {
    public default void m1() {
        System.out.println("m1...");
    }
    public default void m2() {
        System.out.println("m2...");
    }
    public default void m3() {
        System.out.println("m3...");
    }
}

内部类

类的五大成员:属性、方法、构造器、代码块、内部类

内部类可以访问外部类的所有方法和属性(包括私有)

局部内部类

特性

  1. 可以访问外部类中所有方法和属性,包括私有
  2. 作用域就是方法或代码块中
  3. 不可以添加访问修饰符,但是可以添加final表示该类不能被继承
  4. 局部内部类定义在方法中
  5. 如果外部类和内部类成员重名的时候,就是用就近原则,如果要指定访问外部类中属性或方法,那么这样使用:外部类名.this.属性或方法
  6. 其他类中不能直接访问内部类
/**
 * 局部内部类
 */
public class Demo {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass("小红", 20);
        outerClass.m2();
    }
}


class OuterClass { // 外部类
    private String name;
    private Integer age;

    public OuterClass(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public void m1() {
        System.out.println("外部类的m1方法被调用");
    }

    public void m2() {
        // 创建局部内部类
        class InterClass { // 内部类
            private String age;
            private String job;
            public void interM3() {
                // 1. 可以直接访问外部类中所有方法和属性
                System.out.println("name = " + name);
                m1();
                // 2. 如果内部类和外部类属性或方法名重复默认是就近原则,可以使用外部类名.this.属性的方式访问外部类
                System.out.println("外部类的age " + OuterClass.this.age);
            }
        }
        InterClass interClass = new InterClass();
        interClass.interM3();
    }
}

匿名内部类[重要]

匿名内部类,就是直接new接口,然后实现该实现的方法

本质上,匿名内部类是有名字的,名字就是:外部类的名$从1开始的编号

public class Demo1 {
    public static void main(String[] args) {
        AAA a = new AAA() {
            @Override
            public void m1() {
                System.out.println("实现m1");
            }
        };
        System.out.println(a.getClass());  //输出Demo1$1
    }
}

interface AAA {
    public default void m1() {
        System.out.println("m1...");
    }
    public default void m2() {
        System.out.println("m2...");
    }
    public default void m3() {
        System.out.println("m3...");
    }
}

成员内部类

就是类中的成员是一个类,需要注意的是,在new的时候不同,记住语法格式就行。

/**
 * 成员内部类
 */
public class MemberInner {
    public static void main(String[] args) {
        // 方式一;
        OtherClass otherClass = new OtherClass();
        OtherClass.Inner inner = otherClass.new Inner();
        inner.m1();

        // 方式二:
        OtherClass.Inner inner1 = new OtherClass().new Inner();
        inner1.m1();

    }
}

class OtherClass {
    private String name;
    private Integer age;

    /**
     * 成员内部类
     */
    public class Inner {
        private String name;
        public void m1() {
            System.out.println("内部类m1....");
        }
    }

    public void m2() {
        Inner inner = new Inner();
        inner.m1();
    }
}

静态成员内部类

特性

  1. 成员类,是static
  2. 访问外部类属性或方法,直接:类名.属性或方法,因为类已经是静态的了,只能访问外部类的静态内容
/**
 * 静态成员内部类
 */
public class StaticInnerClass {
    public static void main(String[] args) {
        OtherClass.Inner inner = new OtherClass.Inner();
        inner.m1();
    }
}

class OtherClass {
    private String name;
    private static Integer age;

    public static class Inner {
        public void m1(){
            System.out.println("m1...被调用了");
        }
    }
}

常用于单例模式中线程安全的懒汉模式

/**
 * 线程安全的懒汉模式
 */
public class StaticInnerClass02 {
    public static void main(String[] args) {
        // 获取线程安全的单例模式
        OtherClass02 other = OtherClass02.getOtherClass();
    }
}

class OtherClass02 {
    private OtherClass02() {}
    
    private String name;
    private static Integer age;

    private static class Inner02 {
        private static final OtherClass02 other = new OtherClass02();
    }
    
    public static OtherClass02 getOtherClass() {
        return Inner02.other;
    }
}

注解

注解相当于类的补充信息,在Java EE中使用广泛

基本注解

  1. @Override:修饰在方法上,表示该方法是重写父类的

  2. @Deprecated:修饰在属性、方法、参数、包、类上,表示已过时,废弃,不建议使用

  3. @SuppressWarnings(str 或 {"", ""}):屏蔽编译器警告,参数可以填写抑制的信息,all是所有

all,抑制所有警告
boxing,抑制与封装/拆装作业相关的警告
cast,抑制与强制转型作业相关的警告
dep-ann,抑制与淘汰注释相关的警告
deprecation,抑制与淘汰的相关警告
fallthrough,抑制与switch陈述式中遗漏break相关的警告
finally,抑制与未传回finally区块相关的警告
hiding,抑制与隐藏变数的区域变数相关的警告
incomplete-switch,抑制与switch陈述式(enum case)中遗漏项目相关的警告
javadoc,抑制与javadoc相关的警告
nls,抑制与非nls字串文字相关的警告
null,抑制与空值分析相关的警告
rawtypes,抑制与使用raw类型相关的警告
resource,抑制与使用Closeable类型的资源相关的警告
restriction,抑制与使用不建议或禁止参照相关的警告
serial,抑制与可序列化的类别遗漏serialVersionUID栏位相关的警告
static-access,抑制与静态存取不正确相关的警告
static-method,抑制与可能宣告为static的方法相关的警告
super,抑制与置换方法相关但不含super呼叫的警告
synthetic-access,抑制与内部类别的存取未最佳化相关的警告
sync-override,抑制因为置换同步方法而遗漏同步化的警告
unchecked,抑制与未检查的作业相关的警告
unqualified-field-access,抑制与栏位存取不合格相关的警告
unused,抑制与未用的程式码及停用的程式码相关的警告

元注解

  1. @Retention:指定注解的作用范围,三种SOURCECLASSRUNTIME

    • SOURCE:编译器使用后,直接丢弃这种策略的注释
    • CLASS:编译器会把注解记录在class文件中,当运行Java程序时,JVM不会保留注解,这是默认值
    • RUNTIME:编译器将把注解记录在class文件中,当运行的Java程序的时候,JVM会保留注解,程序可以通过反射获取注解
  2. @Target:指定注解可以在哪些地方可以使用

  3. @Documented:指定该注解是否会在javadoc中体现

  4. @Inherited:子类会继承父类注解

自定义注解

注解配合反射的使用

枚举

  1. 枚举本质上还是一个类,但是他不能被继承,也不能继承其他类
  2. 枚举可以实现接口

应用场景

  1. 单例模式
  2. 已经有定义,比如一周只能有7天,一年只能有四季,这种情况下就可以使用枚举

使用

输出星期

public class EnumDemoWeek {
    public static void main(String[] args) {
        System.out.println("所有星期如下:");
        for (Week value : Week.values()) {
            System.out.println(value);
        }
        // 单个使用
        Week week = Week.MONDAY;
        System.out.println(week.name());
    }
}

enum Week {
    MONDAY("星期一"), Tuesday("星期二"), Wednesday("星期三"),
    Thursday("星期四"), Friday("星期五"), Saturday("星期六"), Sunday("星期日");

    Week(String today) {
        this.today = today;
    }

    private String today;

    @Override
    public String toString() {
        return today;
    }
}

常用方法

name() 获取当前枚举常量名
compareTo() 根据定义顺序的下标来比较枚举
ordinal() 根据当前枚举返回定义时顺序的下标
枚举名.values() 返回所有枚举
枚举名.valueOf(str) 根据str字符串,返回已有的枚举类型

javap工具使用

javap是bin目录下,根据class文件反编译的一个工具

使用方法

javap xxx.class文件

异常

异常就是程序在执行的时候,出现出错就会退出

异常分类[异常体系图]

Java中的异常分为两大类

  1. Error(错误):Java虚拟机无法解决的严重问题。如JVM系统内部错误、资源耗尽等情况。Error是验证错误,程序会奔溃,比如:StackOverflowError栈溢出和OOM
  2. Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问、试图读取不存在的文件、网络链接中断等等。Exception分为两大类:运行时异常和编译时异常
    • RuntimeException运行时异常
    • 编译时异常就是RuntimeException类之外的

运行时异常

  1. ClassCastException:类型转换异常

  2. ArrayIndexOutOfBoundsException:数组越界

  3. NullPointerException:空指针异常

  4. ArithmeticException:算数异常

  5. NumberFormatException:数值格式不正确,就是根据字符串数值转换为数值类型,如果字符串不正确就会报错

    • Integer.valueOf("123a")
      
  6. InterruptedException:中断异常

编译异常举例

  1. SQLException:操作数据库时,查询表可能触发异常
  2. IOException:操作文件时,发生的异常
  3. FileNotFoundException:文件不存在
  4. ClassNotFoundException:加载的类不存在
  5. EOFException:操作文件,到文件最末尾
  6. IllegalArgumentException:参数异常

异常处理机制

try-catch-finally

程序员在代码中捕获发生的异常,自行处理

throws

将发生的异常抛出,交给调用者(方法)来处理,最顶级的就是JVM

子类重写父类方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出异常一致,那么为父类所抛出异常的子类

语法

try {
    
}catch (Exception e) {
    // 异常会封装为一个对象,一般都是输出
    // 这里就是捕获try中异常,有的话就执行,没有的话就不执行
}finally {
    // 不管是否异常,这里是必须要执行的
}

自定义异常

  1. 继承Exception属于编译异常
  2. 继承RuntimeException属于运行时异常,一般来说继承此类

throw是在方法中抛出异常,throws是在方法上抛出异常

public class ExceptionDemo3 {
    public static void main(String[] args) throws RuntimeException {
        int age = new Scanner(System.in).nextInt();
        if (age <18 || age > 120) {
            throw new AgeRuntimeException("年龄不符合18-120岁");
        }
    }
}

class AgeRuntimeException extends RuntimeException {
    public AgeRuntimeException(String message) {
        super(message);
    }
}

常用类

包装类

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

自动装箱和拆箱

自动装箱使用的方法是valueOf(),比如Integer.valueOf()

Integer的坑

  1. 主要是Integer在创建的时候会有一个从-128~127的一个数组,如果不是new的话他们的地址是一样的
  2. Integer类型与int类型做==运算符的时候他们比较的是值
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true

Integer aa = new Integer(10);
Integer bb = new Integer(10);
System.out.println(aa == bb); // false  这里是已经new了

Integer c = 10;
int d = 10;
System.out.println(c == d); // true 比较的是地址

Integer在加载的时候创建的数组

// Integer 中成员类:IntegerCache的一个static方法
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);

// 自动装箱方法valueOf(),在这个方法中遍历使用已经创建好的数组
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
// 通过代码可以看到只有超过-128~127之间才会new新的对象

String

注:因为String类中value是由final修饰,所以字符串在常量池中

new String 与 直接赋值的区别

String str1 = "hello";
String str2 = new String("hello");

/*
str1 这个变量空间在栈中指向常量池中的字符串,如果没有hello就会创建
str2 这个变量指向堆空间地址,然后堆空间中的value常量指向常量池中字符串,如果没有hello就会创建
*/

String的坑

String str1 = "hello" + "world";
String str2 = "helloworld";
System.out.println(str1 == str2);  // true ,编译器会对str1做出优化将两个字符串合并为一个

String a = "hello";
String b = "java";
String str3 = "hellojava";
String str4 = a + b;
System.out.println(str1 == str2);  // false 
/*
String str4 = a + b; 这条语句
在底层
1. 创建StringBuffer对象空的
2. 调用StringBuffer的append()方法加入a
3. 再调用append()方法加入b
4. 最后new String对象返回
*/

String常用方法

百度

StringBuffer类

  1. StringBuffer中字符串存储在其父类AbstractStringBuilder中,且value属性不是final修饰,所以字符串存储在堆中
  2. 因为value不是final修饰,所以在扩容或删除字符中比String效率要高
  3. StringBuffer是线程安全的

构造器

public StringBuffer();   // 默认创建16个字符大小
public StringBuffer(String str);  // str.length()+16个大小空间
public StringBuffer(int capacity);// capacity大小空间

CRUD方法

// 对于下标计算,一般是[startIndex, endIndex)
// 就是说从startIndex开始,到endIndex结束,包含startIndex,不包含endIndex
// 追加
append();
// 删
delete();
// 改
replace();
// 插入
insert();

null的字符串append和构造的处理

String str = null;
StringBuffer buffer = new StringBuffer();
buffer.append(str);
System.out.println(buffer.length()); // 4
System.out.println(buffer);          // null

StringBuffer buffer2 = new StringBuffer(str); // Error NullPointerException异常
  1. 在执行append()方法的时候,如果String是个null那么会添加null字符串到value属性中

    //append会调用父类此方法
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }
    
  2. 如果是构造方法传入String对象是null,那么会报空指针异常

    public StringBuffer(String str) {
        super(str.length() + 16);  // str==null所以报异常
        append(str);
    }
    

StringBuilder类

  1. StringBuilfer类是线程不安全的,但是在insertdelete中效率是最高的
  2. StirngBuilderStringBuffer类一样都继承了AbstractStringBuilder
  3. 从使用上,StringBuilderStringBuffer方法是一样的

String与StringBuffer与StringBuidler对比

  1. 如果字符串中大量增删改查操作,单线程,推荐使用StringBuilder
  2. 如果字符串中大量增删改查操作,多线程,推荐使用StringBuffer
  3. 如果字符串很少修改,被多个线程引用推荐使用String

Math类

常用于数学计算,一般都是静态方法

常用方法

其余百度

Math.random:求随机数,是0~1之间的随机数,是小数,但是可以乘10得到想要的数字

Arrays类

  1. 有比较
  2. 查找
  3. 拷贝
  4. 比较两个数据中数据是否一致

其他常用百度或查文档

System类

常用方法

  1. exit 退出当前程序
  2. arraycopy 复制数组元素,Arrays.copyOf()底层调用的就是它
  3. currentTimeMillens:返回当前时间距离1970-1-1的毫秒
  4. gc运行垃圾回收机制

日期类

Date

没什么说的,就是使用SimpleDateFormat类,来格式化日期

public class DateDemo01 {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf  = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
        // 将日期转字符串
        System.out.println(sdf.format(new Date()));

        // 将字符串转日期
        System.out.println(sdf.parse("2020-01-01 15:40:01 星期一"));
    }
}

Calendar

  1. 是一个抽象类,且私有化了构造方法,所以只能通过getInstance()来获取Calendar对象
  2. 没有格式化方法,只能通过方法来获取具体日期,由程序员来组合使用
public class CalendarDemo1 {
    public static void main(String[] args) {
        Calendar calendar =  Calendar.getInstance();
        System.out.println(calendar);

        // 获取年份
        System.out.println(calendar.get(Calendar.YEAR));
        // 获取月份 这里需要+1,因为是从0开始的
        System.out.println(calendar.get(Calendar.MONTH) + 1);
        // 获取几号
        System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
        // 获取小时
        System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
        // 获取分钟
        System.out.println(calendar.get(Calendar.MINUTE));
        // 获取秒
        System.out.println(calendar.get(Calendar.SECOND));

        // 因为没有格式化字符方式,所以需要手动来编写
        System.out.println("当前时间:" + calendar.get(Calendar.YEAR) + "年" + calendar.get(Calendar.MONTH) + 1 + "月" +
                calendar.get(Calendar.DAY_OF_MONTH) + "日 " + calendar.get(Calendar.HOUR_OF_DAY) + "小时" +
                calendar.get(Calendar.MINUTE) + "分钟" + calendar.get(Calendar.SECOND) + "秒");
    }
}

LocalDateTime

  1. 此类是升级版本,完美支持了时间
  2. 支持时间的加减法
  3. 可以格式化输出
  4. 对时间做加法操作方法是plus开头,对时间做减法操作时minus开头
  5. 还有很多,查看用到再说
public class LocalDateTimeDemo1 {
    public static void main(String[] args) {
        // 通过now()方法,获取当前时间
        LocalDateTime dateTime = LocalDateTime.now();
        System.out.println(dateTime);

        // 进行格式化
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss E");
        String format = dtf.format(dateTime);
        System.out.println(format);

        // 单个获取年、月、日、小时、分钟、秒
        System.out.println("年" + dateTime.getYear());
        System.out.println("月" + dateTime.getMonthValue());
        System.out.println("日" + dateTime.getDayOfMonth());
        System.out.println("小时" + dateTime.getHour());
        System.out.println("分" + dateTime.getMinute());
        System.out.println("秒" + dateTime.getSecond());

        // 日期的加减,都是返回新的对象
        // 天的加
        LocalDateTime dateTime1 = dateTime.plusDays(1);
        System.out.println(dateTime1);
        // 天的减
        LocalDateTime dateTime2 = dateTime.minusDays(10);
        System.out.println(dateTime2);
    }
}

集合

集合体系图

  1. 集合类中有Collection接口和Map接口,分别处理不同的数据

Collection集合下所有类

Iterable接口是一个迭代器

Map集合下所有类

Collection

Collection接口常用方法

  1. add:添加单个元素
  2. remove:删除指定元素
  3. contains:查询某个元素是否存在
  4. size:获取元素个数
  5. isEmpty:判断是否为空
  6. clear:清空
  7. addAll:添加多个元素
  8. containsAll:查询多个元素是否存在,只要有一个不存在返回false
  9. removeAll:删除多个元素

Collection遍历的两种方式

方式一:

  1. 使用迭代器
  2. iterator.hasNext()方法是验证元素是否到默认,没有到末尾返回true
  3. iterator.next()返回当前指针指向的元素
  4. 如果iterator.next()已经指向最后一个元素,在运行此方法的话,会抛出一个NoSuchElementException异常
List list = new ArrayList();
list.add("小红");
list.add("小白");
list.add("小黑");
list.add("小黄");
list.add("小绿");

// 开始遍历
// 使用迭代器遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) {        // 快捷键生成 itit
    System.out.print(iterator.next() + "\t");
}

方式二:

  1. 使用增强for
List list = new ArrayList();
list.add("小红");
list.add("小白");
list.add("小黑");
list.add("小黄");
list.add("小绿");
for (Object item : list) {
    System.out.print(item + "\t");
}

List常用方法

  1. add:可以指定位置添加元素
  2. get:可以通过下标来获取元素
  3. indexOf(Object obj):返回obj对象在集合中的位置(第一个)
  4. lastIndexOf(Object obj):返回当前集合中末尾出现obj对象的位置
  5. remove(index):指定位置删除元素
  6. set(index, obj):替换指定位置的元素,index必须存在
  7. subList(startIndex, endIndex):从startIndex位置开始到endIndex位置结束截取其中集合,返回新集合

Vector常用方法

  1. 都是实现List接口
  2. 线程安全

LinkedList常用方法

  1. 底层是一个双向链表
  2. 维护了两个属性firstlast分别指向首页点和尾节点
  3. 每个节点Node(内部类),里面又维护了prevnextitem三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
  4. 添加、删除元素效率高

Set接口介绍

  1. 无序,添加和输出都是无序的
  2. 不允许有重复,可以有null
  3. Set接口也实现了Collection类,所以它的方法Set接口也可以使用

Set接口遍历方式

@SuppressWarnings("all")
public class SetDemo1 {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("hehe");
        set.add("haha");
        set.add("haha");
        set.add("lhf");
        set.add(null);
        set.add(null);

        // 方式一:迭代器遍历
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }

        // 方式二:for增强遍历
        for (Object o : set) {
            System.out.println(o);
        }
    }
}

LinkedHashSet

  1. LinkedHashSetHashSet的子类
  2. LinkedHashSet底层是一个LinkedHashMap,维护的是数组+双向链表

有树的基础,看他的源码

Map

  1. value可以重复
  2. keynull的话,只能有一个
  3. k-v为了方便程序员遍历,还会创建EntrySet集合,该集合存放元素类型是Entry,而一个Entry对象存储k-v的引用EntrySet<Entry<K, V>>,这样目的就是为了方便遍历,EntrygetKey()getValue()

常用方法

  1. put:添加
  2. remove:柑橘键删除映射关系
  3. get:根据键获取对应value
  4. size:获取元素个数
  5. isEmpty:判断个数是否为0
  6. clear:清除
  7. containsKey:查找键是否存在
  8. keySet:获取所有键
  9. entrySet:获取所有关系k-v
  10. values:获取所有值

Map结构遍历方式

// map结构遍历
// 1. 获取所有k-v,然后直接输出
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
for (Map.Entry<Integer, String> entry : entrySet) {
    System.out.println(entry.getKey() + "-" + entry.getValue());
}

// 2. 获取所有key,然后get(key)获取vlaue
Set<Integer> keys = map.keySet();
for (Integer key : keys) {
    System.out.println(key + "-" + map.get(key));
}

Properties

  1. Properties类继承Hashtable类且实现Map接口,也是一种键值对存储方式
  2. 常用于读取xx.properties文件,加载到Properties对象中进行读取和修改
  3. 键和值都不能为null,回报异常NullPointerException

代码实例

public class PropertiesDemo {
    public static void main(String[] args) {
        Properties properties = new Properties();

        // 插入和修改元素
        properties.put("username", "root");
        properties.put("password", "123456");
        properties.put("username", "test1");
        System.out.println(properties);

        // 单独获取值
        Object username = properties.get("username");
        System.out.println(username);

        // 删除元素
        properties.remove("username");
        System.out.println(properties);
        
        // 在实际项目中会读取配置xx.properties文件,方法是获取类加载器,通过类加载器得到当前类路径下的文件
        InputStream input = PropertiesDemo.class.getClassLoader().getResourceAsStream("jdbc.properties");
        properties.load(input);
        System.out.println(properties.get("username"));
    }
}

TreeSet和TreeMap

  1. TreeSet继承TreeMap
  2. TreeSetTreeMap构造方法都可以传递一个比较的接口Comparator,也就是说元素必须实现Comparator接口

很有意思的题-会输出什么

public class HouseWorkDemo9 {
    public static void main(String[] args) {
        HashSet<P> set = new HashSet<>();
        P p1 = new P(1001, "AA");
        P p2 = new P(1002, "BB");
        set.add(p1);
        set.add(p2);
        p1.setName("CC");
        set.remove(p1);
        System.out.println(set);

        set.add(new P(1001, "CC"));
        System.out.println(set);
        set.add(new P(1001, "AA"));
        System.out.println(set);

    }
}

@Data
@AllArgsConstructor
class P {
    private Integer id;
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        P p = (P) o;
        return Objects.equals(id, p.id) && Objects.equals(name, p.name);
    }

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

Collections工具类

Collections是一个操作SetListMap等集合的工具类

常用方法

  1. reverse(List):翻转List中元素顺序
  2. shuffle(List):对List集合元素进行随机排序
  3. sort(List):根据元素的自然顺序对指定的List集合按升序排序
  4. sort(List, Comparator):根据指定的Comparator实现类对List进行排序
  5. swap(List, int, int):将指定List集合中的i元素和j元素进行替换
  6. max(Collection):根据元素的自然排序,返回最大元素
  7. max(Collection, Comparaotr):根据指定Comparaotr顺序,返回最大值
  8. min(Collection):根据元素的自然排序,返回最小元素
  9. mix(Collection, Comparaotr):根据指定Comparaotr顺序,返回最小值
  10. frequency(Collection, Object):返回指定集合中指定元素出现的次数
  11. copy(List dest, List src):将src的内容复制到dest
  12. replaceAll(List, Object oldVal, Object newVal):使用新值替换List对象所有旧值

泛型

  1. 参数话类型

基础语法

class Person<T> {
    T t;
    public Person(T t) {
        this.t = t;
    }
    public T getT() {
        return t;
    }
    public void f() {
        System.out.println(t.getClass());
    }
}

自定义泛型

自定义泛型注意事项

  1. 普通成员可以使用泛型(属性、方法)
  2. 使用泛型的数组,不能初始化
  3. 静态方法或静态属性中不能使用类的泛型,因为无法确定申请 内存空间大小
  4. 泛型类的类型,是在创建对象时确定
  5. 如果在创建对象时没有指定类型,那么默认是Object

泛型接口

  1. 在继承接口或实现接口的时候,需要指定类型
  2. 没有指定类型,默认为Object
public class InterfaceGenericDemo1 {
}

interface IUsd<T, E> {
    public void show(T t, E e);
    public T getT(T t);
}

// 在继承中使用泛型,需要在继承的时候指定类型,如果不指定那么是Object类型
interface U extends IUsd<String, Integer> {
}

// 在实现中使用泛型,需要在实现的时候指定类型,如果不指定那么是Object类型
class A implements IUsd<Double, Float> {
    @Override
    public void show(Double aDouble, Float aFloat) {

    }

    @Override
    public Double getT(Double aDouble) {
        return null;
    }
}

泛型方法

  1. 泛型方法可以在普通类方法中使用,可以在泛型方法中使用
  2. 在调用的时候,泛型方法必须确定下来24
  3. 如果方法前面有没泛型,那么这是使用了泛型并不是泛型方法8
  4. 泛型方法,如何指定类型?在调用的时候直接输入参数就可以指定22
  5. 泛型方法可以使用类创建的时候指定的,也可以使用自定义的9
// 在普通类中使用
class A<E> {
    //泛型方法
    public<T, R> void f(T t, R r) {
        
    }
    // 使用了泛型,不是泛型方法
    public void Test(E t){}
    public<K> void hello(K k){}
}

// 在泛型类中使用
class Fish<T, R> {
    public<T, R> void eat(T t, R r) {
        
    }
}
// 使用:在调用的时候,类型必须确定下来
public class Demo {
    public void main(String[] args) {
        A a = new A();
        a.f("小白", 22); // 这个时候就已经确定类型了
        Fish<String, Double> fish = new Fish<>();
        fish.eat("小白", 22.1D);
        // 像第9行代码直接调用即可
        a.hello("String");
    }
}

泛型的继承和通配符

  1. 泛型是不可用继承的

    List<Object> list = new ArrayList<String>(); //错误的 String不可用这么用
    
  2. <?>:支持任意泛型的类型

  3. <? extends A>:支持A类以及A类的子类, 规定了泛型的上限

  4. <? super A>:支持A类以及A类的父类,规定了泛型的下限

线程

基础概念

并发:同一时刻,多个任务交替执行,造成一种貌似同时执行的错觉,简单来说,单核CPU实现的多任务就是并发

并行:同一时刻,多个任务同时执行,多核CPU可以实现并行

创建线程两种方式

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,重写run方法

全部是调用start方法来启动线程的

结论

在启动程序的时候,会创建进程,然而进程的执行,需要主线程也就是执行main方法的一个主线程,当调用实现Runnable接口的对象的start方法的时候就会创建一个子线程,主线程不会阻塞,继承执行

子线程还可以开子线程

方式一继承Thread类

代码实例

/**
 * @version 1.0
 * @date 2021年9月27日20:03:17
 * 通过Thread继承,使用线程
 * 开启一个线程,每隔一秒,在控制台输出喵喵喵
 * 当输出80次喵喵喵,结束该线程
 */
public class ThreadDemo1 {
    public static void main(String[] args) throws Exception {
        Thread thread = new Cat();
        thread.start();
        System.out.println("我要关闭了");
    }
}

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

JConsole的使用

  1. 使用JConsole可以监控线程的执行情况,是Java自带的程序

使用方法

  1. 在主线程和子线程两个地方循环多次输出,然后每输出一次主线程和子线程都睡一秒
  2. 在控制台输入JConsole来选择本地进程观察情况
public class ThreadDemo1 {
    public static void main(String[] args) throws Exception {
        Thread thread = new Cat();
        thread.start();
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程i=" + i + " 主线程名称=" + Thread.currentThread().getName());
            Thread.sleep(1000L);
        }
    }
}

class Cat extends Thread {
    int currentNum = 0;
    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("喵喵喵 currentNum=" + currentNum + " 线程名称=" + Thread.currentThread().getName());
                Thread.sleep(1000L);
                currentNum++;
                if (currentNum > 80)
                    break;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行程序,在控制台输入jconsole,打开JConsole,连接该程序的进程,之后查看线程

连接完成之后打开点击线程

方式二实现Runnable接口

public class RunnableDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
        System.out.println("主线程还在工作");
        for (int i = 0; i < 8; i++) {
            System.out.println(i );
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyRunnable implements Runnable {
    private int currentNum = 0;

    @Override
    public void run() {
        System.out.println("子线程名=" + Thread.currentThread().getName());
        while (true) {
            System.out.println("Hi");
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (++currentNum > 10) {
                break;
            }
        }
    }
}

为什么调用start方法?

继承Thread or实现Runnable接口区别

  1. 从设计上来看,都是创建线程,没有任何区别
  2. 实现Runnable接口方式更适合多个线程共享一个资源的情况,并避免了单继承的实现

主线程通知子线程终止

  1. 用一个变量来表示子线程的执行,主线程在操作子线程中的变量,从而达到子线程的终止
public class SonThreadStop {
    public static void main(String[] args) {
        ThreadStop threadStop = new ThreadStop();
        new Thread(threadStop).start();

        // 主线程执行10秒退出子线程
        try {
            Thread.sleep(1000 * 10);
            threadStop.setFlg(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadStop implements Runnable {
    private int current = 0;
    private boolean flg = true;

    @Override
    public void run() {
        while (flg) {
            System.out.println("子线程在运行 current=" + current++);
        }
    }

    public void setFlg(boolean flg) {
        this.flg = flg;
    }
}

常用方法

  1. setName:设置线程名称
  2. getName:返回该线程的名称
  3. start:使该线程开始执行
  4. run:调用线程对象run方法
  5. setPriority:更改线程优先级
  6. getPriority:获取线程优先级
  7. sleep:睡眠
  8. interrupt中断线程,会抛出InterruptedException异常,常使用此方法来提前结束线程睡眠
/**
 * @date 2021年10月5日16:01:13
 * 提前让线程结束休眠的方式
 * 通过interrupt方法来让线程抛出InterruptedException异常,可以提前结束休眠,然后执行代码
 */
public class ThreadMethod1 {
    public static void main(String[] args) throws InterruptedException {
        Th th = new Th();
        th.setName("小新");
        th.start();

        // 执行4秒后,提前结束线程(小新)的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(4000);
            th.interrupt();  // 抛出InterruptedException异常,然后Th线程中捕获,从而达到线程的提前结束休眠
        }
    }
}

class Th extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " 在吃包子~ i=" + i);
            }
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("线程抛出了异常");
            }
        }
    }
}
  1. yield:线程的礼让,让出CPU,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功。如果资源充足也不太会礼让成功

  2. join:线程的插队,插队的线程一旦插队成功,则肯定先执行万插入的线程的所有任务。例如

    main创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1秒输出hi,输出20次。
    要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续
    
/**
 * @date 2021年10月5日16:20:21
 * 线程的插队,join.
 * 前提:有两个线程t1,t2
 * 当t1线程正在执行的之后,如果执行到t2.join()那么t1线程就会挂起,等待t2线程一定执行完毕才接着执行t1线程
 *
 * main创建一个子线程,每隔1s输出hello,输出20次,主线程没给个1秒输出hi,输出20次。
 * 要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续
 */
public class ThreadMethodJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread hello = new Hello();
        hello.start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " hi i=" + i);
            Thread.sleep(1000);
            if (i == 5) {
                System.out.println("main线程让子线程先执行");
                hello.join();
                System.out.println("子线程执行完,让主线程执行");
            }
        }
    }
}

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

线程的优先级

  1. MIN_PRIORITY=1:最小优先级

  2. NORM_PRIORITY=5:默认优先级

  3. MAX_PRIORITY=10:最大优先级

守护线程

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

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

如何创建守护线程?

t2.setDaemon(true)

/**
 * @date 2021年10月5日17:24:26
 * 子线程如果是个死循环,那么它会一直执行,这个时候主线程执行完毕,子线程还在执行
 * 目的:当主线程执行完毕,子线程结束执行
 */
public class ThreadDaemon {
    public static void main(String[] args) throws InterruptedException {
        Thread t2 = new T2();
        // 设置t2为守护线程,当主线程执行完毕,子线程终止执行
        t2.setDaemon(true);
        t2.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("小绿在工作....");
            Thread.sleep(1000L);
        }
        System.out.println("小绿工作完成.....!!!");
    }
}

class T2 extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("小红和小黄在聊天");
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程的声明周期

Thread.State

  1. NEW:尚未启动的线程,刚刚创建的线程,还未使用start方法
  2. RUNNABLE:可运行
  3. BLOCKED:被阻塞等待线程
  4. WAITING:等待
  5. TIMED_WAITING: 超时等待
  6. TERMINATED:退出的线程

线程同步synchronized-互斥锁

  1. 在多线程中,一些敏感数据不允许同时被多个线程访问,此时就需要使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
  2. 也可以这样理解:线程同步,就是当一个线程访问在对内存操作的时候,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对内存地址进行操作

Synchronized同步方法的语法

// 1. 同步代码块
synchronized(对象) {  // 得到对象的锁,才能操作同步代码
    // 需要被同步的代码
}

// 2. 同步方法,就是访问此方法时只能有一个线程访问
public synchronized void m(String name) {
    // 需要被同步的代码
}

基本介绍

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性

  2. 每个对象都对应一个可称为互斥锁的标记,这个标记用来保证同一时刻,只能有一个线程访问该对象

  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该独享在任一时刻只能由一个线程访问

  4. 同步的局限性:导致程序执行效率降低

  5. 同步方法(非静态)的锁是this,也可以是其他对象(要求是同一个对象)this

    public synchronized void m()
    // 锁在this上
    
  6. 同步方法(静态)的锁为当前类本身类.class

    public static synchronized void m();
    // 锁在类上
    

细节

  1. 需要分析上锁的代码
  2. 选择同步代码块或同步方法
  3. 要求多个线程的锁对象为同一个

车票售卖问题

/**
 * @date 2021年10月6日14:20:20
 * 100张火车票,三个线程同时售卖问题
 * 不管是Thread实现线程或者是Runnable线程,都会造成线程同步问题,如这个案例,票会卖超,或者卖出相同的票
 * 这次使用同步代码块解决此问题
 * 代码锁
 */
public class TrainTickets02 {
    public static void main(String[] args) {
        Train train = new Train();
        new Thread(train).start();
        new Thread(train).start();
        new Thread(train).start();
    }
}

class Train1 implements Runnable {
    private Integer currentNum = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (currentNum) {
                System.out.println("出售一张票 第" + currentNum-- + " 线程名" + Thread.currentThread().getName());
            }
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (currentNum <= 0)
                return;
        }
    }
}

死锁

多个线程都占用了对方的锁资源,但不肯想让,导致了死锁,在编程上是一定要避免的

public class Demo() {
    public static void main(String[] args) {
        new T1(true).start();
        new T1(true).start();
    }
}

class T1 extends Thread {
    static Object o1 = new Object(); // 保证多线程,共享的是一个对象这里使用static
    static Object o2 = new Object();
    boolean flag;
    
    public T1(boolean flag) {
        this.flag = flag;
    }
    
    @Override
    public void run() {
        if(flag) {
            synchronized(o1) {
                System.out.println(Thread.currentThread().getName() + "进入1");
                    synchronized(o2) {
                    System.out.println(Thread.currentThread().getName() + "进入2");
            	}
            }
        }else {
            synchronized(o2) {
                System.out.println(Thread.currentThread().getName() + "进入3");
                    synchronized(o1) {
                    System.out.println(Thread.currentThread().getName() + "进入4");
            	}
            }
        }
    }
}

释放锁

以下操作会释放锁

  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到breakreturn
  3. 当前线程在同步代码块、同步方法中出现了未处理的异常ErrorException,导致异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

以下操作不会释放锁

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

IO

文件流

文件在程序中是以流的形式来操作的

流:数据在数据源(文件)和程序(内存)之间的通道

输入流:数据从文件读到内存的路径

输出流:内存的数据写到文件(磁盘)

一切都是以内存为中心

File的使用

构造方法+创建文件

File file = new File("E:/news1.txt");
if (file.createNewFile()) {
    System.out.println("new1.txt 文件创建成功");
}

File file2 = new File("E:", "news2.txt");
if (file2.createNewFile()) {
    System.out.println("news2.txt 文件创建成功");
}

File file3 = new File("E:");
File file4 = new File(file3, "/news3.txt");
if (file4.createNewFile()) {
    System.out.println("news3.txt 文件创建成功");
}

获取文件相关信息

/**
 * @date 2021年10月8日21:29:30
 * 使用File类来获取文件的信息
 */
public class GetFileInformation {
    /**
     * 文件操作
     * 1. 获取文件名称 getName
     * 2. 获取文件绝对路径 getAbsolutePath
     * 3. 获取父级目录 getParent
     * 4. 获取文件大小(字节) length
     * 5. 判断文件是否存在 exists
     * 6. 判断文件是否是个目录 isFile
     * 7. 判断是否是一个目录 isDirectory
     */
    @Test
    public void FileInformation() {
        File file = new File("E:/news1.txt");
        System.out.println("文件名称为:" + file.getName());
        System.out.println("文件的绝对路径:" + file.getAbsolutePath());
        System.out.println("文件的父级目录:" + file.getParent());
        System.out.println("文件大小(字节):" + file.length());//0字节
        System.out.println("文件是否存在:" + file.exists());//t
        System.out.println("该文件是否是一个文件:" + file.isFile());//t
        System.out.println("是否是一个目录:" + file.isDirectory());//f
    }

    /**
     * 目录操作
     * 1. 创建一级目录 mkdir
     * 2. 创建多级目录 mkdirs
     * 3. 删除目录(只能删除空目录) delete
     * 4. 获取目录下所有文件对象 listFiles
     */
    @Test
    public void DirectoryOperation() {
        File file = new File("E:/aaa/bbb/ccc");
        // mkdir 创建一级目录,如果目录存在返回false
        System.out.println("创建一级目录" + file.mkdir());
        // mkdirs 创建多级目录,如果目录存在返回false
        System.out.println("创建多级目录" + file.mkdirs());
        // delete 只能删除空目录,如果目录中有文件或目录那么删除失败
        System.out.println("删除空目录" + file.delete());

        File[] files = file.listFiles();
        for (File file1 : files) {
            System.out.println(file1.getName());
        }
    }
}

概念

概念

  1. I/OInput/Output的缩写,I/O技术是非常实用的技术,用于处理数据的传输。如读/写文件,网络通讯等
  2. Java程序中,对于数据的输入和输出操作以流(Stream)的方式进行
  3. Java.io包下提供了各种流类的接口,用以获取不同种类的数据,并通过方法输入或输出数据
  4. 输入Input,是指,硬盘上的文件读到内存中去
  5. 输出Output,是指,内存中的数据写到硬盘上

IO的分类

根据各种不同的方式分类

  1. 按操作数据单位不同分为:字节流(8bit),字符流
  2. 按数据流的流向不同分为:输入流、输出流
  3. 按流的角色不同分为:字节流,处理流、包装流
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

Java的IO流涉及40多个类,实际上非常规则,都是从如上4个抽象基类的子类

子类都是以上面4个基类为后缀

节点流和处理流

节点流和处理流的概念

  1. 节点流可以从一个特定的数据源读写数据,如FileReaderFileWriter

  1. 处理接也叫包装流,是"连接"在已存在的节点流或处理流上,为程序提供更为强大的读写功能,也更加灵活,如BufferedReaderBufferedWriter

包装流的意义就是简化操作,不管你流对象是什么,只要new包装流正确我们操作的对象就是包装流了

如图,上面是节点流,下面是对应的包装流

如代码:表示可以接受Reader的所有子类

public class BufferedReader extends Reader {

    private Reader in;
  1. 节点流是底层流/低级流,直接跟数据源相连
  2. 处理流包装节点流,即可消除不同节点流的实现差异,也可以提供更方便的方法完成输入输出
  3. 处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连

优点体现

  1. 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
  2. 操作的便捷:包装流可能提供了一系列便捷的方法来一次输入输出最大批量的数据,使用更加灵活

序列化和反序列化-ObjectInput/Output

序列化和反序列化的介绍

  1. 序列化就是在保存对象的还是,保存数据类型的值和数据类型
  2. 反序列化就是根据数据类型和数据类型的值,创建对应的对象
  3. 需要序列化,需要将类实现两个的其中一个接口SerializableExternalizable的其中一个,推荐使用Serializable,因为另一个需要实现接口方法

序列化和反序列化的使用细节

  1. 读写顺序一致

  2. 要求实现序列化或反序列化对象,需要实现Serializable接口

  3. 序列化的类建议添加serialVersionUID,表示版本,提高版本的兼容性

    // 比如之前的一个类中没有color这个属性,然后就序列化并写入文件里了
    // 在反序列的时候如果有加上serialVersionUID这个属性,然后添加了color这个顺序,JVM就不会认为这个类是一个全新的类了
    static final long serialVersionUID = 1L;
    
  4. 序列化对象时,默认所有属性都进行序列化,除了statictransient修饰的成员

  5. 序列化对象时,要求里面的属性也支持序列化

  6. 序列化支持继承性,这是肯定的,父类实现了某个接口,子类也必然需要实现它

输入流

字节输入流-InputStream

FileInputStream

构造+读方法的示例

/**
 * @date 2021年10月8日22:34:25
 * 使用InputStream读取字节,
 * 介绍构造方法、常用方法
 */
public class DemoInputStream {
    /***
     * 构造方法
     */
    @Test
    public void createInputStream() throws FileNotFoundException {
        String path = "E:/news.txt";
        File file = new File(path);

        FileInputStream inputStream1 = new FileInputStream(path);
        FileInputStream inputStream2 = new FileInputStream(file);
    }

    /**
     * 读字节方法
     * 一次读取一个字节
     * read() 方法读取一个字节,返回读取到的内容(int),如果到了末尾返回-1
     * 缺点:一次读取一个字节,效率极低
     * 推荐使用read(byte[] b)
     */
    @Test
    public void readByte1() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("E:/hello.txt");
        int readDate = -1;
        // 每次读取一个字节并输出
        while ((readDate = fileInputStream.read()) != -1) {
            System.out.print((char) readDate);
        }
        // 重要的是每次要关闭流
        fileInputStream.close();
    }

    /**
     * 一次读取多个字节的方法使用
     * read(Byte[] b)
     *  如果读取到了数据,那么就返回实际读到的字节个数
     *  如果到了默认就会返回-1
     */
    @Test
    public void readBytes() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("E:/hello.txt");
        byte[] buf = new byte[8];
        int readLen = -1;
        while ((readLen = fileInputStream.read(buf)) != -1) {
            System.out.print(new String(buf, 0, readLen));
        }
        // 重要的是每次要关闭流
        fileInputStream.close();
    }
}

BufferedInputStream-包装流

/**
 * @date 2021年10月14日21:03:32
 * 使用BufferedInputStream和BufferedOutputStream完成对二进制文件的copy,如图片或者音乐
 * copy文本文件也是可以的
 */
public class DemoBinaryFileCopy {
    public static void main(String[] args) throws IOException {
//        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("G:\\下载\\Demonic Mirror.mp4"));
//        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:/copy.mp4"));

        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("E:/news1.txt"));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:/news2.txt"));

        int dataLen = -1;
        byte[] data = new byte[8*1024];
        while ((dataLen = bufferedInputStream.read(data)) != -1) {
            bufferedOutputStream.write(data, 0, dataLen);
        }

        bufferedInputStream.close();
        bufferedOutputStream.close();
    }
}

ObjectInputStream-包装流

构造方法+常用方法

/**
 * @date 2021年10月14日22:14:02
 * 反序列化
 * 把DemoObjectOutputStream写入到文件里的对象读取出来并识别为对象
 *
 * 构造方法
 *      ObjectInputStream(InputStream in)
 * 常用方法
 *      readBoolean() 读取布尔直
 *      readByte() 读取一个字节
 *      readChar() 读取一个字符
 *      readDouble() 读取双精度
 *      readFloat() 读取单精度
 *      readInt() 读取int类型
 *      readLong() 读取Long类型
 *      readObject() 读取一个对象
 *      readShort()
 *      readUTF() 读取字符串
 */
public class DemoObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E:/data.dat"));
        // 读取的时候注意顺序(和序列化的顺序一直)
        int i = objectInputStream.readInt();
        Dog o = (Dog) objectInputStream.readObject();
        System.out.println(i);
        System.out.println(o);
        objectInputStream.close();
    }
}

字符输出流-Reader

FileReader

构造+reader方法使用

注意重点22行代码

一定要使用new String(char, off, len) 方法来初始化字符串不然会多输出字符的[重要]

/**
 * @date 2021年10月10日22:34:51
 * FileReader的使用,构造方法+读方法
 */
public class DemoFIleReader {
    /**
     * 构造方法使用
     * FileReader(File file)
     * FileReader(String fileName)
     * <p>
     * reader方法使用
     * int read() 读取一个字符,如果没有返回-1
     * int read(char[] buf) 读取多字符放入buf中,返回读取字符个数,如果没有返回-1
     * int read(char[] buf, int off, int len) 读取多字符放入buf中,从off偏移量,len读取最大值,返回读取字符个数,如果没有返回-1
     */
    @Test
    public void createFileReader() throws IOException {
        FileReader fileReader = new FileReader("E:/news1.txt");
        char[] buf = new char[5];
        int bufLen = 0;
        while ((bufLen = fileReader.read(buf)) != -1) {
            // 一定要使用new String(char, off, len) 方法来初始化字符串不然会多输出字符的[重要]
            System.out.print(new String(buf, 0, bufLen));
        }
        fileReader.close();
    }
}

BufferedReader-包装流

缓存空间是8M

针对Reader的包装流(处理流),简单的来说就是对节点流增强,实际操作文件的对象还是节点流,在不使用的时候,只要关闭包装流即可

构造+方法的使用

/**
 * @date 2021年10月14日18:28:50
 * 本次案例是 使用包装刘,实际对象是FileReader操作文件,BufferedReader来操作FileReader,提供了增强方法
 *
 * 构造方法
 * BufferedReader(Reader in) 传入Reader流的子类
 *
 * 增加了 String readLine() 读取一行数据,如果读取到末尾返回null,虽然读取一行,但是不会把换行符也读取
 */
public class DemoBufferedReader {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("E:/news1.txt"));
        String str = null;

        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }

        // 最后只需要关闭包装流对象即可
        bufferedReader.close();
    }
}

InputStreamReader-包装流转换流

介绍 InputStreamReaderOutputStreamWriter

  1. InputStreamReaderReader的子类,可以将InputStream字节流包装为Reader字符流
  2. OutputStreamReaderWriter的子类,可以将OutputStream字节流包装为Writer字符流
  3. 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换为字符流
  4. 在转换过程中可以指定编码,如:utf8gbk

需要场景

在Java项目中默认的读取编码是UTF8,如果在对文件进行操作的话,文件不是UTF8编码,那就会出现乱码的情况,例如

/**
 * @date 2021年10月16日15:04:10
 * 问题的引出,Java项目默认是UTF8编码,如果读取的文件不是UTF8编码,那么会出现乱码问题
 InputStreamReader(InputStream in, String charsetName) 第二个参数可以指定编码
 */
public class DemoInputStreamReader {
    /**
     * 乱码的读取
     * news1.txt 文件的编码是gbk
     * 程序输出结果
     * xiaoxinС�� 明显乱码,解决办法:指定编码
     */
    @Test
    public void GarbledCode() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("E:/news1.txt"));
        System.out.println(bufferedReader.readLine());
        bufferedReader.close();
    }

    /**
     * 指定编码读取,文件,解决编码问题
     */
    @Test
    public void SuccessCode() throws Exception {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("E:/news1.txt"), "gbk"));
        System.out.println(bufferedReader.readLine());
        bufferedReader.close();
    }
}

输出流

字节输出流-OutputStream

FileOutputStream

构造+写方法示例

/**
 * @date 2021年10月10日18:43:07
 * FIleOutputStream 基本使用
 * 构造方法
 * 写方法
 */
public class DemoOutputStream {
    /**
     * FileOutputStream 构造方法介绍
     *  1. 特性:如果文件不存在那么就自己创建
     *  2. 一个参数的构造方法默认是覆盖方式写,如果想要追加方式,那么第二个参数设置为ture
     *      如:FileOutputStream(File file, true)
     *  FileOutputStream(File file)
     *  FileOutputStream(File file, boolean append)
     *  FileOutputStream(String name)
     *  FileOutputStream(String name, boolean append)
     */
    @Test
    public void createOutputStream() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("E:/news1.txt");
        fileOutputStream.write("hello,world".getBytes());
        fileOutputStream.close();
    }

    /**
     * 写方法介绍
     *  write(byte[] b) 将字节数组写出磁盘
     *  write(byte[] b, int off, int len) 将字节数组写出磁盘,off是偏移位置,len是写入字节的长度
     *  write(int b) 写入一个ASCII或int类型值
     */
    @Test
    public void writeDemo() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("E:/news1.txt");
        byte[] bytes = "Hello, java".getBytes();
        fileOutputStream.write(bytes, 0, bytes.length);
    }
}

BufferedOutputStream

/**
 * @date 2021年10月14日21:03:32
 * 使用BufferedInputStream和BufferedOutputStream完成对二进制文件的copy,如图片或者音乐
 * copy文本文件也是可以的
 */
public class DemoBinaryFileCopy {
    public static void main(String[] args) throws IOException {
//        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("G:\\下载\\Demonic Mirror.mp4"));
//        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:/copy.mp4"));

        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("E:/news1.txt"));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:/news2.txt"));

        int dataLen = -1;
        byte[] data = new byte[8*1024];
        while ((dataLen = bufferedInputStream.read(data)) != -1) {
            bufferedOutputStream.write(data, 0, dataLen);
        }

        bufferedInputStream.close();
        bufferedOutputStream.close();
    }
}

ObjectOutputStream

需求

  1. int num = 10这个int类型的数据保存在文件中,不是保存数字10,而是记录类型和值,并且还能够从文件中读且直接恢复成int num=10
  2. Dog dog = new Dog("小黄", "黄色")这个Dog类型的数据保存在文件中,不是保存对象里的值,而是记录类型和值,并且还能够从文件中读且直接恢复成Dog dog = new Dog("小黄", "黄色")

综合上面需求,能够将基本类型或者对象进行序列化或反序列化的操作

/**
 * @date 2021年10月14日21:52:19
 * 将基本类型和一个Dog(name, age)对象保存在data.dat文件中
 * 构造方法
 *      ObjectOutputStream(OutputStream out) 需要OutputStream子类
 * 常用方法
 *      write(byte[] buf) 写入一个字节数组
 *      write(byte[] buf, int off, int len) 写入一个字节数组,从off开始写入len个
 *      write(int val) 写入一个字节
 *      writeByte(int val) 写入一个字节8位
 *      writeBoolean(boolean val) 写入一个布尔值类型
 *      writeBytes(String str) 写入一个字符串对象
 *      writeChar(int val) 写入一个char类型
 *      writeChars(String str) 写入一个chars对象
 *      writeDouble(double val) 写入一个Double对象
 *      writeObject(Object obj) 写入一个对象
 *
 *      flush() 刷新流
 *      close() 关闭流
 */
public class DemoObjectOutputStream {
    public static void main(String[] args) throws IOException {
        int num = 10;
        Dog dog = new Dog("小黄", 5);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/data.dat"));
        objectOutputStream.writeInt(num);
        objectOutputStream.writeObject(dog);

        objectOutputStream.flush();
        objectOutputStream.close();
    }
}

@Data
@AllArgsConstructor
class Dog implements Serializable /*因为要实例化,所以需要实现这个接口*/ {
    private String name;
    private int age;
}

字符输出流-Writer

FileWriter

注意

FileWerter使用后,必须使用close()flush()方法来关闭流或者刷新流,否则写入不到执行的文件

构造+writer方法使用

/**
 * @author 小新
 * @version 1.0
 * @date 2021年10月10日22:53:38
 * FileWriter构造+writer方法使用
 * 构造 【如果文件不存在,默认自动创建文件】
 *      FileWriter(File file)
 *      FileWriter(File file, boolean append) 如果append为ture那么是追加模式
 *      FileWriter(String fileName)
 *      FileWriter(String fileName, boolean append) 如果append为ture那么是追加模式
 * writer
 *      write(char[] buf) 将字符数组写入
 *      write(char[] buf, int off, int len) buf字符数组从off开始写入len个字符
 *      write(int c) 写入ASCII或int的值
 *      write(String str) 将字符串写入
 *      write(String str, int off, int len) str字符串从off开始的下标写入len个字符
 *
 */
public class DemoFileWriter {
    @Test
    public void createFileWriter() throws IOException {
        FileWriter fileWriter = new FileWriter("E:/hello.txt");
        fileWriter.write("雨过之后,定见彩虹");
        // 一定要刷新或者直接关闭流,不然不会将数据写到文件到[重要]
        fileWriter.flush();
        fileWriter.close();
    }
}

BufferedWriter-包装流

缓存空间是8M

构造方法和常用方法,至于包装流说明看包装流那个标题

/**
 * @date 2021年10月14日20:41:32
 * 案例:使用BufferedWriter将 "我要学习Java且努力坚持下去" 写到一个文件里面
 */
public class DemoBufferedWriter {
    public static void main(String[] args) throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("E:/comeOn.txt"));
        bufferedWriter.write("我要学习Java且努力坚持下去");
        // 插入一个换行符(和系统相关的换行符)
        bufferedWriter.newLine();
        bufferedWriter.write("我要学习Java且努力坚持下去");

        // 一定要关闭或者刷新不然数据写不到文件里面
        bufferedWriter.flush();
        bufferedWriter.close();
    }
}

文本文件的copy

/**
 * @date 2021年10月14日20:48:21
 * 使用BufferedReader和BufferedWriter完成文本文件copy
 */
public class FileCopy {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader("E:/news1.txt"));
        BufferedWriter writer = new BufferedWriter(new FileWriter("E:/NewNews1.txt"));
        String str = null;
        // readLine() 虽然读取一行,但是不会把换行符也读取
        while ((str = reader.readLine()) != null) {
            writer.write(str);
            writer.newLine();
        }

        // copy完毕,一定要刷新输出流, 虽然直接close()也可行
        writer.flush();
        reader.close();
        writer.close();
    }
}

OutputStreamWriter-包装流转换流

指定编码读取文件

/**
 * @date 2021年10月16日15:26:40
 * 指定编码读取文本文件
 * OutputStreamWriter(OutputStream out, String charsetName) 第二个参数指定编码
 */
public class DemoOutputStream {
    public static void main(String[] args) throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/news1.txt"), "gbk"));
        bufferedWriter.write("hello 小新");
        bufferedWriter.close();
    }
}

标准输出流和标准输入流

概念

标准输出流:System.out, 类型是:PrintStream 默认设备是:显示器

标准输入流:System.in, 类型是:BufferedInputStream 默认设备是:键盘

/**
 * @date 2021年10月16日13:58:24
 * 标准输入流和标准输出流饿演示
 * System.in 和 System.out
 * BufferedInputStream 和 PrintStream
 */
public class DemoPrintStream {
    public static void main(String[] args) throws IOException {
        System.out.println(System.in.getClass());
        System.out.println(System.out.getClass());

        InputStream ip = System.in;
        PrintStream printStream = System.out;
        int dataLen = -1;
        byte[] data = new byte[8 * 1024];
        dataLen = ip.read(data);
        printStream.write(data, 0, dataLen);
        ip.close();
        printStream.close();
    }
}

打印流

  1. 打印流只有输出流,PrintStreamPrintWriter
  2. 可以修改默认标准输出设备(如修改为文件),System.setOut(new PrintStream("E:/news1.txt")),接下来System.out.println("Hello, Java!");的打印回到E:/news1.txt文件中去
/**
 * @date 2021年10月16日19:33:20
 * 打印流,PrintStream 和 PrintWriter 打印流只有输出流
 * 1. 打印流默认输出标准设备是屏幕
 * 2. 可以修改打印流的位置,默认是屏幕,我们可以修改为文件,
 *	System.setOut(new PrintStream("E:/news1.txt")); 这样就是把标准输出设备更换为文件了
 *   执行到sout会输出到E:/news1.txt的文件中去
 */
public class DemoPrintStream {
    /**
     * 字节打印流
     */
    @Test
    public void DefaultEquipment() throws IOException {
        PrintStream printStream = System.out;
        printStream.write("Hello, 小新".getBytes(StandardCharsets.UTF_8));
        printStream.close();
    }

    /**
     * 修改打印流位置,位置修改为文件
     * @throws IOException
     */
    @Test
    public void DefaultFile() throws IOException {
        System.setOut(new PrintStream("E:/news1.txt"));
        System.out.println("Hello, Java!"); // 这个字符串会输出到E:/news1.txt 文件中去
    }
}

字符打印流

/**
 * @date 2021年10月16日19:51:17
 * PrintWriter的使用
 * 打印字符流
 */
public class DemoPrintWriter {
    public static void main(String[] args) {
        PrintWriter printWriter = new PrintWriter(System.out);
        printWriter.print("我爱你呀");
        // 将数据写入到文件中(此代码中是屏幕),一定要刷新或者close()才会有数据写入
        printWriter.flush();
        printWriter.close();
    }

    @Test
    public void test() throws IOException {
        PrintWriter printWriter = new PrintWriter("E:/news1.txt");
        printWriter.write("Hello, 小白");
        // 将数据写入到文件中(此代码中是屏幕),一定要刷新或者close()才会有数据写入
        printWriter.close();
    }
}

Properties

/**
 * @date 2021年10月16日22:09:31
 * 1. 使用Properties类读取mysql.properties 配置文件
 * 2. 使用Properties类将K-V添加到新文件mysql2.properties
 * 3. 使用Properties类完成对mysql.properties 配置文件的读取并修改
 *      读取在修改的话有一个坑,如果调用setProperties(k, v) 修改的话,修改后的内容是存储在内存里的
 *      如果想要更新到文件,需要调用properties.store(OutputStream) 方法才可以
 */
public class DemoProperties1 {
    /**
     * 1. 使用Properties类读取mysql.properties 配置文件
     * @throws IOException
     */
    @Test
    public void practice01() throws IOException {
        Properties properties = new Properties();
        properties.load(DemoProperties1.class.getClassLoader().getResourceAsStream("mysql.properties"));
        String ip = properties.getProperty("ip");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");

        System.out.println("ip=" + ip);
        System.out.println("username=" + username);
        System.out.println("password=" + password);
    }

    /**
     * 2. 使用Properties类将K-V添加到新文件mysql2.properties
     * @throws IOException
     */
    @Test
    public void practice02() throws IOException {
        Properties ps = new Properties();

        // 指定编码
        ps.setProperty("root", "lixiaoxin");
        ps.setProperty("pwd", "123");
        ps.setProperty("username", "小新");
        // 第二个参数是null, 这个参数的意思是解释的意思,如果有写,那么会写在文件第一行并用#注释掉
        ps.store(new FileOutputStream("mysql2.properties"), null);

    }

    /**
     * 3. 使用Properties类完成对mysql.properties 配置文件的读取并修改
     * @throws IOException
     */
    @Test
    public void practice03() throws IOException {
        Properties ps = new Properties();
        ps.load(new FileInputStream("mysql2.properties"));

        String root = ps.getProperty("root");
        String username = ps.getProperty("username");
        System.out.println(root);
        System.out.println(username);

        // 修改username的值
        ps.setProperty("username", "小白");

        // 在读取一次,查看是否修改成功
        System.out.println(ps.getProperty("username"));

        ps.store(new FileOutputStream("mysql2.properties"), null);
    }
}

文件copy的坑

在使用IO流读写文件的时候,在写入操作时,一定要使用write(data, off, len)方法,不然会出现这种坑,造成文件错误或者

image-20211010214837445

/**
 * @date 2021年10月10日21:30:44
 * 文件拷贝
 * 分析:
 * 1. 需要输入流
 * 2. 需要输出流
 * 3. 两个String类型的变量,存储路径
 * 4. 需要一个缓存空间 8 * 1024 * 1024,8M的缓存空间
 * 5. 一个变量记录,每次读取的字节数量 [重要]
 * 6. 一定要使用write(data, off, len)方法,避免多写入数据,不然的话就麻烦了 [重要]
 */
public class FileCopyNewFIle {
    public static void main(String[] args) throws IOException {
        // 8M缓存空间
        byte[] data = new byte[8 * 1024 * 1024];
        InputStream inputStream = new FileInputStream("G:\\下载\\H5小游戏.rar");
        OutputStream outputStream = new FileOutputStream("E:/H5小游戏.rar");
        int dataLen = -1;
        // 循环读取和写入文件
        while ((dataLen = inputStream.read(data)) != -1) {
            outputStream.write(data, 0, dataLen); // 一定要使用这个方法
        }
        // 关闭流
        inputStream.close();
        outputStream.close();
    }
}

反射

  1. 反射允许程序在执行期间借助于ReflectionAPI取得类的任何信息
  2. 在加载类完成的时候,内存中就会有一个Class类型对象,通过这个对象可以得到对应类的任何信息

反射机制的原理

反射的作用

  1. 在运行判断任意一个对象的所属类型
  2. 在运行构建一个类的对象
  3. 在运行得到任何一个类所具有的的方法和属性
  4. 在运行调用任何一个类的方法和属性
  5. 生成动态代理

反射相关的主要类

  1. java.lang.Class:代表一个类,Class对象表示判断类加载后再堆的对象

  2. java.lang.reflect.Method:代表类方法,该对象表示某个类的方法

  3. java.lang.reflect.Field:代表类的属性,该对象表示某个类的属性

  4. java.lang.reflect.Constructor:代表类的构造器,该类表示某个类的构造器

反射的优点和缺点

  1. 优点:动态的创建和使用对象(框架的灵魂),使用灵活,没有反射机制,框架技术就失去底层支撑
  2. 缺点:使用反射是解释执行,对执行速度有影响

反射的使用

获取CLASS对象三种方式

  1. Class.forName("全限定类明"):将字节码文件加载到内存,返回class对象
  2. 类名.class:通过类名的属性class获取
  3. 对象.getClass()getClass()方法在Object类中定义着

代码示例

public class Dome2 {
    public static void main(String[] args) throws Exception {
        // 1. 将字节码文件加载到内存,返回class对象
        Class<?> clazz1 = Class.forName("domain.Student");

        // 2. 通过类名的属性class获取
        Class<Student> clazz2 = Student.class;

        // 3. `getClass()`方法在Object类中定义着
        Class<? extends Student> clazz3 = new Student().getClass();

        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);
    }
}

/**---------------
输出结果:
true
true
*/

结论

同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个

Class对象获取方法一览

常用方法

  1. 获取功能
  1. 获取成员变量们

    Field getField(String name) : 获取public修饰的指定成员变量
     
    Field[] getFields() :获取public修饰的所有成员变量
    
    Field getDeclaredField(String name) :获取指定的成员变量,不管是什么修饰的
     
    Field[] getDeclaredFields() :获取所有的成员变量,不管是什么修饰的
    
  2. 获取成员方法们

    Method getMethod(String name, 类<?>... parameterTypes) 
    Method[] getMethods() 
    
    Method getDeclaredMethod(String name, 类<?>... parameterTypes) 
    Method[] getDeclaredMethods() 
    
  3. 获取构造方法们

    Constructor<T> getConstructor(类<?>... parameterTypes) 
        
    Constructor<?>[] getConstructors() 
        
    <A extends Annotation> A getDeclaredAnnotation(类<A> annotationClass)
    
    Annotation[] getDeclaredAnnotations() 
    
  4. 获取类名

    String getName()  
    软件包 getPackage()  
    
  5. 获取注解

    Annotation[] getAnnotations()  获取所有注解
    <A extends Annotation> A getDeclaredAnnotation(类<A> annotationClass)  获取指定注解
    
  6. 设置安全检查。提高程序性能,在安全检查设置为True的时候会提高程序性能,

    忽略访问修饰符` void setAccessible(true)`:
    

Field-成员变量对象

为什么要先new 对象呢?我的感觉是Class在内存中是一个对象的模板,new出来的对象是根据模板在内存中创建的对象,然后再设置值或获取值的时候,成员变量或成员方法都是需要存储在对象中的。

操作

  1. 设置值void set(Object obj, Object value)
  2. 获取值get(Object obj)
  3. 忽略访问修饰符void setAccessible(true)
 public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("domain.Student");

        Student student = new Student();
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);  // 忽略访问修饰符
        name.set(student, "小新");
        System.out.println("name成员变量的值:" + name.get(student));
}

Constructor-构造方法对象

  1. 创建对象T newInstance(Object ... initargs)
  2. 如果使用空参构造创建对象,操作可以简化为:Class对象的newInstance()方法

代码示例

public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("domain.Student");

    // 1. 获取构造方法 有参数的
    // public Student(Integer id, String name, int age) {
    Constructor<?> studentCons = clazz.getConstructor(Integer.class, String.class, int.class);// 根据构造方法参数类型,写上对应class即可
    Object xiaoxin = studentCons.newInstance(1, "小新", 21);
    System.out.println(xiaoxin);

    // 2. 调用无参构造
    // 方式一个
    Constructor<?> constructor = clazz.getConstructor();
    Student o = (Student) constructor.newInstance();
    o.setAge(21);
    o.setName("小白");
    System.out.println(o);
    // 方式二:直接通过CLass对象来调用无参构造
    Student o1 = (Student) clazz.newInstance();
    o1.setName("风间彻");
    System.out.println(o1);

}

Methods-成员方法对象

执行方法:Object invoke(Object obj, Object ... args)

  • 参数1:在堆区中的对象

    参数2:参数

获取方法名称:String getName()获取方法名

代码示例

public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("domain.Student");

    // 1. 获取有参数的方法
    Method sName = clazz.getMethod("setName", String.class);
    Student student = new Student();
    sName.invoke(student, "小白");

    System.out.println(student);
    // 输出结果 : Student{id=null, name='小白', age=0}

    // 2. 获取无参方法
    Method getName = clazz.getMethod("getName");
    Object name = getName.invoke(student);
    System.out.println(name);
    // 输出结果:小白

}

案例:通过配置文件来创建对象并执行方法

配置文件

classNmae=domain.Student
methodName=getName

Java代码

package reflect;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * 框架:读取配置文件来加载并执行任何类的方法
 */
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 1. 读取配置文件中类全名  方法名
        Properties properties = new Properties();
        // 1.1 获取ClassLoader的src下配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream in = classLoader.getResourceAsStream("pro.properties");
        // 1.2 配置文件的输入流  ,可以通过CLassLoader获取
        properties.load(in);
        // 1.3 获取方法名和类名
        String path = properties.getProperty("classNmae");
        String methodStr = properties.getProperty("methodName");

        // 2. 获取构造对象创建其对象,获取方法对象执行其对象
        // 2.1 创建对象 并执行方法
        Class<?> clazz = Class.forName(path);
        Object o = clazz.newInstance();

        // 2.2 执行方法
        Method method = clazz.getMethod(methodStr);
        method.invoke(o);
    }
}

反射机制的性能问题 ★★★★★

概念

setAccessible(true)

  • 启动和禁用访问安全检查的开关,值为true则指示反射的对象在使用时应该取消Java语言的访问检查。并不是为true就能访问false就不能访问是为了提高性能
  • 禁止安全检查,可以提高反射的运行速度

代码示例

public class Demo06 {
	
	public static void test01(){
		User u = new User();
		
		long startTime = System.currentTimeMillis();
		
		for (int i = 0; i < 1000000000L; i++) {
			u.getUname();
		}
		
		long endTime = System.currentTimeMillis();
		System.out.println("普通方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms"); 
	}
	
	public static void test02() throws Exception{
		User u = new User();
		Class clazz = u.getClass();
		Method m = clazz.getDeclaredMethod("getUname", null);
//		m.setAccessible(true);
		
		long startTime = System.currentTimeMillis();
		
		for (int i = 0; i < 1000000000L; i++) {
			m.invoke(u, null);
		}
		
		long endTime = System.currentTimeMillis();
		System.out.println("反射动态方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");
	}
	
	public static void test03() throws Exception{
		User u = new User();
		Class clazz = u.getClass();
		Method m = clazz.getDeclaredMethod("getUname", null);
		m.setAccessible(true);	//不需要执行访问安全检查
		
		long startTime = System.currentTimeMillis();
		
		for (int i = 0; i < 1000000000L; i++) {
			m.invoke(u, null);
		}
		
		long endTime = System.currentTimeMillis();
		System.out.println("反射动态方法调用,跳过安全检查,执行10亿次,耗时:"+(endTime-startTime)+"ms");
	}
	
	
	public static void main(String[] args) throws Exception {
		test01();
		test02();
		test03();
	}
}

结果

普通方法调用,执行10亿次,耗时:551ms
反射动态方法调用,执行10亿次,耗时:1377ms
反射动态方法调用,跳过安全检查,执行10亿次,耗时:1065ms

结论

  1. 普通调用方法10亿次 贼快
  2. 反射调用方法,有安全检查,贼很,是普通调用的3倍
  3. 反射调用方法,无安全检查,很快,是普通调用的2倍

反射操作泛型

前提

Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac 使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除

简单的说:就是Java编译器在编译java源代码文件的时候,会把泛型擦除。

反射操作泛型

为了通过反射操作这些类型以迎合实际开发的需求,Java就新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归一来Class类中的类型但是又和原始类型齐名的类型

ParameterizedType:表示一种参数化的类型,比如Collection<String>

GenericArrayType:表示一种元素类型是参数化类型或者类型变量数组类型

TypeVariable:是各种类型变量的公共接口

WildcardType:代表一种通配符类型表达式,比如:?, ? extends Number, ? super Integer【wildcard是一个单词:就是“通配符”】

代码示例

package com.bjsxt.test;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import com.bjsxt.test.bean.User;

/**
 * 通过反射获取泛型信息
 * @author dell
 *
 */
public class Demo04 {
	
	public void test01(Map<String,User> map,List<User> list){
		System.out.println("Demo04.test01()");
	}
	
	public Map<Integer,User> test02(){
		System.out.println("Demo04.test02()");
		return null;
	}
	
	public static void main(String[] args) {

		try {
			
			//获得指定方法参数泛型信息
			Method m = Demo04.class.getMethod("test01", Map.class,List.class);
      // 获取泛型类型
			Type[] t = m.getGenericParameterTypes();
			for (Type paramType : t) {
				System.out.println("#"+paramType);
				if(paramType instanceof ParameterizedType){
					Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
					for (Type genericType : genericTypes) {
						System.out.println("泛型类型:"+genericType);
					}
				}
			}
			
			//获得指定方法返回值泛型信息
			Method m2 = Demo04.class.getMethod("test02", null);
			Type returnType = m2.getGenericReturnType();
			if(returnType instanceof ParameterizedType){
					Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();

					for (Type genericType : genericTypes) {
						System.out.println("返回值,泛型类型:"+genericType);
					}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Class类

  1. Class也是类,因此也进程Object
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class对象,在内存中也是只有一份,因此类也只加载一份
  4. 每个类实例都会标记自己是由哪个Class实例生成的
  5. 通过Class对象可以得到一个类的所有方法、属性以及构造方法
  6. Class对象是存放在堆里的

常用方法

方法名 功能说明
static Class forName(String name) 返回指定nameClass对象
Object newInstance() 调用无惨的构造函数,返回一个对象实例
getName() 返回此Class对象所表示的实体名称
Class[] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体父类Class
Constructor[] getConstructors() 返回Class对象的所有构造方法
Field[] getDeclaredFields() 返回Field(属性)对象的一个数组
Method getMethod(String name, Class paramType) 返回一个Method(方法)对象

Socket

  1. 套接字(Socket)开发网络应用程序被广泛使用,以至于成为事实上的标准
  2. 通信的两端都要有Socket,是两台机器间通信的端点
  3. 网络通信其实就是Socket间的通信
  4. Socket允许程序把网络链接当成一个流,数据在两个Socket间通过IO传输
  5. 一般主动发起通信的应用叫客户端,等待通信的叫服务端

InetAddress工具类使用

/**
 * @date 2021年10月18日19:02:05
 * 1. 获取本机InetAddress对象 getLocalHost
 * 2. 根据指定主机名/域名获取ip地址独享 getByName
 * 3. 获取InetAddress对象的主机名 getHostName
 * 4. 获取InetAddress对象的地址 getHostAddress
 */
public class DemoInetAddress {
    public static void main(String[] args) throws Exception {
        // 1. 获取本机InetAddress对象 getLocalHost
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);  // DESKTOP-EDV1HVU/192.168.224.1

        // 2. 根据指定主机名/域名获取ip地址独享 getByName
        InetAddress baidu = InetAddress.getByName("www.baidu.com");
        System.out.println(baidu); // www.baidu.com/220.181.38.149

        // 3. 获取InetAddress对象的主机名 getHostName
        String hostName = baidu.getHostName();
        System.out.println(hostName);  // www.baidu.com

        // 4. 获取InetAddress对象的地址 getHostAddress
        String hostAddress = baidu.getHostAddress();
        System.out.println(hostAddress); // 220.181.38.149
    }
}

Socket服务端与客户端的案例

案例一:

  1. 编写一个服务端,和一个客户端
  2. 服务端在9999端口监听
  3. 客户端连接到服务端,发送hello serve,然后退出
  4. 服务端接受信息,输出,并退出
// 客户端
/**
 * @date 2021年10月18日19:15:30
 * 1. 链接服务器
 * 2. 向服务端发送hello server 信息
 * 3. 释放资源
 */
public class DemoSocketClient01 {
    public static void main(String[] args) throws IOException {
        // 1. 链接服务器,写上服务器IP和端口
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        // 2. 获取输出流,往服务端写入数据
        OutputStream clientOutputStream = socket.getOutputStream();
        clientOutputStream.write("hello, server".getBytes());
        // 3. 释放IO资源与Socket资源
        clientOutputStream.close();
        socket.close();
    }
}
// 服务端
/**
 * @date 2021年10月18日19:08:36
 * 此程序是一个服务端,主要功能如下
 * 监听9999端口
 * 等待客户端链接, 如果不连接程序就会阻塞
 * 读取客户端发来的信息,并打印输出
 */
public class DemoSocketServer01 {
    public static void main(String[] args) throws IOException {
        // 1. 服务端开始监听9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2. 等待客户端链接,如果没有链接,那么程序会一直阻塞在这里
        //    如果有客户端链接,就会拿到对应客户端的对象
        Socket socketClient = serverSocket.accept();
        // 3. 读取客户端发来的信息,并打印输出
        byte[] data = new byte[8 * 1024];
        int dataLength = -1;
        // 4. 服务端读取信息,所以是InputStream socketClient.getInputStream()
        InputStream ClientInput = socketClient.getInputStream();
        while ((dataLength = ClientInput.read(data)) != -1) {
            System.out.print(new String(data, 0, dataLength));
        }

        // 5. 释放资源
        ClientInput.close();
        socketClient.close();
        serverSocket.close();
    }
}

案例二:

  1. 编写服务端,和一个客户端
  2. 服务端在监听9999端口
  3. 客户端连接到服务端,发送hello server,并接受服务端回发的hello client并退出
  4. 服务端接受到客户端发送的信息,并返回客户端一条信息,再退出
// 客户端
/**
 * @date 2021年10月18日19:26:01
 * 1. 创建Socket,写上域名和端口
 * 2. 想服务端发送数据,hello, server
 * 3. 读取服务端发来的数据 hello, client
 * 4. 退出
 * 坑:在向客户端发送数据的时候一定要调用socket.shutdownOutput()方法,不然程序会阻塞在这里,不知道什么时候发送结束
 */
public class DemoSocketClient02 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);

        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello, server".getBytes());
        // 发送结束一定要标记发送结束了,不然程序会不知道什么时候发送结束,会卡在这里
        socket.shutdownOutput();

        byte[] data = new byte[8 * 1024];
        int dataLength = -1;
        InputStream inputStream = socket.getInputStream();
        while ((dataLength = inputStream.read(data)) != -1) {
            System.out.print(new String(data, 0, dataLength));
        }

        inputStream.close();
        outputStream.close();
        socket.close();
    }
}
// 服务端
/**
 * @author 小新
 * @version 1.0
 * @date 2021年10月18日19:20:34
 * 此程序是一个服务端,主要功能如下
 * 1. 监听9999端口
 * 2. 等待客户端链接, 如果不连接程序就会阻塞
 * 3. 读取客户端发来的信息,并打印输出
 * 4. 想客户端发送 hello, Client
 * 5. 退出
 * 坑:在向客户端发送数据的时候一定要调用socket.shutdownOutput()方法,不然程序会阻塞在这里,不知道什么时候发送结束
 */
public class DemoSocketServer02 {
    public static void main(String[] args) throws Exception {
        // 1. 开始监听端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2. 等待链接,获取客户端Socket
        Socket clientSocket = serverSocket.accept();
        // 3. 读取客户端发来的信息,并打印输出
        byte[] data = new byte[1024 * 8];
        int dataLength = -1;
        InputStream inputStream = clientSocket.getInputStream();
        System.out.print("客户端发来信息:");
        while ((dataLength = inputStream.read(data)) != -1) {
            System.out.print(new String(data, 0, dataLength));
            System.out.println("...");
        }

        // 4. 向客户端发送信息 hello, Client
        OutputStream outputStream = clientSocket.getOutputStream();
        outputStream.write("hello, Client".getBytes());
        // 5. 发送结束一定要shutdown下,因为程序不知道什么时候发送结束,因为不知道程序什么时候会卡在这里
        clientSocket.shutdownOutput();
        // 5. 释放资源-退出
        inputStream.close();
        outputStream.close();
        clientSocket.close();
        serverSocket.close();
    }
}

案例三:

  1. 和案例二一样,只是使用字符串通信
// 客户端
/**
 * @author 小新
 * @version 1.0
 * @date 2021年10月18日22:25:48
 * 1. 创建Socket,写上域名和端口
 * 2. 想服务端发送数据,hello, server
 * 3. 读取服务端发来的数据 hello, client
 * 4. 退出
 * 坑:在向客户端发送数据的时候一定要调用socket.shutdownOutput()方法,不然程序会阻塞在这里,不知道什么时候发送结束
 */
public class DemoSocketClient03 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);

        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF8"));
        writer.write("hello server ,这次是字符流传输拉~!");
        writer.flush(); // 如果使用字符流,一定要刷新一下,否则数据不会写入数据通道
        // 通知发送消息结束,不要阻塞程序了
        socket.shutdownOutput();

        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF8"));
        System.out.println("服务端发送过来的数据" + reader.readLine());

        writer.close();
        reader.close();
        socket.close();
    }
}

案例四:

  1. 客户端向服务端发送图片数据,退出程序
  2. 服务端接受到图片数据并写入磁盘,最后退出程序
// 客户端
/**
 * @date 2021年10月20日20:53:58
 * 客户端给服务端发送图片
 */
public class TCPFileCopyClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);

        // 读取要发送的图片
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("G:/姜泰莉4k壁纸3840x2160_彼岸图网.jpg"));
        // 一下将图片数据读取到内存中
        byte[] data = DemoStreamUtils.streamToByArray(bis);
        bis.close();

        // 开始发送数据
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(data);
        outputStream.flush();


        // 告诉socket发送数据结束
        socket.shutdownOutput();

        // 接受客户端发来消息
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF8"));
        System.out.println("服务端发来消息:" + br.readLine());

        outputStream.close();
        br.close();
        socket.close();
    }
}
// 服务端
/**
 * @date 2021年10月19日19:13:12
 * 1. 编写服务端 和 客户端
 * 2. 服务端监听9999端口
 * 3. 客户端连接服务器,发送一张图片
 * 4. 服务端接受客户发送来的图片,保存在src下,发送 “收到图片“ 在退出
 * 5. 客户端接受服务端发送来的 “收到图片”,再退出
 * 6. 改程序要求使用StreamUtils.java
 */
public class TCPFIleCopyServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);
        Socket socket = serverSocket.accept();
        System.out.println("等待客户端发来图片");
        // 读取客户端发来的图片/视频数据
        InputStream is = socket.getInputStream();
        byte[] data = new byte[8 * 1024];
        int dataLen = -1;
        // 保存客户端发来的图片/视频
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));

        while ((dataLen = is.read(data)) != -1) {
            bos.write(data, 0, dataLen);
        }
        // 数据保存成功
        bos.flush();

        // 给客户端发送消息,表示保存成功
        socket.getOutputStream().write("图片保存成功".getBytes());
        socket.shutdownOutput();
        // 关闭流
        bos.close();
        socket.shutdownInput();
        socket.close();
        serverSocket.close();
    }
}

常用方法

  1. Arrays.sort():冒泡排序(默认升序),想要自定义类使用此排序,需要实现Comparable接口的compareTo()方法
    • compareTo()方法:0是等于;1是大于;-1是小于
  2. 时间格式化输出SimpleDateFormat 格式是:yy-MM-dd HH:mm:ss
  3. 快速生成迭代器while的话,可以输入itit来生成
  4. 显示所有快捷键Ctrl + J
  5. try/catch快捷键:Ctrl + Alt + T
posted @ 2021-11-14 13:46  蜡笔没有大象  阅读(61)  评论(0)    收藏  举报