JAVA基础学习

198 阅读25分钟

数据类型

基本数据类型

  • 整数
    • byte[1]
    • short[2]
    • int[4]
    • long[8]: int a = 3L;

整数类型,最新单位是bit。一个byte占用8个bit,其中第一位bit表示正负数,0位正数,1位负数。

  • 浮点类型
    • float[4]: float a = 1.1F;
    • double[8]

java的浮点数默认为是double类型

浮点数 = 符号位 + 指数位 + 尾数位。 其中尾数可能丢失,造成精度损失,所以小数都是近似值。

  • 字符型
    • char[2]

char类型的本质是一个数字,是可以进行计算的。

  • 布尔型
    • boolean[1]:只能是false或true。

引用数据类型

  • 类 class
  • 接口 interface
  • 数组 []

基本数据类型转换

自动类型转换

当java程序在进行赋值或者运算时,精度小的类型自动转换为精度达的数据类型,这个就是自动类型转换

  • char -> int -> long -> float -> double
  • byte -> short -> int -> long -> float -> double
  • (byte、short)与char之间不会互相自动转换
  • 具体赋值给byte时,先判断byte数值范围,如果是变量赋值,判断类型
  • byte,short,char,他们三者可以计算,在计算时首先转换为int类型
  • boolean不参与类型的自动转换

强制类型转换

用()进行强制转换, 但()只针对最近的操作数有效,往往会使用小括号提升优先级

int n1 = (int)1.9;  //1 造成精度损失

int n2 = 2000;
byte n3 = (byte)n2; //-48  造成数据溢出

int n4 = (int)(10*3.5 + 66*1.5); //44

基本数据类型和String类型的转换

  • 基本数据类型换String:直接在后面 + ""即可
  • String转基本数据类型: parseXXX
int n1 = 100;
String s1 = n1 + "";

String s5 = "123";
int num1 = Integer.parseInt(s5);
double num2 = Double.parseDouble(s5);
boolean num2 = Boolean.parseBoolean("true");
// float long byte boolean short 都有对应的包装类
...

字符编码表

  • ASCII:一个字节表示,一共128个字符
  • Unicode:使用2个字节来表示字符,汉字和字母都用两个字节
  • UTF-8:字母1个字节,汉字3个字节
  • GBK:字母1个字节,汉字2个字节
  • GB2312:可以表示汉字,GB2312<GBK
  • BIG5:台湾,香港(繁体中文)

运算符

算术运算符

  • +,-,*,/,%,++,--
  • a % b = a - (int)a / b * b;
  • int k = ++j; // => j = j + 1; k = j;
  • int k = j++; // => k = j; j = j + 1;
int i = 1;
i = i++; // temp = i; i = i + 1; i = temp;
System.out.println(i); // 1
int i = 1;
i = ++i; // i = i + 1; temp = i; i = temp;
System.out.println(i); // 2

关系运算符(比较运算符)

  • ==, !=, <, >, <=, >=, instanceof

逻辑运算符

  • &&, ||, !, &, |, ^
  • &&: 短路与。前面为假,后面的不会再执行。
  • &:逻辑与。前面为假,后面的仍然会执行。
  • 短路或,逻辑或同理。
  • a^b: 当a和b不同时为真, 反之为假

赋值运算符

  • =, +=, -=, *=, /=, %=
  • 运算顺序是从右往左
  • 复合赋值运算符会进行类型转换
byte b = 3;
b += 2; // b = (byte)(b + 2);
b++; // b= (byte)(b + 1);

三元运算符

运算符优先级

  • 只有单目运算符和赋值预算法是从右向左的。
  • () {}
  • == --
  • 算术运算符
  • 位移运算符
  • 比较运算符
  • 逻辑运算符
  • 三元运算符
  • 赋值运算符

位运算符

  • 原码、反码、补码(重点、难点)
    • 二进制最高位是符号位: 0表示整数, 1表示负数
    • 正数的原码、反码、补码都一样
    • 负数的反码 = 它的原码符号位不变,其他位取反
    • 负数的补码 = 它的反码 + 1, 负数的反码 = 负数的补码 - 1
    • 0 的反码、补码都是0
    • java没有无符号数,都是有符号的
    • 计算机在运行的时候,都是以补码的方式来运算的
    • 当我们看运算结果的时候,要看他的原码
  • &、|、^、~、>>、<<、>>>
  • &: 按位与。两位全为1,结果为1,否则为0。
  • |:按位或。两位只要有1个为1, 结果为1, 否则为0。
  • ^: 按位异或。两个一个为0一个为1, 结果为1, 否则为0。
  • ~:按位取反。0 -> 1, 1-> 0
  • >>: 算术右移。低位溢出,符号位不变,并用符号位补溢出的高位。
  • <<: 算术左移。符号位不变,低位补0
  • >>>: 逻辑右移,也叫无符号右移。低位溢出,高位补0.
  • 没有 <<< 符号

获取控制台的输入

import java.util.Scanner;

public class Test {
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入名字");

    // 当程序执行到next时,会等待用户输入
    String name = scanner.next();
    System.out.println("请输入年龄");
    int age = scanner.nextInt();
    System.out.println("请输入薪水");
    double sal = scanner.nextDouble();
  }
}

数组

  • double[] arr = {1, 2.5, 6};
  • int a[] = new int[5];
  • 数组创建后, 如果没有赋值,有默认值。char \u0000, boolean false, String null
  • 数组型数据是对象
  • 内存: 栈、堆、方法区
  • 数组扩容:开辟新空间,赋值
  • 数组缩减:

冒泡排序

对象

public class Hello {
  public static void main(String[] args) {
    Cat cat1 = new Cat(); // cat在栈中保存一个地址值,这个地址值指向堆中开辟的一片空间。
    cat1.name = "aa";
    cat1.age = 2;
    cat1.color = "白色";   // 字符串放在方法区的常量池中。
    System.out.println(cat1.name);
  }
}

class Cat {
  String name;
  int age;
  String color;
  public void speak(){
      System.out.println("....");
  }
}

java内存结果分析

  • 栈:基本数据类型
  • 堆:存放对象
  • 方法区:常量池(常量,比如字符串),类加载信息(属性、方法)

java创建对象流程

  • Person p = new Person();
  • p.name = "jack";
  • 先加载Person类信息(方法、属性,只会加载一次)
  • 在堆中分配空间,进行默认初始化
  • 把地址赋给p, p就指向对象
  • 进行制定初始化

方法的调用机制

  • 当程序执行到方法时, 就会开辟一个独立的栈空间
  • 当方法执行完毕,或者执行到return语句时,就会返回
  • 返回到调用方法的地方
  • 返回后,继续执行方法后面的代码
  • 注意:方法不能嵌套定义,即方法中不能再定义方法

方法调用

  • 同一个类中直接调用
  • 跨类中的方法,A类调用B类中的方法:先创建B类的对象,在用对象调用B类中的方法。

可变参数

  • 可变参数只能放在最后
  • 一个形参列表中最多只能有1个可变参数
// nums可以当做数组使用
public int sum(String str, int... nums){

}

变量的作用域

  • 全局变量: 属性,作用域为整个类体。全部变量可以不赋值直接使用,因为有默认值
  • 局部变量:成员方法中定义的变量,作用域为该成员方法。局部变量没有默认值,必须赋值后才能使用

构造方法/构造器

  • 完成对新对象的初始化
  • 构造器的修饰符可以默认,也可以是public protected private
  • 构造器没有返回值, 也不能写void
  • 方法名和类的名字必须一样
  • 参数列表和成员方法一样的规则
  • 构造器的调用系统完成
  • 如果没有定义构造器,系统会自动给类生成一个默认无参构造器
  • 定义了构造区,默认的构造器就被覆盖了,要调用默认的必须显示再定义一次。

对象创建的流程 Person p = newPerson('xxx', 20)

  • 加载类信息(Person.class),只会加载一次
  • 在堆中分配空间(地址)
  • 完成对象的初始化
    • 默认初始化 age=0 name=null
    • 显示初始化 age=90 name=null
    • 构造器初始化 age=20 name="xxx"
  • 将对象在堆中的地址,返回给p(p是对象名,也可以理解为是对象的引用)

this

  • this可以用来访问本类的属性、方法、构造器
  • this用于区分当前类的属性和局部变量
  • 访问成员方法: this.方法名(参数列表)
  • 访问构造器:this(参数列表),注意只能在构造器中访问另一个构造器,且只能在构造器中的第一行去调用

包 package com.test

  • package: 关键字,表示打包,需要放在类的最上面,一个类中最多只有一句package
  • com.test:表示包名
  • 包的本质:创建不同的文件夹来保存类文件
  • import指令 位置放在package的下面,在类定义的前面,可以有多句且没有顺序要求

常用包

  • java.lang.* //基本包,默认引用,不需要再引用
  • java.util.* //系统提供的工具包,工具类,Scanner
  • java.net.* //网络包,网络开发
  • java.awt.* //java界面开发 GUI

访问修饰符

  • public: 本类(√)、同包√)、子类(√)、不同包(√)
  • protected: 本类(√)、同包(√)、子类(√)、不同包(×)
  • 默认: 本类(√)、同包(√)、子类(×)、不同包(×)
  • privat:本类(√)、同包(×)、子类(×)、不同包(×)
  • 只有默认和public可以修饰类

封装

  1. 将属性进行私有化private,不能直接修改属性
  2. 提供一个public的set方法,用于对属性判断并赋值
  3. 提供一个public的get方法,用于获取属性的值

继承 extends

  • 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但私有属性不能直接在子类访问,要通过父类提供的公共方法去访问
  • 子类必须调用父类的构造器,完成父类的初始化
  • 当创建子类构造器时, 不管使用子类的哪个构造器,默认情况总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
  • 如果希望指定去调用父类的某个构造器,则显示的调用一下: super(参数列表)
  • super在使用时,需要放在构造器第一行
  • super()和this()都只能放在构造器的第一行,因此这两个方法不能共存在一个构造器
  • java所有类都是Object的子类
  • 父类构造器的调用不限于直接父类
  • 子类最多只能继承一个父类(直接继承),java是单继承机制

super关键字

  • 访问父类的属性,但不能访问父类的private属性
  • 访问父类的方法,但不能访问父类的private方法
  • 访问父类的构造器,放在第一行

方法重写/覆盖

  • 子类的方法和父类的方法名称、返回类型、参数列表都一样
    • 返回类型:父类为Object、子类为String,也可以
    • 子类返回类型为父类返回类型的子类即可
  • 子类方法不能缩小父类方法的访问权限
  • 重载 overload
    • 发生范围:本类
    • 方法名:必须一样
    • 参数:类型,个数、顺序至少一个不同
    • 返回类型: 无要求
    • 修饰符:无要求
  • 重写 overwrite
    • 发生范围:父子类
    • 方法名:必须一样
    • 参数:相同
    • 返回类型: 子类返回类型为父类返回类型的子类,或一致
    • 修饰符:子类方法不能缩小父类方法的访问权限

多态

  1. 方法的多态
  • 方法重载体现多态
  • 方法重写体现多态
  1. 对象的多态
  • 一个对象的编译类型和运行类型可以不一致
  • 编译类型在定义对象时,就确定了,不能改变
  • 运行类型是可以变化的
  • 编译类型看定义时 = 号的左边,运行时看 = 号的右边
// animal编译类型是Animal,运行类型是Dog
Animal amimal = new Dog(); 
animal.cry(); // 执行到该行时,animal的运行类型是Dog,所以这个cry(), 调用的是Dog的cry()
// animal的运行类型变成了Cat,编译类型仍然是Animal
animal = new Cat();
animal.cry(); //运行时,animal的运行类型变成了Cat,调用Cat的cry()

// 向下转型
Cat cat = (Cat) animal;

  • 多态的前提是:两个对象(类)存在继承关系
  • 多态的向上转型
    • 本质:父类的引用指向了子类的对象
    • 语法: 父类类型 引用名 = new 子类类型();
    • 特点: 编译类型看左边,运行类型看右边
    • 可以调用父类的所有成员,但是不能调用子类特有的成员
    • 因为在编译阶段能调用那些成员,是由编译类型来决定的
  • 多态的向下转型
    • 语法: 子类类型 引用名 = (子类类型) 父类引用
    • 只能强转父类的引用,不能强转父类的对象
    • 要求父类的引用必须指向的是当前目标类型的对象
    • 当向下转型后,可以调用子类类型中的所有成员
  • 属性没有重写之说,属性的值看编译类型。属性,静态方法,采用静态绑定,只看编译类型,一旦绑定就不变,实例方法采用动态绑定,编译时按照编译类型绑定,运行时按照运行类型绑定
  • instanceof:判断一个对象的运行类型是否是另一个类型或者另一个类型的的子类型

java的动态绑定机制

  • 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  • 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用

多态数组

  • 数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

Object类详解

equals 和 ==

  • == :既可以判断基本类型,也可以判断引用类型
  • == :如果判断基本类型,判断的是值是否相等
  • == :如果判断引用类型,判断的是地址值是否相等
  • equals:Object类中的方法,只能判断引用类型
  • equals默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。

hashcode

  • 提高具有hash结构的容器的效率
  • 两个引用,指向同一个对象, 则哈希值肯定是一样的
  • 两个引用,指向不同对象,则哈希值是不一样的
  • 哈希值主要根据地址号来的,不能完全将哈希值等价于地址
  • 在集合,hashCode如果需要的话,也会重写

toString() --- Object 的类

  • 默认返回:全类名(包名+类名)+ "@" + 哈希值的十六进制
  • 子类往往重写toString()方法,用于打印对象或拼接对象
  • 当直接输出一个对象时,toString()方法会被默认调用

finalize

  • 当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些示范资源的操作
  • 什么时候被回收:当某个对象没有任何引用时,jvm就会认为这是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象钱,会调用finalize方法
  • 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动触发垃圾回收机制

当一个方法是static时,是静态方法,可以直接通过类名调用

static

  • 类变量/静态变量 private static name = "";
  • 类变量是随着类的加载儿创建,所以即使没有创建对象实例也能访问
  • 类变量的生命周期随着类的加载开始,随着类消亡而销毁
  • 访问:类名.类变量 / 对象名.类变量
  • 类方法/静态方法 public static void pay(double fee){}
  • 类变量或类方法的访问也要满足修饰符的规则
  • 工具类的方法可以设置为类方法方便调用
  • 类方法中不允许使用和对象有关的关键字:super / this
  • 普通方法不能通过类名调用
  • 静态方法,只能访问静态成员(静态变量和静态方法)
  • 普通方法,可以访问普通成员和静态成员

main方法

  • main方法是java虚拟机调用的
  • java虚拟机需要调用类的main方法,所以main方法的访问权限必须是public
  • 虚拟机在执行main方法时不必创建对象,所以必须是static
  • main方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
  • java 执行的程序 参数1 参数2 参数3

代码块 [修饰符] {}

  • 修饰符:可选,要写的话只能写static
  • 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
  • 末尾的;可写可不写
  • 不管调用哪个构造器,都会先调用代码块的内容
  • 代码块的调用顺序优先于构造器
  • static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,没创建一个对象,就执行一次
  • 静态代码块只能直接调用静态成员(属性和方法),普通代码块可以调用任意成员

类什么时候被加载

  • 创建对象实例时(new)
  • 创建子类对象实例,父类也会被加载,而且父类先被加载
  • 使用类的静态成员时(静态属性、静态方法)

创建一个对象时,在一个类的调用顺序:(重点、难点

  1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态属性初始化,则按他们定义的顺序调用)
  2. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按他们定义的顺序调用)
  3. 调用构造方法
    • 构造器的前面其实隐含了super()和本类的普通代码块和普通属性初始化

创建一个子类对象时(继承关系),他们的静态代码块,静态初始化,普通代码块,普通属性初始化,构造方法的调用顺序:

  1. 父类的静态代码块和静态属性
  2. 子类的静态代码块和静态属性
  3. 父类的普通代码块和普通属性初始化
  4. 父类的构造方法
  5. 子类的普通代码块和普通属性初始化
  6. 子类的构造方法
在主方法中创建一个子类对象时,会执行以下2个操作:
1. 类加载
    先加载父类,会执行父类的静态代码块和静态属性;
    再加载子类,会执行子类的静态代码块和静态属性
2. 创建对象
    在调用子类的构造函数之前,隐藏了super()和子类普通代码块和普通属性初始化;
    在调用父类的构造函数之前,也隐藏了super()和父类的普通代码块和普通属性初始化;

单例模式

采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

  1. 饿汉式
    • 构造器私有化,防止直接new
    • 类的内部创建对象
    • 向外暴露一个静态的公共方法(getInstance)
public class SingleTon {
    public static void main(String[] args) {
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
        System.out.println(instance == instance2); //true
    }
}

class GirlFriend{
    private String name;
    // 1. 将构造器私有化
    // 2. 在类的内部直接创建对象
    // 3. 提供一个公共的static方法,返回gf对象
    private static GirlFriend gf = new GirlFriend("小红花"); // 饿汉式可能造成创建了对象,但是没有使用
    private GirlFriend(String name) {
        this.name = name;
    }

    public static GirlFriend getInstance(){
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + ''' +
                '}';
    }
}
  1. 懒汉式
    • 饿汉式是在类加载就创建了实例对象,而懒汉式是在使用时才创建
    • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
    • 饿汉式存在浪费资源的可能
    • java.lang.Runtime就是经典的单例模式
public class SingleTon {
    public static void main(String[] args) {
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
        System.out.println(instance == instance2); //true
    }
}

class GirlFriend {
    private String name;
    // 1. 将构造器私有化
    // 2. 定义一个static静态属性对象
    // 3. 提供一个公共的static方法,返回一个GirlFriend对象
    private static GirlFriend gf;

    private GirlFriend(String name) {
        System.out.println("构造器调用");
        this.name = name;
    }

    public static GirlFriend getInstance() {
        if (gf == null) {
            gf = new GirlFriend("小红花");
        }
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + ''' +
                '}';
    }
}

final

  • final可以修饰类、属性、方法和局部变量, final修饰属性又叫常量。一般用大写定义,且定义时必须赋值(或者在构造器/代码块中赋值)。
  • 如果final修饰的属性是静态的,则初始化的位置之鞥呢在定义时或者静态代码块中,不能在构造器中赋值
  • final类不能被继承,但可以被实例化对象
  • 如果类不是finfal类,但有final方法,该方法虽然不能被重写,但仍然可以在子类中使用
  • final类中,没必要再用final去修饰方法
  • final不能修饰构造方法
  • final和static往往搭配使用,效率更高,不会导致类加载。
  • 包装类(String、Double)是final类,不能被继承

使用情况:

  • 当不希望类被继承,可以用final修饰类
  • 当不希望父类的方法被子类重写,可以用final修饰方法
  • 当不希望类的某个属性值被修改,可以用final修饰
  • 当不希望某个局部变量被修改,可以用final修饰

抽象类

  • 抽象方法,没有实现的方法,即没有方法体
  • 当一个类中存在抽象方法时,需要将该类声明为abstract
  • 一般来说,抽象类会被继承,由子类来实现
  • 抽象类不能被实例化
  • 抽象类不一定要包含抽象方法
  • abstract只能修饰类和方法,不能修饰其他的
  • 抽象类可以有任意成员(本质还是类)
  • 如果一个类继承了抽象类,则必须实现抽象类的抽象方法,除非他自己也是抽象类
  • 抽象方法不能用private、final、static来修饰

接口 interface

  • 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来
  • implements 类实现接口,必须实现接口的抽象方法
  • jdk7.0 以前, 接口中只包含抽象方法。jdk8.0以后,接口中可以有抽象方法,静态方法,默认实现方法(需要使用default关键字修饰)
  • 在接口中所有方法都是public,抽象方法可以省略abstract关键字
  • 接口不能被实例化
  • 抽象类实现接口,可以不用实现接口的方法
  • 一个类同时可以实现多个接口
  • 接口中的属性,只能是final的,并且是public static final修饰符,也必须初始化。访问属性:接口.属性名
  • 接口不能继承类,但是可以继承多个别的接口
  • 接口的修饰符只能是public和默认,和类的修饰符是一样的

实现接口 vs 继承类

  • 继承的价值主要在于:解决代码的复用性和可维护性
  • 接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法
  • 接口比继承更加灵活
  • 接口在一定程度上实现代码解耦【即:接口规范性+动态绑定】

接口的多态性

  • 多态参数:接口类型的变量可以指向实现了接口的对象实例
  • 多态数组

内部类

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

内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

内部类的分类

  1. 定义在外部类局部位置上(比如方法/代码块内):
    • 局部内部类
      • 可以直接访问外部类的所有成员,包括私有的。
      • 不能用访问修饰符修饰,但可以用final修饰
      • 作用域:仅仅在定义它的方法或代码块中
      • 外部类访问局部内部类的成员:先创建对象,再访问(在作用域内)
      • 本质仍然是一个类
      • 外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,使用 外部类名.this.成员去访问
    • 匿名内部类(没有类名,重点
      • 本质是类,内部类
      • 该类没有名字
      • 同时是一个对象
      // IA 是一个接口
      IA tiger = new IA(){
          
      }
      tiger.getClass();  //获取运行类型,即为匿名内部类的名称 外部类名称$1
      
      new AClass(){}.ok(); //直接调用匿名内部类的方法
      
      • 匿名内部类使用一次,就不能使用了
      • 外部其他类不能访问匿名内部类
  2. 定义在外部类的成员位置上:
    • 成员内部类(没有static修饰)
      • 定义在外部类的成员位置, 并且没有static修饰
      • 直接访问外部类的所有成员,包括私有的
      • 可以添加任意的访问修饰符,因为它就是类的一个成员
      • 作用域为整个类体,和其他外部类的成员一样
      • 外部其他类访问成员内部类:
        //第一针方式
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.say();
        
        //第二种方式:在外部类中,编写一个方法getInnerInstance,返回Inner对象
        Outer outer = new Outer();
        Outer.Inner innerInstance = outer.getInnerInstance();
        innerInstance.say();
        
    • 静态内部类(使用static修饰)
      • 定义在外部类的成员位置, 并且有static修饰
      • 可以访问外部类的所有静态成员
      • 可以添加任意的访问修饰符
      • 作用域为整个类体,和其他外部类的成员一样
      • 静态内部类访问外部类:直接访问所有静态成员
      • 外部类访问静态内部类:创建对象,再访问
      • 外部其他类访问静态内部类:
        //方式1:
        Outer.Inner inner = new Outer.Inner();
        inner.say();
        
        //方式2:编写一个方法(静态,非静态都可以),返回静态内部类的对象实例
        Outer outer = new Outer();
        Outer.Inner inner = outer.getInner()l
        inner.say();
        
      • 外部类和静态内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,使用 外部类名.成员去访问

枚举

自定义枚举(在类的基础上修改)

  1. 将构造器私有化,防止直接new
  2. 去掉setXXX方法,防止属性被修改
  3. 在类内部,直接创建固定的public static final对象
  4. 固定对象名全部大写

使用 enum关键字

  1. 使用enum替代class,默认会继承Enum类, 不能再继承其他类了,但是还可以实现接口
  2. public static final Season SPRING = new Season("传统", "温暖") ===> SPRING("春天", "温暖")
    • 常量名(实参列表)
  3. 如果有多个常量(对象),使用逗号间隔即可,最后一个以分号结尾
  4. 如果使用enum实现枚举,要求将定义的常量对象,写在第一句
  5. 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
public class EnumPro {
    public static void main(String[] args) {
        System.out.println(Season.AUTUMN);
    }
}

enum  Season{
    SPRING("春天", "温暖"),
    SUMMER("夏天", "温暖"),
    AUTUMN("秋天", "温暖"),
    WINTER("动态", "温暖");

    private String name;
    private String desc;

    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + ''' +
                ", desc='" + desc + ''' +
                '}';
    }
}

常用方法

  1. toSting(): 返回当前对象名,子类可以重写
  2. name(): 返回枚举对象的名字
  3. ordinal(): 输出的是该枚举对象的次序/编号,从0开始编号
  4. values(): 返回所有定义的枚举对象
Season[] values = Season.values();
//增强for循环
for (Season season: values){
    System.out.println(season);
}
  1. valueof(): 将字符串转为枚举对象,要求字符串必须为已有的常量名,否则报异常
  2. compareTo(): 比较两个枚举常量,比较的就是编号

注解

  • @Override:限定某个方法,是重写父类方法,该注解只能用于方法(如果发现@interface, 表示一个注解类, @Tatget注解类的注解(元注解)表示可以修饰什么元素)
  • @Depreacted:用于表示某个程序元素(类、方法)已过时
    • 可用于包、字段、参数...
    • 可以做版本升级过渡使用
  • @SuppressWarning:抑制编译器警告

元注解

  • @Retention:指定注解的作用范围(SOURCE、CLASS、RUNTIIME)
  • @Target:指定注解可以在哪些地方使用
  • @Documented:指定该注解是否会在javadoc体现
  • @Inherited:子类会继承父类注解

异常

Error 错误

Error是严重错误,程序会崩溃

Exception

银编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。

  1. 运行时异常
  2. 编译时异常

异常体系

  • Serializable接口/Object
    • Throwable
      • Exception
        • RuntimeException: 运行时异常
          • NullPointerException:空指针异常
          • ArithmeticException:算术异常
          • ArrayIndexOutOfBoundsException:数组索引越界
          • ClassCaseException:类型转换异常
          • NumberFormatException:数字格式异常
          • NoSuchElementException
        • 以下其他异常都是编译时异常(必须处理)
        • FileNotFoundException:文件不存在
        • ClassNotFoundException:类不存在
        • SQLException:数据库异常
        • IOException:操作文件异常
        • EOFException:操作文件到末尾
        • IllegalArguementException: 参数异常
      • Error
        • OutOfMemoryError
        • StackOverflowError

异常处理

  1. try-catch-finally
    • 不管try代码块是否有异常,finally始终都会执行
    • 如果没有发生异常,catch代码块是不会执行的
    • 通常将释放资源的代码放在finally
    • finally可以没有
    • 异常发生了,try中异常后面的代码不会继续执行,会进入catch块,catch执行完后,有finally就执行finally,没有就跳出当前的try-catch,继续执行外面的代码
    • 如果try代码块有可能有多个异常,可以使用catch分布捕获不同的异常,这时要求子类异常写在前面,父类异常写在后面
  2. throws
    • 运行异常有默认处理,编译异常没有默认处理
    • 如果程序员没有显示处理异常,默认采用throws处理异常
    • 不确定如果处理某种异常,throws可以显示地声明跑出该异常,表明该方法将不对这些进行处理,而由该方法的调用者负责处理
    • throws后面阿德异常类型可以是方法中产生的异常类型,也可以是它的父类
    • throws关键字后也可以是列表,即可以跑出多个异常
    • 子类重写方法时,子类方法异常的类型应该为父类方法异常类型或者父类方法异常类型的子类型
    • 在throws过程中,有try-catch-finally,相当于处理异常,就不必在throws
    • throws是异常处理的一种方式,是放在方法声明的地方,后面跟的是异常的类型
    • throw 是手动生成异常对象的关键字,放在方法体中,后面跟的是异常对象

自定义异常

  • 自定义异常一般是集成RuntimeException,即是运行时异常,可以使用默认的处理机制
public class ExceptionPro {
    public static void main(String[] args) {
        int age = 80;
        // 18-120之间,否则抛出异常
        if(!(age >= 18 && age <= 120)){
            throw new AgeException("年龄需要在18-120之间");
        }
        System.out.println("你的年龄范围正确.");
    }
}
class AgeException extends RuntimeException{
    public AgeException(String message) {
        super(message);
    }
}

常用类

包装类

  1. Boolean:Object, Comparable
  2. Character:Object, Comparable
  3. Byte:Object->Number, Comparable
  4. Short:Object->Number, Comparablev
  5. Integer:Object->Number, Comparable
  6. Long:Object->Number, Comparable
  7. Float:Object->Number, Comparable
  8. Double:Object->Number, Comparable
装箱与拆箱
  • 手动装箱、拆箱
int n1 = 100;
//手动装箱
 Integer integer = new Integer(n1);
 Integer integer1 = Integer.valueOf(n1);
 // 手动拆箱
 int i = integer.intValue();
  • jdk5以后,可以自动装箱和自动拆箱
int n1 = 100;
//自动装箱
 Integer integer = n1;
 //自动拆箱
 int i = integer;

包装类型与String类型的转换

//Integer -> String
Integer i = 100;
String str = i + "";
String str2 = i.toString();
String str3 = String.valueOf(i);

// String -> Integer
 String str4 = "1234";
 Integer i2 = Integer.parseInt(str4);
 Integer i3 = new Integer(str4);
Integer i = 1;
Integer n = 1;
System.out.println(i == n); // true
// -128 ~ 127, 返回的数字,不在范围内返回的是new Integer(x)
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false

String

  • Unicode编码,一个字符占2个字节
  • 实现了接口Serializable,可以进行串行化:可以在网络传输
  • 实现了接口Comparable,String对象可以比较大小
  • String是final类,不能被继承
  • String有属性private final char value[]; 用于存放字符串内容
  • value是一个final类型,不可以被修改(地址)
  • String的intern()返回常量池的地址

创建String对象:

  • 方式1:String s = "xxx";
  • 方式2: String s2 = new String("xxx");
  1. 方式1,:先从常量池查看是否有"xxx"数据空间,如果有,直接指向;如果没有,则重新创建,然后指向。s最终指向的是常量池的空间地址
  2. 方式2:先在堆中创建空间,里面维护了value睡醒,指向常量池的xxx空间。如果常量池没有"xxx",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址
String a = "hello";
String b = "abc";
//1. 先创建一个StringBuilder sb = new StringBuilder();
//2. 执行sb.append("hello");
//3. 执行sb.append("abc");
//4. String c = sb.toString();  => new String()
// 最后其实是c指向堆中的对象(String) value[] -> 池中"helloabc"
// 常量相加,看的是池;变量相加,看的是堆
String c = a + b;
System.out.println(c);

String常用方法

  • equals:区分大小写
  • equalsIgnoreCase
  • length
  • indexOf
  • lastIndexOf
  • subString
    • name.subString(6): 截取从suoyin6到结束
    • name.subString(0, 5): 从索引0开始,截取到索引5-1的位置
  • trim
  • charAt
  • toUpperCase
  • toLowerCase
  • concat
  • replace
    • 不会修改原先的str
    • 返回替换后的字符串
  • split
  • compareTo
    • 长度相同,且每个字符也相同,返回0
    • 长度相同或者不同,但是进行比较时可以区分大小,返回c1-c2
    • 如果前面部分就相同,就返回str.len - str2.len
  • toCharArray
  • format
    • %s:字符串占位符
    • %d:整数占位符
    • %.2f:浮点数占位符,保留2位小数,并且进行四舍五入的处理
    • %c:char占位符

StringBuffer

  • StringBuffer直接父类是AbstractStringBUffer
  • StringBuffer实现了Serializable接口,可以串行化
  • StringBuffer有一个char[] value数组存放字符串内容,引用类型,存放在堆中
  • StringBuffer是一个final类,不能被继承
  • StringBuffer保存字符串变量,里面的值可以修改,每次修改可以更新内容,不用更新地址,效率较高
  • String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低
  • StringBuffer字符内容是存放在char[] value 中,所以在变化(增加/删除)时,不用每次都更换地址(即不是每次创建新对象),所以效率高于String 常用方法
  • append()
  • delete(11, 14): 删除[11, 14)的字符
  • replace(start, end, string)
  • indexOf
  • insert
  • length

String、StringBuffer、StringBuilder

  • StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
  • String:不可变字符序列、效率低,但是复用率高
  • StringBuffer:可变字符序列,效率较高(增删)、线程安全
  • 每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。
  • StringBuilder:可变字符序列、效率最高、线程不安全
  • 对字符串做大量的修改,不要用String,用StringBuffer或StringBuilder
  • 大量修改,并在单线程情况,用StringBuilder
  • 大量修改,并在多线程情况,用StringBuffer
  • 很少修改,被多个对象引用,用String,比如配置信息等

Arrays类

  • Arrays.toString():
  • Arrays.sort(arr)
  • Arrays.binarySearch(arr, 1)
    • 要求arr是有序的,如果是无需的,不能使用该方法
    • 不存在,返回-(low+1)
  • Arrays.copyOf(arr, arr.length)
    • 拷贝arr的arr.length个数到新的数组
    • 拷贝的长度 > arr.length, 就在新数组的后面增加null
    • 拷贝长度 < 0, 返回异常
  • Arrays.fill(arr, 99)
    • 使用99填充arr所有元素,替换所有元素
  • Arrays.equals(arr, arr2)
    • 两个数组元素完全一样,返回true
  • Array.asList(2, 3, 4, 5, 6, 1)
    • List asList = Array.asList(2, 3, 4, 5, 6, 1);
    • asList方法,将(2, 3, 4, 5, 6, 1)数据转为一个List集合
    • 返回的asList编译类型是List接口
    • 返回的运行类型是Arrays的静态内部类ArrayList(asList。getClass())

System类

  • exit: 退出当前程序
  • arrayCopy:复制数组元素
    • System.arrayCopy(src, 0, dest, 0, 3);
    • src: 源数组
    • srcPos: 元素组的开始位置
    • dest: 目标数组
    • destPos:目标数组索引开始位置
    • length: 从源数组拷贝多少个数组到目标数组
  • currentTimeMillens:返回时间戳
  • gc:运行垃圾回收机制

BigInteger类

  • 适合保存比较大的整型
  • 在BigInteger类进行加减乘除时,需要使用对应的方法,不能+-*/
  • 要先创建一个BigInteger对象,然后调用相应的方法。add、subtract、multiply、divide

BigDecimal类

  • 适合保存精度更高的浮点数
  • 在BigDecimal类进行加减乘除时,需要使用对应的方法,不能+-*/
  • 要先创建一个BigDecimal对象,然后调用相应的方法。add、subtract、multiply、divide
  • 除法时可能抛出算术异常,除不尽,需要指定精度
  • bigDecimal.divide(bigDecimal2,BigDecimal.ROUND_CEILING),如果无线循环,保留分子的精度

Date类

Date date = new Date();
System.out.println(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = simpleDateFormat.format(date);
System.out.println(format);

String s = "2022年03月18日 02:23:21 星期五";
Date parse = simpleDateFormat.parse(s);
System.out.println(parse);

Calendar、DateTimeFormatter

  • Calendar 是一个抽象类
//创建日历对象
Calendar c = Calendar.getInstance();
// YEAR、MONTH、DAY_OF_MONTH、HOUR、MINUTE、SECOND
System.out.println("年:" + c.get(Calendar.YEAR) + 1);


LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//plusDays、minusMinutes
LocalDateTime localDateTime = now.plusDays(890);
System.out.println(localDateTime);

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm:ss E");
String format1 = dateTimeFormatter.format(now);
System.out.println(format1);

集合

  • 可以动态保存任意多个对象,使用比较方便
  • 提供了一系列操作对象的方法: add、remove、set、get等
  • 使用集合添加、删除新元素代码简洁

Collection接口实现类的特点

public interface Collection<E> extends Iterable<E>
  • Collection实现子类可以存放多个元素,每个元素可以是Object
  • 有些Collection的实现类,可以存放重复的元素,有些不可以
  • 有些Collection的实现类是有序的(List),有些不是有序的(Set)
  • Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

结构

  • Collection (i)
    • List (i)
      • ArrayList (c)
      • Vector (c)
      • LinkedList (c)
    • Set (i)
      • HashSet (c)
      • TreeSet (c)

Collection接口常用方法,已实现子类ArrayList来演示

List list = new ArrayList();
list.add("jack");
list.add(2);
list.add(true);
System.out.println(list);
list.remove(2);
System.out.println(list);
System.out.println(list.contains("jack"));
System.out.println(list.size());
System.out.println(list.isEmpty());
list.clear();
System.out.println(list);

ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
list.add("聊斋");
System.out.println(list);
list.removeAll(list2);
System.out.println(list);
```java
#### Collection接口遍历元素方式1 --- 使用Iterator(迭代器)
+ Iterator,主要用于遍历Collection集合中的元素
+ 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器
+ 迭代器的结构
+ Iterator仅用于遍历集合,Iterator本身并不存放对象
+ hasNext()
+ next()

Collection col = new ArrayList();

col.add(new Book("aaa", "a", 21)); col.add(new Book("bbb", "b", 21)); col.add(new Book("ccc", "c", 21));

Iterator iterator = col.iterator(); while (iterator.hasNext()){ Object obj = iterator.next(); System.out.println(obj); } //当while退出循环后,这时iterator迭代器,指向最后的元素 //iterator.next(); //NoSuchElementException //如果需要再次遍历可以重置迭代器 iterator = col.iterator();


#### Collection接口遍历元素方式2 --- 增强for循环
```java
//增强for,底层也是迭代器,简化版的迭代器
for (Object book : col) {
    System.out.println(book);
}

List接口和常用方法

  • 有序(添加顺序和取出顺序一致)、且可重复
  • List集合中每个元素都有对应的索引
  • List容器中每个元素都对应一个整数型的序号记载其在容器中的位置,还可以根据序号存取容器中的元素
  • 方法
    • list.add(1, "test"); 默认是加载末尾的
    • list.addAll(1, list2);
    • list.get(1)
    • list.indexOf("test")
    • list.lastIndexOf("test")
    • list.remove(0)
    • list.set(1, "test1")
    • list.subList(0 ,2); 返回[0, 2)的子列表

ArrayList注意事项

  • ArrayList可以加入多个null
  • ArrayList底层是由数组实现数据存储的
  • ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议用ArrayList

ArrayList底层机制和源码分析(重点、难点

  • ArrayList中维护了一个Object类型的数组elementData
    • transient Object[] elementData;
    • transient表示该属性不会被序列化
  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍
  • 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如需扩容,则直接扩容elementData为1.5倍
  • 扩容使用的是Arrays.copyOf()

Vector

  • 继承了AbstractList, 实现了List,RandomAccess、Cloneable、Serializable
  • Vector底层也是一个对象数组:protected Object[] elementData;
  • Vector是线程同步的,即线程安全,synchronized

Vector底层

    • 当创建Vector对象时,如果使用的是无参构造器,则初始化elementData容量为10,如需再次扩容,则扩容elementData为2倍
  • 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如需扩容,则直接扩容elementData为2倍

LinkedList

  • 底层实现了双向链表和双端队列的特点
  • 可以添加任意元素(包括null),元素可以重复
  • 线程不安全,没有实现同步
  • size
  • first
  • last

LinkedList底层结构

  • LinkedList底层维护了一个双向链表
  • LinkedList中维护了两个属性first和last分别指向首节点和尾结点
  • 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表。item真正存放数据
  • 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
  • 改查操作多,用ArrayList
  • 增删操作多,用LinkedList

Set接口

  • 和List一样,也是Collection的子接口,常用方法和Collection一样
  • Set接口遍历
    • 迭代器
    • 增强for
    • 不能使用所有的方式遍历Set接口
  • Set接口的实现类的对象,不能存放存放的元素,可以添加一个null
  • Set接口dui9x存放数据的无需的(即添加顺序和取出顺序是不一致的),但是取出的顺序虽然不是添加的顺序,但是是固定的

HashSet

  • 实现了Set接口
  • HashSet底层实际是HashMap,hashMap底层是数字+链表+红黑树
  • 可以有null,但只能有1个null,元素不能重复
  • HashSet是无序的
  • 执行add后,会返回一个boolean值

HashSet底层

  • HashSet底层是HashMap
  • 添加一个元素时,先得到hash值,再转成索引值
  • 找到存储数据表table,看这个索引的位置是否已经存放有元素
  • 如果没有,直接加入
  • 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后(equals方法可以程序员自己重写定义)
  • HashMap第一次添加是,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75 = 12
  • 如果table数组使用到了临界值12,就会扩容到162=32,新的临界值就是320.75=24,依次类推
  • 在Java8中,如果一条链表的元素个数是TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树)

LinkedHashSet

  • LinkedHashSet是HashSet的子类
  • LinkedHashSet底层是LinkedHashMap,底层维护了一个数组+双向链表
  • LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入的顺序保存的
  • LinkedHashSet不允许添加重复元素
  • LinkedHashSet有head和tail
  • 每一个节点有before和after属性,这样可以形成双向链表
  • 在添加一个元素时,先求hash值,再求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已存在,不添加)
  • 添加第一次时,直接将数组table扩容带16,存放的节点类型是LinkedHashMap$Entry
  • 数组是HashMap$Node[], 存放的元素/数据是LinkedHashMap$Entry类型
  • Entry继承了HashMap.Node(静态内部类)

Map接口和常用方法

  • Map与Collection并列存在。用于保存具有映射关系的数据:key-value(双列元素)
  • Map中的kay和value可以是任何引用类型的数据,会封装到HashMap$Node对象中(Node实现了Entry) => EntrySet<Entry<K, V>>
  • k-v为了方便遍历,会创建EntrySet集合,Map.Entry提供了重要的方法,getKey(), getValue();
  • EntrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node(Node实现了Entry接口)
  • Map中的key不可以重复,当有相同的key时,等价于替换
  • Map中的value是可以重复的
  • Map中的key可以为null,value也可以为null,但是key中的null只能有1个,value中的null可以有多个
  • 常用String类作为Map的key
  • key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
  • map.put
  • map.get
  • entrySet:获取所有关系
  • Set set = map.keySet();获取所有的键
  • Collection c = map.values();获取所有的值
  • remove
  • size
  • isEmpty
  • clear
  • containsKey:查找键是否存在

Map接口实现类

  • Map接口的常用实现类:HashMap、HashTable、Properties
  • HashMap是Map接口使用频率最高的实现类
  • HashMap是以key-val对的方式开存储数据
  • HashMap的key不能重复,值可以重复
  • 添加相同的key,则会覆盖原来的key-val,等同于修改
  • 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
  • HashMap没有实现同步,因此是线程不安全的,没有synchronized方法

HashMap底层

  • 数组+链表+红黑树
  • 底层维护了Node类型的数增速table,默认为null
  • 当创建对象时,将加载因子(loadFactor)初始化为0.75
  • 当添加key-val时,通过key的hash值得到在table的索引,然后判断该索引处是否有元素,如果没有元素则直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
  • 第一次添加,则需要扩容table容量为16,临界值(threshold)为12
  • 以后再扩容,则需要扩容table容量为原来的2倍,临界值值为原来的2倍
  • 在Java8中,如果一条链表的元素个数是TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树)

HashTable

  • 存放的是键值对:k-v
  • 键和值都不能为null,否则会抛出空指针异常
  • 使用方法基本和HashMap一样
  • HashTable是线程安全的
  • 底层有一个数组 HashTable$Entry[], 初始化大小为11、
  • 临界值为8 (11*0.75)
  • 扩容:int newCapacity = (oldCapacity << 1) + 1

Properties

  • Properties类继承自HashTable类并实现了Map接口,也是使用键值对的形式来保存数据
  • key和value不能为空,有相同的key,value也会被替换
  • 使用特点和HashTable类似
  • Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改

如何选择集合实现类

  1. 先判断存储类型(一组最新【单列】或一组键值对【双列】)
  2. 一组对象:Collection接口
    • 允许重复:List
      • 增删多:LinkedList(底层维护了一个双向链表)
      • 改查多:ArrayList(底层维护Object类型的可变数组)
    • 不允许重复:Set
      • 无序:HashSet(底层是HashMap,维护了几个哈希表,即数组+链表+红黑树)
      • 排序: TreeSet
      • 插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
  3. 一组键值对:Map
    • 键无序:HashMap(底层是哈希表)
    • 键排序:TreeMap
    • 键插入和取出顺序一致:LinkedHashMap
    • 读取文件:Properties

TreeSet

  • 是有序的
  • 构造函数可以传一个比较器对象,赋给TreeSet底层的TreeMap的属性this.comparator
  • 底层是TreeMap

泛型

  • 泛型执行的数据类型,只能是引用类型,不能是基本类型
  • 在给泛型指定具体类型后,可以传入该类型或者其子类类型
  • 泛型默认是Object
  • 泛型不具备继承性
  • <?> : 支持任意泛型类型
  • <? extends A> : 支持A类及A类的子类
  • <? super A> : 支持A类及A类的父类

自定义泛型类

  • 普通成员可使用泛型
  • 使用泛型的数组,不能初始化(因为数组在new的时候不能确定数组类型,就无法在内存开空间)
  • 静态方法中不能使用类的泛型(如果静态方法和静态属性使用了泛型,JVM就不能初始化)
  • 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  • 如果在创建对象时,没有指定类型,默认为Object

自定义泛型接口

  • 接口中,静态成员不能使用泛型
  • 泛型接口的类型,在继承接口或者实现接口时确定
  • 没有指定类型,默认为Object

自定义泛型方法

  • public <T, R> void fly(T t, R r){}
  • 泛型方法,可以定义在普通类中,也可以定义在泛型类中

绘图

  • JFrame 画框
  • JPanel 画布
    • paint(Graphics g) 画笔
  • Component类提供了2个和绘图相关最重要的方法
    1. paint(Graphics g) 绘制组件的外观
    2. repaint() 刷新组件的外观
  • 当组件第一次在屏幕显示的时候,程序会自动的调用paint()方法来绘制组件
  • 在一下情况paint()将会被调用
    1. 窗口最小化,再最大化
    2. 窗口的大小发生变化
    3. repaint函数被调用
package com.draw;

import javax.swing.*;
import java.awt.*;
public class DrawCircle extends JFrame {

    private MyPanel mp = null;
    public static void main(String[] args) {
        new DrawCircle();
    }

    public DrawCircle() throws HeadlessException {
        // 初始化面板
        mp = new MyPanel();
        // 把面板放入窗口
        this.add(mp);
        //设置窗口大小
        this.setSize(400, 300);
        // 关闭窗口时退出程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }
}


class MyPanel extends JPanel{
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        System.out.println("paint被调用了...");
        // 画圆
        g.drawOval(10, 10, 100, 100);
        // 画直线
        g.drawLine(10, 10, 100, 100);
        // 画矩形
        g.drawRect(10, 10, 100, 100);
        //设置画笔的颜色
        g.setColor(Color.blue);
        g.fillOval(10, 10, 100, 100);
        // 画图片
        Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pkq.png"));
        g.drawImage(image,10, 10, 100, 100, this);
        //给画笔设置颜色和字体
        g.setColor(Color.red);
        g.setFont(new Font("隶书", Font.BOLD, 50));
        //位置是设置的字符串的左下角
        g.drawString("Hello", 200, 200);
    }
}

java的事件控制

package com.draw;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class DrawCircle extends JFrame {

    private MyPanel mp = null;
    public static void main(String[] args) {
        new DrawCircle();
    }

    public DrawCircle() throws HeadlessException {
        // 初始化面板
        mp = new MyPanel();
        // 把面板放入窗口
        this.add(mp);
        //设置窗口大小
        this.setSize(400, 300);
        // 窗口JFrame对象可以监听键盘事件
        this.addKeyListener(mp);
        // 关闭窗口时退出程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }
}


class MyPanel extends JPanel implements KeyListener {
    int x = 10;
    int y = 10;
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.fillOval(x, y, 20, 20);

    }

    //有字符出书时,被触发
    @Override
    public void keyTyped(KeyEvent e) {

    }

    // 当某个键被按下,被触发
    @Override
    public void keyPressed(KeyEvent e) {
        System.out.println((char)e.getKeyCode() + "被按下..");
        if(e.getKeyCode() == KeyEvent.VK_DOWN){
            y++;
        } else if(e.getKeyCode() == KeyEvent.VK_UP){
            y--;
        }else if(e.getKeyCode() == KeyEvent.VK_LEFT){
            x--;
        }else if(e.getKeyCode() == KeyEvent.VK_RIGHT){
            x++;
        }
        //让面板重绘
        this.repaint();
    }

    // 当某个键松开,被触发
    @Override
    public void keyReleased(KeyEvent e) {

    }
}

线程

  • 单线程:同一个时刻,只允许执行一个线程
  • 多线程:同一个时刻,可以执行多个线程
  • 并发:同一个时刻,多个任务交替执行(单核CPU)
  • 并行:同一个时刻,多个任务同时执行(多核CPU)
// 查看CPU数量
Runtime runtime = Runtime.getRuntime();
int i = runtime.availableProcessors();
System.out.println(i);
  • 当一个类继承了Thread类,该类就可以当做线程使用
    • run(): Thread类实现了Runnable接口的run方法
    • 重写run方法
package com.test;
public class CpuNum {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        // cat.run(); // run方法就是一个普通的方法,没有真正的启动一个线程,就会吧run方法执行完毕,才向下执行
        // start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,有COU统一调度
        // 真正实现多线程的效果,是start0(),而不是run()
        cat.start(); //启动线程,最终hi执行cat的run方法。start0:本地方法,是JVM调用,底层是C/C++

        for (int i = 0; i < 60; i++) {
            System.out.println(i);
            Thread.sleep(1000);
        }
    }
}

class Cat extends Thread{
    int times = 0;
    @Override
    public void run() {
        super.run();
        while (true){
            System.out.println("run...." + (++times));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 8){
                break;
            }
        }
    }
}

创建线程

  • java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了
  • java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
package com.test;
public class CpuNum {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        // 静态代理模式
        Thread thread = new Thread(cat);
        thread.start(); //启动线程,最终hi执行cat的run方法。start0:本地方法,是JVM调用,底层是C/C++

        for (int i = 0; i < 60; i++) {
            System.out.println(i);
            Thread.sleep(1000);
        }

        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

class Cat implements Runnable{
    int times = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("run...." + (++times));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 8){
                break;
            }
        }
    }
}

class Tiger implements Runnable{
    @Override
    public void run() {
        System.out.println("tiger....");
    }
}
class ThreadProxy implements Runnable{
    private Runnable target = null;

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    @Override
    public void run() {
        if(target != null){
            target.run();
        }
    }

    public void start(){
        start0();
    }

    public void start0(){
        run();
    }
}

线程常用方法

  • setName
  • getName
  • start
  • run
  • setPriority:更改线程的优先级
  • getPriority
  • sleep
  • interrupt:中断线程
  • yield:线程的礼让
  • join:线程的插队

如果我们希望当main线程结束后,子线程自动结束,秩序将子线程设为守护线程即可:myDaemonThread.setDaemon(true); myDaemonThread.start();

Synchronized

  1. 同步代码块: Synchronized(对象 / this){}
  2. 同步方法:public Synchronized void m(String name){}
    • 同步方法(静态的)的锁为当前类本身 SellTicket.class
    • 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)

IO流

文件

创建文件

  1. new File(String pathName):根据路径构建一个File对象
  2. new File(File parent, String child):根据父目录文件+子路径构造
  3. new File(String parent, String child):根据父目录+子路径构造
// new File(String pathName) //枸橘路径构建一个File对象
public void create(){
    String filePath = "D:\news1.txt";
    File file = new File(filePath);

    try {
        file.createNewFile();
        System.out.println("文件创建成功");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// new File(File parent, String child)  //根据父目录文件+子路径构造
public void create02(){
    File parentFile = new File("D:\");
    String fileName = "news2.txt";
    File file = new File(parentFile, fileName);
    try {
        file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// new File(String parent, String child)  //根据父目录+子路径构造
public void create03(){
    String parentPath = "D:\";
    String fileName = "news3.txt";
    File file = new File(parentPath, fileName);
    try {
        file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

获取文件相关信息

File file = new File("D:\news1.txt");
System.out.println(file.getName()); // news1.txt
System.out.println(file.getAbsolutePath()); // D:\news1.txt
System.out.println(file.getParent()); // D:\
System.out.println(file.length()); // 0
System.out.println(file.exists()); // T
System.out.println(file.isFile()); // T
System.out.println(file.isDirectory()); // F

目录的操作和文件删除

  • mkdir: 创建一级目录
  • mkdirs: 创建多级目录
  • delete:删除文件或目录
@Test
public void m1(){
    String path = "D:\news1.txt";
    File file = new File(path);
    if(file.exists()){
        if(file.delete()){
            System.out.println("删除成功");
        }else{
            System.out.println("删除失败");
        }
    }else{
        System.out.println("该文件不存在");
    }
}

@Test
public void m2(){
    String path = "D:\demo";
    File file = new File(path);
    if(file.exists()){
        if(file.delete()){
            System.out.println("删除成功");
        }else{
            System.out.println("删除失败");
        }
    }else{
        System.out.println("该目录不存在");
    }
}

@Test
public void m3(){
    String dirPath = "D:\demo\a\b";
    File file = new File(dirPath);
    if(file.exists()){
        System.out.println("该目录已存在...");
    }else{
        System.out.println("该目录不存在...");
        if(file.mkdirs()){
            System.out.println(dirPath + "创建成功...");
        }else{
            System.out.println(dirPath + "创建失败...");
        }
    }
}

IO流原理及流的分类

java IO流原理

  1. I/O 是Input/Output的缩写,用于处理数据传输,如读写文件,网络通讯等。
  2. Java程序中,对于数据的输入输出操作以流(stream)的方式进行
  3. java.io包下提供了各种流类和接口,用于获取不同种类的数据,并通过方法输入或输出数据

流的分类

  1. 按操作数据单位不同分为:字节流(8 bit), 字符流(按字符)
    • 字节输入流:InputStream(文件 -> 程序)
      • FileInputStream:文件输入流
      • BufferedInputStream:缓冲字节输入流
      • ObjectInputStream:对象字节输入流
      • FilterInputStream:
      public void readFile01(){
          String filePath = "D:\news2.txt";
          int readData = 0;
          FileInputStream fileInputStream = null;
          try {
              fileInputStream = new FileInputStream(filePath);
              // read(): 从该输入流读取一个字节的数据,如果没有输入可以,此方法种植
              // 如果返回-1,表示读取完毕
              while ((readData = fileInputStream.read()) != -1){
                  System.out.print((char)readData);
              }
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              try {
                  fileInputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
      public void readFile02(){
          String filePath = "D:\news2.txt";
          int readData = 0;
          byte[] buf = new byte[8]; // 一次读取8个字节
          int readLen = 0;
          FileInputStream fileInputStream = null;
          try {
              fileInputStream = new FileInputStream(filePath);
              // read(buf):
              // 如果返回-1,表示读取完毕
              // 如果读取正常,表示实际读取的字节数
              while ((readLen = fileInputStream.read(buf)) != -1){
                  System.out.print(new String(buf, 0, readLen));
              }
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              try {
                  fileInputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 字节输出流:OutputStream
      • FileOutputStream
      public void writeFile(){
          String filePath = "D:\news3.txt";
          FileOutputStream fileOutputStream = null;
          try {
              // new FileOutputStream(filePath)创建方式,当写入内容时,会覆盖原来的内容
              // new FileOutputStream(filePath, true)创建方式,当写入内容时,会追加带文件后面
              fileOutputStream = new FileOutputStream(filePath);
              // 写入一个字节
              fileOutputStream.write('H');
              //写入字符串
              String str = "hello";
              // str.getBytes()可以把字符串转为字节数组
              // write() 方法后面两个参数可以没有,没有就算写入整个字符串
              fileOutputStream.write(str.getBytes(), 0 , str.length());
          } catch (FileNotFoundException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              try {
                  fileOutputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 字符输入流:Reader
      • FileReader
      public void FileReader01(){
              String filePath = "D:\news3.txt";
              FileReader fileReader = null;
      //        int data = 0;
              int readLen = 0;
              char[] buf = new char[8];
              try {
                  fileReader = new FileReader(filePath);
      //            while ((data = fileReader.read()) != -1){
      //                System.out.print( (char) data);
      //            }
      
                  while ((readLen = fileReader.read(buf)) != -1){
                      System.out.print(new String(buf, 0, readLen));
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
      
              }
          }
      
    • 字符输出流:Writer
      • OutputStreamWriter
        • FileWriter: 使用后,必须关闭(close)或刷新(flush),否则写入不到指定的文件
        public void fileWriter(){
            String filePath = "D:\news2.txt";
            FileWriter fileWriter = null;
            char[] chars = {'a', 'b', 'c'};
            try {
                // 添加true,为追加模式
                fileWriter = new FileWriter(filePath, true);
                // 写入单个字符
                fileWriter.write('L');
                // 写入指定数组(可以加后面两个参数指定的写入部分长度的数组)
                fileWriter.write(chars, 0, 2);
                // 写入字符串(可以加后面两个参数指定的写入部分长度的字符串)
                fileWriter.write("你好啊", 0, 1);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // FileWrite,一定要关闭流,才能真正写入数据
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        

java IO流共涉及40多个类,都是从如上4个抽象基类派生的,由这4分类派生出来的子类名称都是以其父类名作为子类名后缀

  1. 按数据流的流向不同分为:输入流,输出流
  2. 按流的角色的不同分为:节点流,处理流/包装流

节点流和处理流

  1. 节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
  2. 处理流也叫包装流,是连接在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter
  3. BufferedReader、BufferedWriter属于字符流,是按照字符来读取数据的,不要去操作二进制文件【声音、视频、doc、pdf】,不然会造成文件损坏
  4. 关闭处理流时,只需要关闭外层流即可

BufferedReader和BufferedWriter

public class BufferedCreate {
    public static void main(String[] args) {
        BufferedReader_ bufferedReader_ = new BufferedReader_(new StringReader_());
        bufferedReader_.readString();
        bufferedReader_.readStrings(3);
        BufferedReader_ bufferedReader_1 = new BufferedReader_(new FileReader_());
        bufferedReader_1.readFile();
        bufferedReader_1.readFiles(3);
    }

}

abstract class Reader_{
    public void readFile(){}
    public void readString(){}
}
class FileReader_ extends Reader_{
    public void readFile(){
        System.out.println("读取了文件");
    }
}

class StringReader_ extends Reader_{
    public void readString(){
        System.out.println("读取了字符串");
    }
}

class BufferedReader_ extends Reader_{
    private Reader_ reader_;

    public BufferedReader_(Reader_ reader_) {
        this.reader_ = reader_;
    }

    @Override
    public void readFile() {
        reader_.readFile();
    }

    @Override
    public void readString() {
        reader_.readString();
    }

    public void readFiles(int num){
        for (int i = 0; i < num; i++) {
            reader_.readFile();
        }
    }

    public void readStrings(int num){
        for (int i = 0; i < num; i++) {
            reader_.readString();
        }
    }
}
// BufferedReader
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedReader_ {
    public static void main(String[] args) throws Exception {
        String filePath = "D:\ts.txt";
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
        String line;
        // readLine(), 按行读取, 没有读取换行符,当返回null时,表示文件读取完毕
        while ((line = bufferedReader.readLine()) != null){
            System.out.println(line);
        }

        // 关闭流,只需要关闭BufferedReader,因为底层会自动去关闭节点流FileReader
        bufferedReader.close();
    }

}
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriter_ {
    public static void main(String[] args) throws IOException {
        String filePath = "D:\news2.txt";
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true));
        bufferedWriter.write("今天星期四");
        bufferedWriter.newLine(); // 插入一个和系统相关的换行符
        bufferedWriter.write("今天星期四2");
        bufferedWriter.write("今天星期四3");
        bufferedWriter.write("今天星期四4");
        bufferedWriter.close();
    }

}

BufferedInputStream和BufferedOutputStream

  1. 按照字节处理数据,可以处理二进制文件

ObjectInputStream和ObjectOutputStream

  1. 用于序列化和反序列化,要实现Serialzable或者Externalizable接口
  2. 序列化时,用static和transient修饰的成员不会序列化
  3. 序列化对象时,要去里面的属性也实现序列化接口

标准输入和输出

  • System.in
    • 编译类型:InputStream
    • 运行类型:BufferedInputStream
  • System.out
    • 编译类型:PrintStream
    • 运行类型:PrintStream

转换流 InputStreamReader和OutputStreamWriter

  • 字节流(InputStream)转为字符流(Reader)
  • InputStreamReader(InputStream, Charset)
String filePath = "D:\news3.txt";
BufferedReader br = new BufferedReader(
        new InputStreamReader(
                new FileInputStream(filePath), "utf-8"));
String s = br.readLine();
System.out.println(s);
br.close();
  • OutputStreamReader(OutputStream, Charset)

输入流:内存到磁盘

输出流:磁盘到内存

Properties类

网络通讯

  • java.net
  • TCP协议:传输控制协议
    • 使用TCP协议前,须先建立TCP连接,形成传输数据通道
    • 传输前,采用三次握手方式,是可靠的
    • TCP协议进行通信的两个应用进程:客户端、服务端
    • 在连接中可进行大数据量的传输
    • 传输完毕,需释放已建立的连接,效率低
  • UDP: 用户数据协议
    • 将数据、源、目的封装成数据包,不需要建立连接
    • 每个数据包的大小限制在64K内,不适合传输大量数据
    • 因无需连接,故是不可靠的
    • 发送数据结束时无需释放资源(因为不是面向连接的),速度快
// 获取本机的InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);

// 根据指定的主机名 获取InetAddress对象
InetAddress host = InetAddress.getByName("DESKTOP-PF1OPBN");
System.out.println(host);

// 根据域名返回InetAddress对象
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2); // www.baidu.com/14.215.177.39

// 根据InetAddress对象,获取对应的地址
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress); // 14.215.177.39

// 根据InetAddress对象,获取对应的主机名或者域名
String hostName = host2.getHostName();
System.out.println(hostName); // www.baidu.com

Socket

  1. socket.getOutputStream()
  2. socket.getInputStream()

TCP编程

1. 编写一个服务器端,一个客户端
2. 服务器端在9999端口监听
3. 客户端连接到服务器,发送"hello,server",然后退出
4. 服务器端接收到客户端发送过来的信息,输出,并退出

服务器端

// 1. 在本机的9999端口监听,等待连接(端口没有被占用)
// 这个 ServerSocket 可以通过 accept() 返回对个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接");

// 2. 当没有客户端连接9999端口时,程序会阻塞,等待连接
// 当有客户端连接时,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端socket=" + socket.getClass());

// 3. 通过socket.getInputStream()读取客户端谢日到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();

// 4. IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
    System.out.println(new String(buf, 0, readLen));
}

// 5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, client".getBytes());

// 设置写入结束标志
socket.shutdownOutput();

// 6. 关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();

客户端

// 1. 连接服务器(ip、端口)
// 连接本地的9999端口,如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端socket=" + socket.getClass());

// 2. 连接上后,生成Socket,通过socket.getOutputStream()
// 得到和socket对象相关联的输出流对象
OutputStream outputStream = socket.getOutputStream();

// 3. 通过输出流,写入数据到数据通道
outputStream.write("hello, server".getBytes());

// 设置写入结束标志
socket.shutdownOutput();

// 4. 获取和socket关联的输入流,读取数据(字节),显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
    System.out.println(new String(buf, 0, readLen));
}

// 5. 关闭流对象和socket
outputStream.close();
socket.close();
System.out.println("客户端退出...");

TCP字符流编程

  1. 客户端给服务端发消息
  • 客户端
    • socket.getOutputStream()
    • 将 OutputStream -> Writer
    • 使用转换流 OutputStreamWriter(字节流)
  • 服务端
    • socket.getInputStream
    • 将InputStream -> Reader
    • 使用转换流InputStreamReader
  • 设置写入结束标记,也可以用writer.newLine(), 但是这个标记必须使用readLine()才能读取到
  • 服务端代码(字符流方式)
// 1. 在本机的9999端口监听,等待连接(端口没有被占用)
// 这个 ServerSocket 可以通过 accept() 返回对个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接");

// 2. 当没有客户端连接9999端口时,程序会阻塞,等待连接
// 当有客户端连接时,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端socket=" + socket.getClass());

// 3. 通过socket.getInputStream()读取客户端谢日到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();

// 4. IO读取, 使用字节流
/*
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
    System.out.println(new String(buf, 0, readLen));
}
*/


// IO读取,使用字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);

// 5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
// 使用字节流
/* outputStream.write("hello, client".getBytes());
 设置结束标记
 socket.shutdownOutput();*/
// 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello client 字符流");
bufferedWriter.newLine();
bufferedWriter.flush();

// 6. 关闭流和socket
bufferedReader.close();
bufferedWriter.close();
//        outputStream.close();
//        inputStream.close();
socket.close();
serverSocket.close();
  • 客户端代码(字符流方式)
// 1. 连接服务器(ip、端口)
// 连接本地的9999端口,如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端socket=" + socket.getClass());

// 2. 连接上后,生成Socket,通过socket.getOutputStream()
// 得到和socket对象相关联的输出流对象
OutputStream outputStream = socket.getOutputStream();

// 3. 通过输出流,写入数据到数据通道(字节流方式)
// outputStream.write("hello, server".getBytes());
// 设置写入结束标志
// socket.shutdownOutput();

// 以下4行为字符流的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello, server字符流");
bufferedWriter.newLine(); // 插入一个换行符,表示写入的内容结束
bufferedWriter.flush(); //使用的字符流,需要手动刷新,否则数据不会写入数据通道



// 4. 获取和socket关联的输入流,读取数据(字节),显示
InputStream inputStream = socket.getInputStream();
/*
// 字节
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
    System.out.println(new String(buf, 0, readLen));
}*/
// 字符读取
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);

// 5. 关闭流对象和socket
bufferedReader.close();
bufferedWriter.close();
//        outputStream.close();
socket.close();
System.out.println("客户端退出...");

UDP编程

数据库

约束

  1. 非空约束:NOT NULL
  2. 唯一约束:UNIQUE
  3. 主键约束:PRIMARY KEY(非空且唯一)
    • auto_increment 自动增长
  4. 检查约束: CHECK(mysql不支持)
  5. 默认约束: DEFAULT
    • 值为null不会按照默认填充
  6. 外键约束:FOREIGN KEY
    • CONSTRAINT fk_emp_dept FOREIGN KEY(dep_id) REFERENCES dept(id)
    • 删除外键:alter table emp drop FOREIGN key fk_emp_dept
    • alter table emp add CONSTRAINT fk_emp_dept FOREIGN KEY(dep_id) REFERENCES dept(id)

#JDBC

  • 创建工程,导入驱动jar包
  • 注册驱动
// mysql5后可以不用写了
Class.forName("come.mysql,jdbc.Driver");
  • 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
//String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
  • 定义sql语句
String sql = "update ...";
  • 获取执行sql对象
Statement stmt = conn.createStatement();

PreparedStatement pstmt = conn.prepareStatement(); //预编译sql并执行sql语句
  • 执行sql
stmt.executeUpdate(sql);
ResultSet rs = stmt.excuteQuery(sql);
  • 处理返回结果
while(rs.next()){
    int id = rs.getInt(1);  //列从1开始
    ...
}
  • 释放资源

Connection

事务管路

  • 开启事务: setAutoCommit(true)
  • 提交事务: commit()
  • 回滚事务: rollback()

Statement

  • executeUpdate(sql)
  • executeQuery(sql)