第二阶段:JAVA基础深化与提高

文章目录

  • 2 第二阶段 JAVA深化和提高
    • 2.1 面向对象深化
      • 2.1.1 抽象类和抽象方法
        • 2.1.1.1 抽象方法概念
        • 2.1.1.2 什么情况下使用抽象方法和抽象类?
      • 2.1.2 接口详解
        • 2.1.2.1 接口的概念
        • 2.1.2.2 接口中可以包含什么?
        • 2.1.2.3 接口与类的关系
      • 2.1.3 接口的特征_使用接口的意义
        • 2.1.3.1 接口的传递性
        • 2.1.3.2 接口的继承性(多继承)
        • 2.1.3.3 使用接口的意义_接口实现多态的步骤
      • 2.1.4 内部类
        • 2.1.4.1 内部类的概念
        • 2.1.4.2 内部类的特点
        • 2.1.4.3 内部类的优缺点
        • 2.1.4.4 内部类的访问
        • 2.1.4.5 内部类访问外部类
        • 2.1.4.6 定义在方法中的内部类
        • 2.1.4.7 匿名内部类(没有名字的内部类)
      • 2.1.5 String类常用方法_API文档阅读
        • 2.1.5.1 String 类的定义
        • 2.1.5.2 String类的常用方法
        • 2.1.5.3 API文档
      • 2.1.6 String类与常量池
        • 2.1.6.1 String类的构造方法
        • 2.1.6.2 通过String类学习内存空间的运作
      • 2.1.7 字符串相等的判断
        • 2.1.7.1
    • 2.2 数组深化
      • 2.2.1 多维数组
        • 2.2.1.1 二维数组的格式
        • 2.2.1.2 二维数组的初始化
        • 2.2.1.3 **二维数组中的元素,实际上是存储一维数组的地址**
        • 2.2.1.4 二维数组的遍历
      • 2.2.2 Object数组存储表格数据
      • 2.2.3 数组的拷贝
        • 2.2.3.1 地址的拷贝(即引用的拷贝)
        • 2.2.3.2 值的拷贝:这里使用System.arrcopy()方法
      • 2.2.4 java.util.Arrays 工具类的使用
        • 2.2.4.1 java.util.Arrays常用工具介绍
      • 2.2.5 冒泡排序的基础算法
      • 2.2.6 冒泡排序法的优化算法
      • 2.2.7 二分法(折半查找法)
    • 2.3 异常机制 Exception
      • 2.3.1 异常概念_分类
        • 2.3.1.1 异常的概念及出现的情况
        • 2.3.1.2 Error 错误
        • 2.3.1.3 Exception 异常的分类
      • 2.3.2 捕获异常
        • 2.3.2.1 try-catch 组合
        • 2.3.2.2 try-finally 组合
        • 2.3.2.3 try-catch-final 组合
      • 2.3.3 声明异常
        • 2.3.3.1 声明异常的关键字throws
        • 2.3.3.2 继承关系中的异常声明
        • 2.3.3.3 throw 抛出异常对象
      • 2.3.4 常见的简单异常的解决方法
        • 2.3.5 常见的异常类型
          • 2.3.5.1 RuntimeException类下的常见异常
          • 2.3.5.2 Checked类下的常见异常
      • 2.3.6 自定义异常
        • 2.3.6.1 自定义异常类的步骤
    • 2.4 常用类的基本用法
      • 2.4.1 包装类的基本用法
        • 2.4.1.1 为什么需要包装类(Wrapper Class)
        • 2.4.1.2 包装类和基本数据类型的对应关系
        • 2.4.1.3 包装类的继承关系
        • 2.4.1.4 包装类的基本操作
      • 2.4.2 自动装箱和拆箱
        • 2.4.2.1 自动装箱auto-boxing
        • 2.4.2.2 自动拆箱 unboxing
      • 2.4.3 Stringg 类底层分析_JDK源码分析
        • 2.4.3.1 String 的底层数据结构
      • 2.4.4 StringBuilder_StringBuffer用法_JDk底层源码分析
        • 2.4.4.1 可变的字符串
        • 2.4.4.2 StringBuilder与StringBuffer的常用方法的使用
      • 2.4.5 不可变字符序列和可变字符序列使用陷阱
      • 2.4.6 java.util.Date类
      • 2.4.7 DateFormat与SimpleDateFormat
      • 2.4.8 Calendar 日历类
        • 2.4.8.1 Calendar的类
        • 2.4.8.2 GregorianCalendar类的常用方法的使用
      • 2.4.9 Math类的常用方法_静态导入
        • 2.4.9.1 Math类的常用方法
        • 2.4.9.2 静态导入
      • 2.4.10 FIle类
        • 2.4.10.1 File类操作文件
        • 2.4.10.2 File类操作文件夹
        • 2.4.10.3 特别注意
      • 2.4.11 使用递归算法遍历目录结果_树结构
      • 2.4.12 枚举
        • 2.4.12.1 枚举的构建
        • 2.4.12.2 枚举的成员
        • 2.4.12.3 枚举的调用
        • 2.4.12.4 枚举的方法和其它成员变量的调用
        • 2.4.12.5 枚举成员与String类型的互转
        • 2.4.12.6 枚举类型在switch方法的使用
    • 2.5 容器(集合)
      • 2.5.1 基本概念_Collection_Set_List接口介绍
        • 2.5.1.1 容器的概念
        • 2.5.1.2 容器的分类
        • 2.5.1.3 容器框架(Collection)及其提供的主要方法
        • 2.5.1.4 集合的遍历
        • 2.5.1.5 各接口的特点
      • 2.5.2 迭代器及其使用
        • 2.5.2.1 **迭代器Iterator接口的概念及其方法**
        • 2.5.2.2 ListIterator迭代器:List接口下的容器所使用的的迭代器接口
      • 2.5.3 List集合_ArryaList
        • 2.5.3.1 ArrayList容器
        • 2.5.3.2 ArrayList的特有的方法详解
        • 2.5.3.3 ArrayList_JDK源码分析
      • 2.5.4 List集合_LinkedList容器
        • 2.5.4.1 链表的概念
        • 2.5.4.2 LinkedList的特有方法
        • 2.5.4.3 源码分析
      • 2.5.6 List集合_Vector容器
        • 2.5.6.1 相对ArrayList,Vctor中较特殊的方法:elements() 与 迭代器的比较
        • 2.5.6.2 为什么还要继续使用Enumeration接口
      • 2.5.7 Set接口_HashSet
        • 2.5.7.1 为啥要用HahSet?
        • 2.5.7.2 HashSet容器的底层数据结构
        • 2.5.7.3 HashSet容器的操作原理(引用地址[:资料引用地址](https://www.cnblogs.com/runwulingsheng/p/5208762.html))
      • 2.5.8 HashMap的底层原理
      • 2.5.9 二叉树和红黑树
      • 2.5.10 TreeSet的使用_JDK源码分析
      • 2.5.11 泛型
        • 2.5.11.1 什么是泛型
        • 2.5.11.2 泛型的分类
      • 2.5.12 泛型的高级使用_容器中使用泛型
        • 2.5.12.1 泛型的高级使用
      • 2.5.15 容器体系框架总结
    • 2.6 IO流技术
      • 2.6.1 IO流的基本概念
      • 2.6.2 流概念细分和体系_四大抽象类
        • 2.6.2.1 流的分类
        • 2.6.2.2 IO流的四大抽象类
      • 2.6.3 文件字节流FileInputStream与FileOutputStream
        • 2.6.3.1 FileInputStream类的的使用
        • 2.6.3.2 FileOutputStream类的使用方法
      • 2.6.4 使用字节流实现文件复制
      • 2.6.5 文件字符流_FileWriter类与FileReader类
        • 2.6.5.1 FileReader类
        • 2.6.5.2 FileWriter类
      • 2.6.6 缓冲字节流_缓冲字符流
        • 2.6.6.1 缓冲字节流——BufferedInputStream
        • 2.6.6.2 缓冲字节流——BufferedOutputStream
        • 2.6.6.3 缓冲字符流——BufferedReader
        • 2.6.6.4 缓冲字符流——BufferedWriter
      • 2.6.7 转换流
        • 2.6.7.1 InputStreamReader
        • 2.6.7.3 OutputStreamWriter
      • 2.6.8 打印流
        • 2.6.8.1 打印流的概念
        • 2.6.8.2 字节打印流_PrintStream
        • 2.6.8.3 字符打印流_PrintWriter
      • 2.6.9 数据流
        • 2.6.9.1 DataInputStream和DataOutputStream
      • 2.6.10 对象流
        • 2.6.10.1 对象序列化(Serializable)
        • 2.6.10.2 为什么要对象序列化?
        • 2.6.10.3 对象序列化的条件
      • 2.6.11 序列化和反序列化
      • 2.6.12 文件夹的复制
        • 2.6.12.1 字节流和字符流的选择
        • 2.6.12.2 问题分解
      • 2.6.13 字节数组流
        • 2.6.13.1 字节数组流的特点
        • 2.6.13.2 ByteArrayInputStream
        • 2.6.13.3 ByteArrayOutputStream
      • 2.6.14 设计模式_装饰器模式
      • 2.6.15 IO体系总结
      • 2.6.16 Apache IOUtils的使用_Apache FileUtils的使用
    • 2.7 多线程技术
      • 2.7.1 多线程的基本概念
      • 2.7.2 通过继承Thread实现多线程
      • 2.7.3 通过接口实现Runnable接口实现多线程
      • 2.7.4 线程的生命周期
      • 2.7.5 获取线程基本信息的方法
      • 2.7.6 多线程的安全问题
      • 2.7.7 暂停线程执行sleep/yeild/stop
      • 2.7.8 线程的优先级问题
      • 2.7.9 线程同步——具体实现
      • 2.7.11 生产者消费者模式的实现1-基本功能实现
      • 2.7.12 生产者消费者模式的实现2-线程通信引入
    • 2.8 网络编程技术
      • 2.8.1 网络编程的基本概念
      • 2.8.2 端口和Socket含义
      • 2.8.3 TCP和UDP协议区别
      • 2.8.4 TCP协议数据传递细节
      • 2.8.5 UDP协议数据传递细节
      • 2.8.6 InetAddress类与InetSocketAddress类
      • 2.8.7 URL类
      • 2.8.8 基于TCP协议的Socket编程 模拟登陆服务器

2 第二阶段 JAVA深化和提高

2.1 面向对象深化

2.1.1 抽象类和抽象方法

2.1.1.1 抽象方法概念

  • 抽象类用abstract描述
  • 抽象类有构造方法、可以包含成员变量、成员方法、静态方法、final修饰的方法、抽象方法
  • final修饰的方法只能被子类调用,不允许被子类重写
  • 使用abstract修饰的方法是抽象方法,含有抽象方法的类必须是抽象类
  • 抽象方法只定义方法名,不能有方法体,如:
public abstract void hello();
  • 抽象类的子类必须实现父类中的抽象方法(也可以只是把方法写出来,但是方法体中可以不写东西)或该子类本身也是一个抽象方法

2.1.1.2 什么情况下使用抽象方法和抽象类?

通过抽象类,可以避免子类设计的随意性。通过抽象类,我们就可以做到严格限制子类的的设计,使子类之间更加通用。(程序的可拓展和可维护性)

  • 父类的名称比较抽象,创建的对象无意义,如:
Animal 动物类->设计为抽象类,定义一些抽象方法如sbstract void shout();方法

2.1.2 接口详解

2.1.2.1 接口的概念

  • 如果一个类中所有的方法均为abstract方法,那么这个类就可以声明为接口(接口不是类)
  • 新建一个接口类型,右键new->Interface
  • 接口使用interface而不是class修饰,是一种数据类型,引用数据类型
  • 接口中不允许定义构造方法
  • 接口可以被另一个接口继承,使用extends关键字
  • 同样一个接口中只允许有一个public修饰的接口,其它的为内部接口

2.1.2.2 接口中可以包含什么?

  • 抽象方法:
    Interface默认的方法就是抽象方法,默认被修饰为public abstract
  • 非抽象方法(jdk1.8新特性)
//必须使用default修饰
public default void method(){
    
}
  • 属性常量:
    接口中的属性默认且只能是public final static 修饰的,且必须赋值

2.1.2.3 接口与类的关系

  • 类去实现接口中的实现方法:实现关系,需要使用关键字 implements
public class A implements MyInterface{
    
}
  • 一个类既有继承关系又有实现关系:继承在前,实现在后,写法如:
public class A extends B implements C{
    
}
  • 一个类,它的父类中有和所实现的接口完全相同的方法,他实现的方法是父类来的
  • 一个类,他的父类和实现接口中有同名的方法,传递的参数不相同,那么对这个类要求他对父类和所实现的接口实现方法的重载/重写(在这个子类里写)

2.1.3 接口的特征_使用接口的意义

2.1.3.1 接口的传递性

如果接口B继承了接口A,而接口中有一个抽象方法method();那么B的一个实现将拥有且必须实现这个抽闲方法method()

public interface InterfaceA{
    public abstract void method();
}

interface InterfaceB extends InterfaceA{
    
}

class Iml implements InterfaceB{
    @Override
    public abstract void method{
        //TODO balabalabala
    }
}


2.1.3.2 接口的继承性(多继承)

一个类可以继承多个接口,格式如下

class ClassExample implements InterfaceA,InterfaceB{ //多个接口之间使用逗号分开
    
}

2.1.3.3 使用接口的意义_接口实现多态的步骤

  • 意义:可以实现设计与实现的分离,抽象出N多不同类的共同点
举例:飞机,鸟,炮弹,宇宙飞船这几种物体
继承:is-a 关系,三角形is a 几何图形(实例)
接口:has-a 关系 鸟has a飞行功能,飞机has a 飞行功能,我们就可以用接口的方式来定义“飞”这种能力,让其他类去实现
//接口体现的是一种能力
  • 接口实现多态的步骤
1.编写接口
2.实现类实现接口中的方法(实现方法不加abstract)
3.接口(类型)new 实现类对象
这就是面向接口的编程
public interface Fly{
    void flying();
}
class Bird implements  Fly{
    @override
    public  void flying{
        System.out.println("小鸟在飞");
    }
}
class Plane implements Fly{
    @override
    public void flying{
        System.out.println("飞机在飞");
    }
}

class Dog implements Fly{
    @override
    public void flying{
        System.out.println("小狗被踢飞");
    }
}
//以下代码在一个测试类中
public static void main(String args[]){
    Fly bird = new Bird;
    bird.flying();
    Fly puppy =new Dog;
    puppy.flying();
    Fly airPlane =new Plane();
    airPlane.flying();
}

2.1.4 内部类

2.1.4.1 内部类的概念

  • 内部类是写在一个外部类中的类,他们的关系是嵌套关系

2.1.4.2 内部类的特点

  • 内部类可以直接访问外部类的成员,反之则不行
  • 内部类作为外部类的成员,可声明为private、default、protect和public
  • 内部类定义在外部类中不可访问的属性,这样就在外部类中实现了此外部类的private还要下的访问权限
  • 脱离了外部类,无法访问内部类

2.1.4.3 内部类的优缺点

  • 优点:内部类可以直接访问外部类的私有属性
  • 缺点:破坏了类的结构

2.1.4.4 内部类的访问

  • 通过实例化一个外部类的对象,再实例化一个内部类的对象,来访问内部类的属性。
public class Face{
    private String shape="瓜子脸";
    public class Nose{
        private String shape="高鼻梁";
        public void breath(){
            System.out.println("鼻子在呼吸");
        }
    }
//测试类
public void static main(String args[]){
    Face f = new Face();
    Nose n = f.new Nose();
    n.breath();
}
}

  • 静态内部类的访问使用如下格式访问
外部类名.内部类名  //对!就是这样的
当要实例一个静态内部类的对象时:
外部类名.内部类名 实例名 = new 外部内名.内部类名(); //就是这样子

2.1.4.5 内部类访问外部类

  • 当内部类拥有和外部类一样的成员变量时,在内部类中访问外部类的变量需要按这样的格式访问
外部类名.this.成员变量
  • 当内部类被static修饰时,内部类不能访问没有带static修饰的外部类的属性
  • 如果在内部类中写了一个静态方法,那么这个内部类必须被修饰为static的

2.1.4.6 定义在方法中的内部类

定义在方法中的内部能,只能在方法中去使用,如下

public void Hello(){
    
    int a=10;
    class Inner{
        public void kk(){
            System.out.println("a="+a);
        }
    }
    
    new Inner().kk();//只能在方法体中调用方法中的内部类,对于方法外,这个内部类毫无意义
}

2.1.4.7 匿名内部类(没有名字的内部类)

  • 存在的前提是要继承或实现一个接口(最多一个接口。比较常见的是在图形界面编程中使用)
//以匿名继承内部类举例

public abstract class Father{
public abstract void dream() ;
}
class Test{
	public static void main(String[] args){
		Father f = new Father(){         //这个部分就是匿名内部类
		@Override
		public void dream() {
			System.out.println("欧耶实现了父亲的梦想!");
			
		}
		};
		f.dream();//调用内部内的方法
	}
}

2.1.5 String类常用方法_API文档阅读

2.1.5.1 String 类的定义

  • String类相当于char类的数组,数组长度一旦创建不可更改,value的数组还是用了final修饰
String str = "abc";
str = "def"; //在这里字符串并没有被改变,改变的只是指向它的地址
//在常量池中,有两个字符串常量"abc" 与"def",刚开始时前者的地址指向str,执行第二句后,变成了后者的地址指向str

2.1.5.2 String类的常用方法

  • char charAt(int index) 返回字符串中第index个字符
//使用方法
字符串对象.charAt();
  • boolean equalsIgnoreCase(String other)如果字符串与other相等(忽略大小写)则返回true
//使用方法
字符串对象.equalsIgnoreCase(<这里输出要比较的字符串,不区分大小写>) // 返回布尔型
  • int indexOf(String str) 以及 lastIndexOf() 查找指字字符串出现的位置(找不到返回-1)
//不带参数的使用方法
字符串对象.indexOf(<需要找的字符串或字符>)  //返回该字符或字符串在字符串中的位置(正向查找,从0开始)int型
字符串对象.lastIndexOf(<需要找的字符串或字符>) //正向查找最后一个符合要求的字符或字符串
//带参数的使用方法
字符串对象.indexOf(<需要查找的字符串或字符><,index>) //从index位置正向查找最近的一个符合要求的结果,返回位置
字符串对象.lastIndexOf(<需要查找的字符串或字符><,index>) //从index位置方向反向查找最近的符合要求的值,返回位置
  • int length()返回字符串的长度
字符串对象.length() //括号里无需添加参数
  • String replace(char oldChar,char newChar) 获得并返回一个新的替换后的字符串
字符串对象.replace()
//获得并返回一个替换后的新字符串,但并没有改变原字符串(因为字符串只能只能共享,不能改变)
  • boolean startsWith(String prefix) 如果字符串以prefix开始,则返回true
  • boolean endsWith(String prefix) 如果字符串以prefix结尾,则返回true
  • string substring 以及String substring(int beginIndex,int endIndex)返回一个新字符串,该字符串包含从beginIndex开始到endIndex结尾的所有字符(包含beginIndex但不包含endIndex),如果不写endIndex则从beginIndex一直截取到字符串的结尾(包含最后一个)
  • String toLowerCase() 返回一个新字符串,该串将原始字符串中的所有大写字母改成小写字母
  • String toUpperCase() 返回一个新字符串,该字符串将原始字符串中的所有小写字母改写成大写字母
  • String trim() 返回一个新字符串,该串删除了原始字符串头部和尾部的空格(如果中间有空格,中间的空格不会被删掉)

2.1.5.3 API文档

API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

  • JDK1.8的API文档 https://docs.oracle.com/javase/8/docs/api/

2.1.6 String类与常量池

2.1.6.1 String类的构造方法

  • String类的构造方法一览
//无法上传,自定查看eclipse源码
  • string类的构造方法举例
char [] c={'a','b','c'}
String str =new String(c);

2.1.6.2 通过String类学习内存空间的运作

String s1 ="abc";
String s2="a"+"b"+"c";
String s3=new String("abc");
String s4 =s3+"";
String s5= new Stirng("abc");

System.out.println(“s1==s2:”+(s1==s2)); //结果true 因为字符串已经在堆中常量池加载了,不需要开辟空间,所以s1和s2都指向常量池“abc”的地址
System.out.println(“s1==s3:”+(s1==s23); //结果false  因为使用了new,在堆中开辟了空间,s3指向的是堆中的空间的地址
System.out.println(“s1==s4:”+(s1==s4)); //结果false 变量运算时也要在堆中开辟一个空间,在此空间中运算,此空间地址指向s4
System.out.println(“s1==s5:”+(s1==s5)); //结果false 同样是因为使用了new开辟了一个空间

2.1.7 字符串相等的判断

2.1.7.1

  • 使用"=="判断两个String对象的地址是否相同
  • 使用String 类自带的方法equals方法判断(判断的是字符串内容是否相等)
//equals原理:比较两个对象地址是否相同,比较两个字符串是否相同
 public boolean equals(Object anObject) { //传入比较的对象
        if (this == anObject) {  //如果当前对象和比较对象地址相等,返回true
            return true;
        }
        if (anObject instanceof String) {   //如果传入的对象不是String的一个实例,返回false,否则往下再判断
            String anotherString = (String)anObject; //传入对象向下转型为String
            int n = value.length;
            if (n == anotherString.value.length) {          //如果两个字符串的长度不相等,返回false,否则往下判断
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;  //如果有一个字符不相等,则返回false
                    i++;
                }
                return true; //字符全部相等则返回true
            }
        }
        return false;
    }

2.2 数组深化

2.2.1 多维数组

使用二维数组来讲解,二维数组就是用来存储一维数组的数组

2.2.1.1 二维数组的格式

//这里用int型举例
int[][] arrA = new int[index][] //其中index为二位数组的长度

2.2.1.2 二维数组的初始化

//静态初始化
int [][] arryA = {{1,2},{3,4,8},{5,6,7}} //二维数组是不规则的矩阵,它包含的各一维数组可以不相同
//声明了一个二维数组,用于存储3个一维数组,每个一维数组的长度未定
int[][] arrA = new int[3][] 
arrA[0]=new int[3];//声明arrA[0]的长度为3,初始化为0(详见各种数据类型的默认初始化值)
arrA[1]=new int[]{1,2,3};//直接给定该一维数组的元素
arrA[2]=new int[3];
//声明一个二维数组,且给出所有一位数组的长度
int[][] arryB = new int[3][3];

2.2.1.3 二维数组中的元素,实际上是存储一维数组的地址

2.2.1.4 二维数组的遍历

  • 加强for循环
for(<数据类型> <迭代变量> : 数组名){
    System.out.println(<迭代变量>);
}
  • 加强for循环遍历二维数组
for(int[] arr : arrA){
    for(int a : arr){ //arr和上一行的arr是同一个数组(一维数组),a是一个整型迭代变量
        System.out.println(a);
    }
}

2.2.2 Object数组存储表格数据

数组除了可以存储基本数据类型,还可以存储引用数据类型。
这里举个例子,我们使用一个数组来存储一组对象

//在Person类中
public class Person {
private String name;
private String gender;
private int age;

public Person(String name, String gender, int age) {
	super();
	this.name = name;
	this.gender = gender;
	this.age = age;
}
public Person() {
	super();
}
@Override
	public String toString() {
		// TODO Auto-generated method stub
		return name+"\t"+gender+"\t"+age;
	}
}
//在Test测试类中
public class Test {

	public static void main(String[] args) {
	
		Person p[] =new Person[3];
		p[0]=new Person("张三", "男", 32);
		p[1]=new Person("李四","男",21);
		p[2]=new Person("王二麻子","女",23);
		for (int i = 0; i < p.length; i++) {
			System.out.println(p[i]);
		}
	}
}

2.2.3 数组的拷贝

2.2.3.1 地址的拷贝(即引用的拷贝)

数组A=数组B;//将数组B对应的内存空间的地址指向数组A,执行这一句后,数组A与数组B的地址相同,它们完全相等

2.2.3.2 值的拷贝:这里使用System.arrcopy()方法

System.arraycopy(<数组a>,index1,<数组b>,index2,index3) //其中index1表示从数组a的index1位置开始复制,复制到数组b从index2开始的位置,复制index3个

2.2.4 java.util.Arrays 工具类的使用

2.2.4.1 java.util.Arrays常用工具介绍

  • toString方法:打印数组元素,只可返回数组中的元素(是返回,不是输出),不能操作。此方法非Object中的toString方法
Arrays.toString(数组)
  • equals(…)方法:比较两个数组是否相同
Arrays.equals(数组a,数组b) //比较两个数组以相同的顺序包含相同的元素
  • copyOf(…)方法: 复制指定的数组
//举例:将数组a的元素赋值给b,内存中奖开辟新的空间,将地址指向b
int[] a = {1,2,3,4,5};
		int[] b=new int[6];
		b=Arrays.copyOf(a, 8); //这里的8表示新数组的长度为8,a数组填不满的用0来填
  • fill()填充:将某个数组的元素填充为某个值
int [] arrA =new int [4];
Arrays.fill(arrA,9) // 表示arrA中的每个元素都填成9
  • sort()数组升序排序(排序的对象需要具有比较大小的能力)
Arrays.sort(arrA)将数组arrA进行升序排序(注意是升序排序,不是排序后输出)

2.2.5 冒泡排序的基础算法

一个一维数组有N个元素,进行N-1轮比较,每轮使一个元素和下一个相邻的元素进行比较,将大的数交换到右边

public class Test {
	public static void main(String[] args) {
		int[] a = {11,52,23,4,56};
		System.out.println(Arrays.toString(a));
		for (int i = 0; i < a.length-1; i++) { //4轮比较
			for (int j = 0; j < a.length-1; j++) { //每轮比较4次
				int temp; //为了优化算法,temp最好定义在循环外
				if (a[j]>a[j+1]) {
					temp=a[j];
					a[j]=a[j+1];
					a[j+1]=temp;
				}
			}
		}
		System.out.println(Arrays.toString(a));
	}
	}

2.2.6 冒泡排序法的优化算法

通过观察,我们发现没比较一轮,就可以少比较一个数,所以有一下优化算法

public class Test {
	public static void main(String[] args) {
		int[] a = {11,52,23,4,56};
		int temp;
		System.out.println(Arrays.toString(a));
		for (int i = 0; i < a.length-1; i++) { //4轮比较
			for (int j = 0; j < a.length-1-i; j++) { //每轮少比较i次
				temp; 
				if (a[j]>a[j+1]) {
					temp=a[j];
					a[j]=a[j+1];
					a[j+1]=temp;
				}
			}
		}
		System.out.println(Arrays.toString(a));
	}
	}

2.2.7 二分法(折半查找法)

折半查找法只适用于已经正序升序的数组,通过不断取数组中间值来与需要查找的元素进行比较,成功则返回该元素的值,失败则返回负的当前最低值-1
此方法相当于arrays.binarySearch()方法

package 测试专用;

import java.util.Arrays;

import 测试专用.Face.Nose;

public class Test {
	public static void main(String[] args) {
		int[] a = {11,22,33,44,55,66};
		int high=a.length-1;
		int low =0;
		int va=66;//需要查找的值
		boolean flag =false;
		while(low<=high) {
			int mid=(low+high)/2;
			if (a[mid]>va) {
				high=mid-1;
			}else if (a[mid]

2.3 异常机制 Exception

2.3.1 异常概念_分类

2.3.1.1 异常的概念及出现的情况

异常(Exception)就是在程序运行过程中所发生的不正常的事件,它会中断正在运行中的程序。

  • 所需文件找不到
  • 网络连接不通或中断
  • 算数运算错
  • 数组下标越界
  • 装载一个不错在的类或者对null对象操作
  • 类型转换异常等等

当java程序出现以上异常时,就会在所处的方法中产生一个异常对象,这个异常对象包括异常的类型,异常出现时程序的运行状态以及对该异常的详细描述。

2.3.1.2 Error 错误

Error相当于人类当中的癌症,仅靠程序本身是无法恢复的严重错误,它与异常同属一个父类Throwable

2.3.1.3 Exception 异常的分类

异常是由java应用程序抛出和处理的非严重错误,异常相当于人类中的小病症

  • Checkedy异常:该类异常必须得到处理否则无法运行的异常

例如:SQLException、ClassNotFoundException等等

  • RuntimeException异常:运行时异常,不要求程序必须做出处理

例如:ArithmeticException、NullPointerException、NumberFormatException等等

2.3.2 捕获异常

通过捕获异常的方式,来处理相对应的异常,让程序能够继续执行下去。

2.3.2.1 try-catch 组合

try {
			需要捕获的代码体

		} catch (Exception e) { //括号内为捕获的异常类型,Exception为所有异常类的父类,这里是父类引用指向子类对象,也可以写具体的异常在里面如 InputMismatchException
			System.err.println("成功捕获后相对应的执行代码区"); //在异常中使用err.println输出,文字为红色
		}

2.3.2.2 try-finally 组合

finally下的代码块内的代码无论如何都会被执行,即便没有报异常

try{
    需要捕获的代码块
}finally{
    无论如何都会执行的代码块,即使没有报异常
}

2.3.2.3 try-catch-final 组合

包含了catch和finally的功能,此种组合catch与finally位置不能互换

try{
    需要捕获异常的代码块
}catch{
    catch代码块
}finally{
    finally代码块
}

注:如需要try下的代码块中有return,先执行finally下的代码块再回来执行return

2.3.3 声明异常

2.3.3.1 声明异常的关键字throws

该关键字用在方法名的后面,用于声明该方法可能会产生一个异常,如果方法声明的是Exception类型的异常或者是Checked Exception异常,要求方法的调用出必须做处理,RuntimeException类型的异常无预处理。处理方式有以下两种:

  • 继续使用throws向上(方法的调用处)声明,例如:
public static void method() throws <异常类名>{ //异常类名笼统的可以写Exception,细致的可以写具体哪个异常类,但只有Exception类型与Checked类型的异常需要做处理
    
}
public static void main(String[] args) <相同的异常类名>{
    method();  //当这里调用method方法时main方法名后就要添上相同的异常类名,否则使用下一种方法解决
}
  • 使用try-catch-finallly进行处理
    如果不使用thows向上声明,则需要添加try-catch方法
public static void method(){
    
}
public static void main(String[] args){
    try{
        method();  //需要捕获异常的方法
    }catch(<异常类名>  e){
        
    }
}

2.3.3.2 继承关系中的异常声明

  • 父类方法声明了Exception类型的异常,子类在重写方法时,可以声明也可以不声明。但是如果子类重写后的方法使用super关键字调用父类的方法,那么要求必须对异常进行处理。
public class Father {
 public void method() throws Exception { //父类的method方法声明了一个异常类型Exception

}
}
class Son extends Father{
	public void method() <此处可声明可不声明异常> {
	super.method(); //但这里使用super调用了父类的方法,则一定要进行处理
	}
	public static void main(String[] args) {
		
	}
}

  • 如果父类的方法没有声明异常,而子类重写的方法一定会有Exception或Checked异常,那么要求子类必须使用try-catch来处理,或者将父类和子类方法都加上异常声明
  • 如果子类重写父类方法时会产生一个RuntimeException类型的异常,则可处理也可不处理

2.3.3.3 throw 抛出异常对象

在捕获一个异常之前,必须有一段代码先生成异常对象并把它抛出,这个过程我们以手工做,也可以由JRE来实现,但是他们调用的都是throw字句。手动调用如下:

 public static void method() {
	try {
		throw new Exception(); //这行代码不写时由JRE来自动实现,new Exception为Exception的一个对象空间
	} catch (Exception e) {  声明的这个e指向上面new Exception开辟的空间
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	
}

2.3.4 常见的简单异常的解决方法

2.3.5 常见的异常类型

2.3.5.1 RuntimeException类下的常见异常
  • 算数异常 ArithmeticException
    比如除数为0,为了避免这样的异常,可使用如下方法:
if(<除数>!=0){ //如果除数不为0,则继续向下执行
    
}
  • 空指针异常 NullpotinterException
    没有创建对象(new)而调用了对象的属性或方法,为避免这种异常可以使用如下方法:
if(<对象>!=null){ //如果对象不为空,则往下执行
    
}
  • 类型转换异常 ClassCastException
    向下类型转换,需要转换成真实的子类对象,否则报异常。比如,Cat类与Dog类都是Animal类的子类,如果强制转换一个Dog类的对象d为Cat类,则报错,为了避免此类异常发生,我们可以使用instanceof来解决这个问题:
if(<对象a> instanceof <类型B>){  //如果对象a是类型B或类型B的子类的一个实例,则程序向下继续执行
    
}
  • 数组下标越界 ArrayIndexOutOfBoundException
    当访问数组元素时给的下标大于实际数组的最大下标时,就汇报这个异常。为了避面这个异常,在访问前先判断下标是否过大就行了。
  • 期望的数据类型与实际输入类型不匹配 InPutMismatchException
    当输入的数据类型与期望的数据类型不想匹配时,就会报此异常,为了避免它,我们是使用hasNextXXX()方法进行处理:
public class Father {
	public static void main(String[] args) {
		int a = 0 ;
		Scanner scan =new Scanner(System.in);
		if (scan.hasNextInt()) {  //这里使用hasNextInt()来判断输入的值是否为int型,是则返回true
			a=scan.nextLine();  //除hasNextInt()之外还有hasNextByte()、hasNextLine()方法等,便于灵活运用
		}
		System.out.println(a);
	}
}
2.3.5.2 Checked类下的常见异常

Checked类异常有很多种,处理的方法为向上声明和实现try-cathc异常捕获,实现规则请笔记向上翻阅

2.3.6 自定义异常

在程序中,可能会遇到任何标准异常类都没有充分的描述清楚问题(比如某次输入你只允许输入1到100之间的数),这种情况下可以创建自己的异常类。

2.3.6.1 自定义异常类的步骤

自定义异常类必须手写throw抛出异常对象

  • 1.继承Exception 或RuntimeException
    命名规范为:xxxException
  • 2.定义构造方法
    使用来自父类的构造方法,一般使用一个无参的和一个String类型的构造方法
  • 3.使用异常
    举个栗子:
//自定义异常类
public class SexException extends Exception{

	public SexException() {  //构造一个无参构造器
		super();
		// TODO Auto-generated constructor stub
	}

	public SexException(String message) {  //构造一个String类型的构造器,用来返回自定义字符串
		super(message);
		// TODO Auto-generated constructor stub
	}
}

public class TestSexException {
	public static void main(String[] args) {
		 System.out.println("请输入性别:");
		 Scanner scan = new Scanner(System.in);
		 String gender =scan.nextLine();
		 if ("男".equals(gender)||"女".equals(gender)) {
			System.out.println("你的性别是:"+gender);
		}else {
			try {
				throw new SexException("性别输入错误");  //由于是自定义异常类,这里Jre无法实现抛出对象,必须手写throw抛出异常对象
			} catch (SexException e) {
				e.printStackTrace();
			}
			
		}
	}

}

2.4 常用类的基本用法

2.4.1 包装类的基本用法

2.4.1.1 为什么需要包装类(Wrapper Class)

java语言是一个面向对象的语言,但是java中的基本数据类型却不是面向对象的。而我们在实际使用中经常需要将基本数据转化为对象,便于操作。比如:集合的操作中,我们就需要将基本类型数据转化为对象。

2.4.1.2 包装类和基本数据类型的对应关系

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

2.4.1.3 包装类的继承关系

graph BT
Character-->Object
Number-->Object
Boolean-->Object
Byte-->Number
Short-->Number
Integer-->Number
Float-->Number
Double-->Number

2.4.1.4 包装类的基本操作

这里我们使用Integer类举例

  • 包装类对象转基本数据类型
Integer i1 =new Integer(23); //定义一个Integer类的对象,值为23
int i = i1.intValue();
  • 基本数据类型转对象
Integer i4 = Integer.valueOf(123);//将基本数据类型123转为对象那个i4
  • String类型转int型
int i2 =Integer.parseInt("234");//将一个纯数字的字符串转为一个int型数据
  • int型转String型
int i3 =123;
String s =String.valueOf(i3); //方法1
String s = i3+""; //方法2
  • Integer型转String型
Integer i5 = new Integer(34);
String ss = i5.toString();

2.4.2 自动装箱和拆箱

自动装箱和自动拆箱提供了基本数据类型与它相同类型的包装对象之间的转换,目的是减少工作量

2.4.2.1 自动装箱auto-boxing

基本数据类型就自动的封装到和它相同类型的包装类当中去,这个过程是编译器帮我们做的

//栗子
Integer a =100; //此代码编译不会报错,这就是自动装箱
这行代码它实际上是调用了valueOf(int i)方法
关于valueof()方法,它会调用一个IntegerCache类,这是一个Integer当中的内部类,它做一个缓存功能
当你获取的值在-128到127之间时,地址都指向这个缓存池,所以
Integer b=100;
(a==b)将返回true;

2.4.2.2 自动拆箱 unboxing

包装类对象自动转换为基本数据类型

//同样使用Integer与int型举例子
Integer a = new Integer(100);
int b = a; //对象直接赋值给了基本数据类型b
这是因为编译器替我们做了转型的工作,他实际上调用了
intValue()方法

2.4.3 Stringg 类底层分析_JDK源码分析

2.4.3.1 String 的底层数据结构

本节的内容详见2.1.5

String的底层数据结构是Char类型的数组

String的相应方法的实现实际上就是对数组的一个操作

2.4.4 StringBuilder_StringBuffer用法_JDk底层源码分析

2.4.4.1 可变的字符串

StringBuilder:效率高,安全性低
StringBuffer:效率低,安全性高

2.4.4.2 StringBuilder与StringBuffer的常用方法的使用

StringBuilder 是一个可变的字符序列。它继承于AbstractStringBuilder,实现了CharSequence接口。StringBuilder和StringBuffer是通用的,它们区别大多在安全与效率上。

  • StringBuilder 的方法之追加: append(),使用此方法可以给StringBuilder的对象追加各种基本类型甚至引用类型
StringBuilder a= new StringBuilder();
	a.append(false);
	a.append("hello");
	a.append(193);
	a.append('你');
	System.out.println(a); //将输出:falsehello193你
  • StringBuilder的方法之去区间删除: delete(int x, int y),此方法将删除[x,y)之间的字符
  • StringBuilder的方法之字符删除:deleteCharAt(itn x),此方法用来删除指定下标x处的字符
  • StringBuilder的方法之插入:insert(int x,XXX y),此方法用来在指定的下标处x处插入一个基本数据类型或是引用数据类型,如:
StringBuilder a= new StringBuilder();
Integer b=300;
a.insert(0,b) //此时字符串a为300
  • StringBuilder的方法之查找:indexOf(String x),此方法用来查找字符串x所在的最近的位置并返回

2.4.5 不可变字符序列和可变字符序列使用陷阱

一段代码引发的知识点

String st1 ="abc";  --1
String st2 =st1+"def";   --2
System.out.println(st2);  --3 //则输出abcdef
我们讲讲第2行代码是怎么实现的:
前面说过在有new或变量运算时会在堆中开空间,用来作计算,实际上,开的这个空间其实是StringBuilder的一个对象对应的空间,第2行代码可看作:
StringBUilder sb = new StringBUilder();
sb.append(st1);
sb.append("def");
st2=sb.toString();

2.4.6 java.util.Date类

本节内容需要提到的知识点,箭头代表继承

graph LR
java.sql.Date-->java.util.Date
java.sql.TimeStamp-->java.util.Date
java.sql.Time-->java.util.Date


2.4.7 DateFormat与SimpleDateFormat

这两个类完成字符串和时间对象的转换,其中java.util.DateFormat类为抽象类

graph LR
java.text.SimpleDateFormat-->java.text.DateFormat

在本节中我们只学习将String类型的数据转化为日期时间与将时间日期转化为String型数据

  • 将Date类型对象转为String类型字符串——format()方法的使用
public static void main(String[] args) {
	Date d =new Date(1264561654654L);
	DateFormat d2 =new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");//有关括号内参数的表示请务必参考JDK8的API
	String str=d2.format(d);
	System.out.println(str); //输出结果为:2010-01-27 11:07:34.654
}
  • 将字符串转为Date类型对象——parse()方法的使用
DateFormat d2 =new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
String str="yyyy-MM-dd hh:mm:ss.SSS";//括号内为格式,需要填写具体数值
Date d=d2.parse(str);
System.out.println(d);//此时执行的是Date型对象d的toString方法

2.4.8 Calendar 日历类

graph LR
java.util.GregorianCalendar-->java.util.Calendar

2.4.8.1 Calendar的类

  • 人们对于时间的认识是:某年某月某日,这样的时间概念,计算机是long类型的数字。通过Calendar在二者之间搭起桥梁。
  • GregorianCalendar 是Calendar的一个具体子类,提供了世界上大多数国家地区使用的标准时间日历。
  • 注意:月份一月是0,以此类推;周日是1,以此类推

2.4.8.2 GregorianCalendar类的常用方法的使用

  • set(int,int,int) 此方法用来设置年月日
  • set(int field,int x) 举例:
set(Calendar.DAY_OF_MONTH,1)初始化当前的“日”为当月的第一天
  • get(field) 此方法返回传递的领域对应的值,如传递Calendar.YEAR则返回当前年份
  • setTime(Date) //传入一个Date型对象,设置为当前时间

2.4.9 Math类的常用方法_静态导入

Math类是用来提供数学的一些基本运算方法与常量

2.4.9.1 Math类的常用方法

  • Manth.PI 即π,调用时直接写Maht.PI
  • abs(int)方法:求绝对值
  • ceil(double)方法:向上取整在转double
  • floor(double)方法:向下取整再转double
  • min(a,b)方法:传入两个数,返回较小的数
  • max(a,b)方法:传入两个数,返回较大的一个数
  • pow(double a,double b) 幂方法:传入double型a与b,返回a的b次方的值
  • random() 返回一个[0,1)之间的double型小数
  • round(double)传入一个小数,进行小数点后一位的四舍五入
  • sqrt(double)方法:开平方

2.4.9.2 静态导入

import xxxx 和 import static xxxx的区别是前者一般导入的是类文件如import java.util.Scanner;后者一般是导入静态的方法,import static java.lang.System.out;//这里导入的是out方法

2.4.10 FIle类

FIle类是文件和目录路径名的抽象表示形式。一个File对象可以代表一个文件或目录。可以实现获取文件和目录属性等功能。可以是实现对文件和目录的创建、删除等功能。File不能访问文件内容。

2.4.10.1 File类操作文件

  • 使用File构造方法创建对象
//FIle类没有无参构造方法,在new 的时候必须传入参数,如:
File f = new File("a.txt"); //在当前项目目录下预创建一个a.txt的文件
File f2 =new File("D://a.txt"); 地址分隔符可以为\或/
File f3 =new File("D://B");没有跟后缀名的文件
File f4 =new File(f3,"a.txt"); //在f3的前提(如果f3是一个存在的文件夹)下创建一个文件a.txt
File f5 =new File("D:"+File.separator+"a.txt"); //由于unix系统的文件路径分割符与windows不同,File.separator会自动匹配不同系统的分隔符
  • 使用createNewFile()方法创建文件/目录
此方法返回的是一个boolean类型的值,当且仅当具有对象文件名的文件不存在时,创建一个新的空白文件
f1.createNewFile()
  • 使用delete()方法删除文件(不会经过回收站)
f1.delete() 

注:无论创建还是删除都是对是预创建的文件进行操作

  • 使用exists()方法判断文件或文件夹是否存在
  • 使用getAbsolutePath()方法获取绝对路径
  • 使用getPath()方法获取相对路径
  • 使用getName()方法获取文件名
  • 直接输出对象将获得文件名
  • 使用isFile()方法返回对象是否是一个文件
  • 使用length()方法返回该文件内容的字节数(一个汉字两个字节,一个英文及字符占一个字节)

2.4.10.2 File类操作文件夹

  • 使用构造方法声明文件夹
//方法同声明文件,这里只举一个例子
File f = new File("D:\\hello") --1  //预创建一个文件或文件夹
File f2 = new File("D:\\aa\\bb\\cc")  --2 //这种实际上是预创建一个名为cc的文件夹或文件 
  • 使用mkdir()方法创建单一文件夹,适用上式1,使用此方法只能创建单层文件夹,当且仅当该该名称不存在时创建
  • 使用mkdirs()方法创建多层文件夹,当遇到上式2时,如没有D:\aa\bb文件夹的存在,则无法使用mkdir()方法创建cc文件夹,这时候就需要使用mkdirs()一次性创建出aa,bb,cc文件夹
  • 使用getParentFile()方法来获得当前文件或目录的父目录,通常需要声明一个File对象来绑定
  • 使用delete()方法来删除自身,但是如果自身是某个文件夹或文件的父目录,则无法删除,也就是说,此方法只能用来删除空目录和文件
  • 使用list()方法来返回与该目录下所有文件名生成字符串,只能做String类型的操作,通常需要用String数组来接收
//输出E盘下的所有文件及文件夹
File f1 =new File("E:\\");
		String[] str =f1.list();  //使用String数组接收
		for(String s : str) { //遍历
			System.out.println(s);
		}
  • 使用listFiles()方法来列举出该目录下的所有文件与文件夹并返回为File类型的对象,需要用FIle数组来接收
File f1 =new File("E:\\");
		File[] fileList =f1.listFiles();  //使用File数组接收
		for(File s : fileList) { //遍历
			System.out.println(s);
		}

2.4.10.3 特别注意

File f =new File("D:\\abc");
f.createNewFile();
f.mkdir();
在此段代码执行后,将只能生成一个名为abc的文件,而不能生成文件夹,如果2行与3行代码互换,则只能生成文件夹而不能生成文件。

若该地址原本就有名为abc的文件或是文件夹,则无法再生成文件或是文件夹

2.4.11 使用递归算法遍历目录结果_树结构

public class TestRecursionFile {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File file =new File("D:\\Test");
		printFile(file,0);
	}

	public static void printFile(File file, int level) { //递归方法体
		File f = file;
		int l = level; //level用来表示层次,输出符号“-”
		for (int i = 0; i < level; i++) {
			System.out.print("- ");
			
		}
		System.out.println(f.getName()); //输出当前文件名
		if (f.isDirectory()) { //如果当前对象是文件夹,则:
			File[] fileList = f.listFiles(); //列举子文件对象
			for (File temp : fileList) { 
				printFile(temp, level + 1);  //子文件调用printFile方法
			}
		}
	}
}

2.4.12 枚举

枚举与类同级,但它实质上也是类,枚举用来让用户做特定值的选择,如输入性别时,你只能选择“男”或者“女”,而不能选择其它的。

2.4.12.1 枚举的构建

枚举和类以及接口处于同一级别,使用关键字enum来声明而不是class新建枚举的方法为:new-Enum

public enum Gender{
    //如此
}

2.4.12.2 枚举的成员

枚举的成员即我们前面说的用户所选的“特定值”,他有如下特征:

  • 默认为public static final修饰,不属与任何类(int、string等等),如:
public enum Gender{
    男,女;
}
  • 如上所示,枚举的多个属性使用英文逗号分隔
  • 不需要使用号“;”结尾,但加了也不会报错
  • 枚举中可以存在成员变量及方法,当存在成员变量时,枚举的成员必须加分号
public enum Gender{
    男,女;
    int a;
    String name="李白";
    public void printHello(){
        System.out.println("Hello");
    }
}

2.4.12.3 枚举的调用

枚举的属性可以在其它类中调用

//数据来源上面代码
Gender g =Gender.男;
Gender f =Gender.女;

2.4.12.4 枚举的方法和其它成员变量的调用

枚举中可以写方法,当需要调用时,使用枚举成员的实例来调用

//每个被枚举的成员实质上就是一个对象
如://接着使用上面的Gender枚举
public class Test{
    public static void main(String[] args){
        Gender g =Gender.男;
        g.printHello(); //此时g就相当于一个对象
        String name =g.name;
    }
}

2.4.12.5 枚举成员与String类型的互转

//枚举成员转String
String gender=g.toString(); //g从上面来的
///String 转枚举成员
//String类型转枚举
Enum g = Enum.valuOf(Enum.class,"人妖");

2.4.12.6 枚举类型在switch方法的使用

  • 举个例子:1.先写一个需要使用枚举类型的类,这里定义一个Person类
package cn.sxt.test;

public class Person {
private String name;
private int age;
private Gender gender; //声明一个枚举对象
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}
public int getAge() {
	return age;
}
public void setAge(int age) {
	this.age = age;
}
public Gender getGender() {
	return gender;
}
public void setGender(Gender gender) {
	this.gender = gender;
}
public Person(String name, int age, Gender gender) {
	super();
	this.name = name;
	this.age = age;
	this.gender = gender;
}
public Person() {
	super();
}
}

  • 举个例子:2.使用switch方法
package cn.sxt.test;

public class Test { //这里使用测试类Test来测试
	public static void main(String[] args) {
		Person p1 = new Person("tom",32,Gender.女); 
		switch(p1.getGender()) {
		case 女:
			System.out.println("我是女人");
			break;
		case 男:
			System.out.println("我是男人");
			break;
		}
	}
}

2.5 容器(集合)

2.5.1 基本概念_Collection_Set_List接口介绍

2.5.1.1 容器的概念

  • 容器既是集合,用来装对象的,无论什么类型的对象都能装
  • 容器有很多种,如ArrayList,LinkedList等,他们的区别就是存储方式不同,这种存储方法称为:数据结构
  • 集合中存储的是对象的内存地址

2.5.1.2 容器的分类

容器分很多种,List接口下的ArrayList,LinkedList,Vector等,Set接口下有HashSet,TreeSet等。

graph TD
List接口-->Collection接口
Set接口-->Collection接口
ArrayList类-->List接口
LinkedList类-->List接口
Vector类-->List接口
HashSet类-->Set接口
TreeSet类-->Set接口

2.5.1.3 容器框架(Collection)及其提供的主要方法

java集合框架提供了一套性能优良、使用方便的借口和类,他们位于java.util包中,存放在集合中的数据,被称为元素(element)。他是所有容器的顶层。方法具体详情请查看jdk 1.8 API:

  • add(E e)方法:容器使用用来装对象的,当add方法中写了一个基本数据类型时,也不会报错,他会将基本数据类型自动装箱
list.add(new Scanner(System.in));
list.add(100);//100自动装箱为Integer类的一个对象
  • size()方法:返回容器中元素的个数,注意是元素的个数而不是字符数

  • isEmpty()方法:返回一个布尔值,如果集合是空的则为true

  • addAll(集合)方法:将指定集合中的元素全部添加到当前集合中

  • remove(Object)方法:根据对象来删除

list.remove(new Integer(100)); //将上面添加的值为100的Integer的对象删除
  • removeAll(集合A)方法:将当前集合中的集合A所包含的所有元素的元素删去

  • contains(元素)方法:判断元素在当前集合中是否存在

  • 直接输出List对象将输出集合中的元素

  • clear()方法:删除当前集合中的所有元素

2.5.1.4 集合的遍历

  • 使用加强for循环for循环遍历集合中的元素 //只是打印名字
for(Object obj : list){
    System.out.println(obj)
}
  • 使用普通for循环遍历 //只是打印名字
for(int i=0;i
  • 使用迭代器 (遍历返回的是可操作的元素)
Iterator ite = list.iterator;
while(ite.has.hasNext()){
    Object obj =ite.next();
    System.out.println(obj);
}

2.5.1.5 各接口的特点

  • Collection接口存储一组不唯一,无序的对象
  • List接口存储一组不唯一,有序(索引顺序)的对象
  • Set接口存储一组唯一,无序(无索引)的对象

2.5.2 迭代器及其使用

2.5.2.1 迭代器Iterator接口的概念及其方法

迭代器是用来去除集合中元素的工具。每种容器的数据结构不同,所以他们的取出元素的方法也不同,但他们都有判断和取出的过程,于是我们将取出器(用来调用取出方法)抽象出来,得到一个抽象接口Iterator,每种容器中都有一个内部类,用来实现Iterator,这个类的对象,就是容器的迭代器。

  • iterator()方法的使用,此方法不是Interator接口下的方法,是容器ArrayList的方法
ArrayList a;
Iterator temp = a.iterator();

  • hasNext()方法的使用
//hasNext()方法是用来判断集合是否有下一个元素的方法,返回值类型为布尔型
System.out.println(a.hasNext());//输出当前集合是否有下一个元素
  • next()方法
//此方法用来返回当前下标的元素,默认下标为0
a.next();
  • remove()方法:使用迭代器时,不能调用容器方法,否则会导致并发异常,这时候想要对容器内对象操作,只能使用remove()方法
for(Iterator it = a.iterator();it.hasNext(); ){
    if(it.next().equals("xxx"){ //如果这是容器中的xxx元素
        it.remove; //那么删除它
    }
}

2.5.2.2 ListIterator迭代器:List接口下的容器所使用的的迭代器接口

ListIterator继承自Iterator,它比起Iterator来,提供了更多的操作方法,便于List容器的使用

  • list容器下新建一个List迭代器
//这里还用ArrayList举例子
ArrayList al = new ArrayList();
ListIterator it = al.listIterator(); //注意这里是ListIterator

ListIterator下特有的方法:

  • hasPrevious()
    返回 true如果遍历反向列表,列表迭代器有多个元素。
  • nextIndex()
    返回随后调用 next()返回的元素的索引。
  • previous()
    返回列表中的上一个元素,并向后移动光标位置。
  • previousIndex()
    返回由后续调用 previous()返回的元素的索引。
  • set(E e)
    用 指定的元素替换由 next()或 previous()返回的最后一个元素(可选操作)。

2.5.3 List集合_ArryaList

2.5.3.1 ArrayList容器

ArrayList的底层数据结构是数组。也就是说数组的优缺点它都具备。优点是查询快,缺点是增删慢,空间是连续的,然后线程不同步。

2.5.3.2 ArrayList的特有的方法详解

  • ArrayList拥有来自接口List的所有方法
  • add(int dindex,object)方法:在指定位置添加对象
  • addAll(int index, Collection c)
    将指定集合中的所有元素插入到此列表中,从指定的位置开始。
  • get(int index)
    返回此列表中指定位置的元素。
  • set(int index, E element)
    用指定的元素替换此列表中指定位置的元素。
  • remove(Index)方法:根据索引删除集合中的对象
  • subList(int fromIndex, int toIndex)
    返回此列表中指定的 fromIndex (包括)和 toIndex之间的独占视图。
  • retainAll(集合A)方法:将当前集合中的集合A中所包含的元素的元素保留,删除其他元素

2.5.3.3 ArrayList_JDK源码分析

构造方法与add方法扩容的分析

  • 无参构造方法
//Object类型的空数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//无参构造方法
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
//调用无参构造方法相当于在空间中开辟了一个空间,指向集合数组
  • 代参构造方法
public ArrayList(int){
    //代码省略,int型参数构造方法,开辟一个长度为int值的Object数组
}
  • add()添加方法及扩容原理
当第一次使用add方法时,完成Object类型数组的初始化容量10
当添加第11个元素时:
//在ArrayList源码中
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity); //最小容量增大方法
    }

2.5.4 List集合_LinkedList容器

LinkedList的底层数据结构是链表。特点是增删快,查询慢,空间不是连续的。

2.5.4.1 链表的概念

  • 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
  • 单项链表
    单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点;
  • 双向链表
    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

2.5.4.2 LinkedList的特有方法

  • addFirst(E e)
    在该列表开头插入指定的元素。

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

  • getFirst()
    返回此列表中的第一个元素。如果链表为空,则报异常。

  • getLast()
    返回此列表中的最后一个元素。如果链表为空,则报异常。

  • peekFirst() 返回链表第一个元素,如果链表为空,则返回null

  • peelLast() 返回列表最后一个元素,如果链表为空,则返回null

  • removeFirst()
    返回链表第一个元素,并删除之。如果列表为空,则报异常

  • removeLast()
    返回链表最后一个元素,并删除之,如果链表为空,则报异常

  • pollFirst()返回链表第一个元素,并在链表内删除之,如链表为空,饭返回null

  • pollLast()返回链表最后一个元素,并删除之,如链表为空,则返回null

2.5.4.3 源码分析

 private static class Node { //Node即节点,这是一个节点类,内部类(在LinkedList类中)
        E item; //数据本身
        Node next; //后继节点
        Node prev; //前驱节点

        Node(Node prev, E element, Node next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2.5.6 List集合_Vector容器

Vector在Collection框架之前出现,是元老级容器。它的底层数据结构也是数组,它与ArrayList相比的特点是,它线程同步,增删查改都慢,被后来出现的ArrayList替代。

2.5.6.1 相对ArrayList,Vctor中较特殊的方法:elements() 与 迭代器的比较

elements返回的是一个枚举。它的功能和迭代器几乎相同,在可选的情况下建议选择迭代器。

  • (01) 函数接口不同
    Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
    Iterator只有3个函数接口。Iterator除了能读取集合的数据之外,也能数据进行删除操作。

  • (02)Iterator支持fail-fast机制,而Enumeration不支持。
    Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。
    而Iterator 是JDK1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

2.5.6.2 为什么还要继续使用Enumeration接口

  • 1、Enumeration和Iterator接口功能相似,而且Iterator的功能还比Enumeration多,那么为什么还要使用Enumeration?这是因为Java的发展经历了很长时间,一些比较古老的系统或者类库中的方法还在使用Enumeration接口,因此为了兼容,还是需要使用Enumeration。

  • 2、Enumeration 比 Iterator 的遍历速度更快。为什么呢?
    这是因为,Hashtable中Iterator是通过Enumeration去实现的,而且Iterator添加了对fail-fast机制的支持;所以,执行的操作自然要多一些。

2.5.7 Set接口_HashSet

2.5.7.1 为啥要用HahSet?

假如我们现在想要在一大堆数据中查找X数据。LinkedList的数据结构就不说了,查找效率低的可怕。ArrayList哪,如果我们不知道X的位置序号,还是一样要全部遍历一次直到查到结果,效率一样可怕。HashSet天生就是为了提高查找效率的。

2.5.7.2 HashSet容器的底层数据结构

HashSet的底层数据结构是Hash表,也称散列表,散列结构,哈希结构。而Hash表是按照哈希表计算方法来存的。

2.5.7.3 HashSet容器的操作原理(引用地址:资料引用地址)

在HashSet容器的一个对象想要进行增删查改时,需要依赖两个方法,一个是hashCode(),用于判断元素的地址,另一个是equals方法,用于判断元素的值是否相同。判断顺序是先判断Hash值,如果哈希值相同则再调用equals方法判断内容。这点与List容器不同,List 容器只依赖hashCode()方法。

  • 1.哈希表存储计算方法:

    假如我们有一个数据(散列码76268),而此时的HashSet有128个散列单元,那么这个数据将有可能插入到数组的第108个链表中(76268%128=108)。但这只是有可能,如果在第108号链表中发现有一个老数据与新数据equals()=true的话,这个新数据将被视为已经加入,而不再重复丢入链表。

  • 2.HashSet的散列单元大小如何指定:

    Java默认的散列单元大小全部都是2的幂,初始值为16(2的4次幂)。假如16条链表中的75%链接有数据的时候,则认为加载因子达到默认的0.75。HahSet开始重新散列,也就是将原来的散列结构全部抛弃,重新开辟一个散列单元大小为32(2的5次幂)的散列结果,并重新计算各个数据的存储位置。以此类推下去…

  • 3.为什么HashSet查找效率提高了:

    知道了HashSet的add机制后,查找的道理一样。直接根据数据的散列码和散列表的数组大小计算除余后,就得到了所在数组的位置,然后再查找链表中是否有这个数据即可。

  • 4.hashCode方法必须与equals方法必须兼容:

    如果我们自己定义了一个类,想对这个类的大量对象组织成散列表结构便于查找。有一点一定要注意:就是hashCode方法必须与equals方法向兼容。

2.5.8 HashMap的底层原理

2.5.9 二叉树和红黑树

2.5.10 TreeSet的使用_JDK源码分析

2.5.11 泛型

2.5.11.1 什么是泛型

泛型是用来规定集合中存储什么类型的对象的一种方法

2.5.11.2 泛型的分类

  • 泛型类

声明一个类是泛型类,使它的对象可以使用泛型

public class Hello{ //T只是一个字母,它表示的对象是一个具体的泛型对象
  
}
class Test{
    public static void maini(String args[]){
        Hello h1= new Hello;
        Hello h2 = new Hello;
    }
}
  • 泛型接口
    原理同泛型类,只是把泛型定义在一个接口上
//定义一个泛型接口
public interface Hello{
    
}

---
//类型一
public class Nihao implements Hello{
    
}
//此种类型在创建对象时,无需指定泛型的具体类型,此处默认为String型
Nihao n = new Nihao();
---

//类型二
public class Hea implements Hello{
    
}
Hea n1 = new Hea();
  • 泛型方法
public class MyMethod{ //定义一个泛型类
    public void method1(T t){ //泛型在当前类实例化是定义
        
    }
    public void method2(Q q){//此种方法的泛型在调用此方法时明确
        
    }
    public void method3(K...k){ //可变长泛型
        
    }
}
---
//实例化试一下
public class Test{
    public static void main(String [] args){
        MyMethod mm = new MyMethod();
        mm.method1("dabendan");//只能传入String型
        mm.method2("dabendandan");//能传入各种类型
        mm.method2(123);//能传入各种类型
        mm.method3("213","ewfdf");//可变长传入各种类型
    }
}

2.5.12 泛型的高级使用_容器中使用泛型

2.5.12.1 泛型的高级使用

  • 泛型的上限:

使用关键字extends,表示参数化的类型可能是所指定的类型或者是此类型的子类

  • 泛型的下限
    使用关键字super进行声明,表示参数化的类型可能是指定的类型,或者是此类型的父类型,直至Object类

2.5.15 容器体系框架总结

2.6 IO流技术

2.6.1 IO流的基本概念

  • IO流相当于一个管道,用来传输数据
  • 在java程序中,对于数据的输入/输出操作以“流”(stream)的方式进行
  • J2SDK提供了各种各样的“流”类,用以获取不同种类的数据;程序中通过标准的方法输入或输出输出数据
  • java的流类一般位于java.io包中

2.6.2 流概念细分和体系_四大抽象类

2.6.2.1 流的分类

  • 按流的方向:

①输入流:数据源到程序(InputStream、Reader读进来)

②输出流:程序到目的地(OutputStream、Writer写进去)

  • 按处理数据单元

①字节流:按照自己饿读取数据(InputStream、OutputStream)

②字符流:按照字符读取数据(Reader、Writer)

**注:在java中,出和入都是按程序来的,数据流向程序叫入,数据流出程序叫出

  • 按功能不同来分

①节点流:可以直接从数据源或目的地读写数据

②处理流(包装流):不直接连接到数据源或目的地,是其他流进行封装。目的主要是简化操作和提高性能

  • 节点流和处理流之间的关系

①节点流处于io操作的第一线,所有的操作必须通过他们进行

②处理流可以对其他流进行处理(提高效率或操作

2.6.2.2 IO流的四大抽象类

  • InputStream
  • OutputStream
  • Reader
  • Writer

2.6.3 文件字节流FileInputStream与FileOutputStream

第一:搭建管道;第二:进行操作

2.6.3.1 FileInputStream类的的使用

1.FileInputStream的构造方法

  • new FileInputStream(String name) :从指定路径获得数据源,若文件不存在则抛异常
FileInputStream f = new FileInputStream("D:\\a.txt");
  • new FileInputStream(File file) 搭建一个File类的对象file对应的文件的数据流通道,文件不存在则抛异常
        File fileA = new File("D:\\b.txt");
		fileA.createNewFile();
		FileInputStream f = new FileInputStream(name); 
  • new FileInputStream(FileDescriptor fdObj) 搭建一个与FileDescriptor类对象描述的文件连接的数据流管道
        FileDescriptor a =new FileDescriptor();
		FileInputStream f = new FileInputStream(a);

2.FileInputStream的普通方法

  • read() :从这个输入流一次读取一个字节的数据

  • read(byte b[]):读取数据源的全部数据放到b byte数组中。我们把数组b叫做缓冲池,处理站等。缓冲池的大小由数组长度决定,比如输出长度为10表示缓冲池大小为10,一次读取10个字节的数据。

  • read(byte b[], int off, int len):从该输入流读取到字节数据。放入一个字节数组。off:数据中的起始偏移量(下标从0开始),len:写入的字数。

  • skip(long n):从当前位置跳过n个字节数。如一个文件中有ILOVEYOU内容

byte b[] =new byte[10]; 
long aa =4L;
f.skip(aa);
f.read(b)
//则b中只存有EYOU内容
  • available():返回可以读取的剩余字节数的估计值。

  • close():关闭流

  • getFD(): 获得FileDescriptor文件描述符

  • getChannel():返回文件通道

2.6.3.2 FileOutputStream类的使用方法

1.FileOutputStream的构造方法

构造方法用法与FileInputStream构造方法基本一致,详参FileInputStream构造方法

  • FileOutputStream(File file)
    创建文件输出流以写入由指定的 File对象表示的文件。如原文件中有数据,默认覆盖数据。
        File fileA = new File("D:\\b.txt");
		fileA.createNewFile();
		FileOutputStream f = new FileOutputStream(fileA); 
  • FileOutputStream(File file, boolean append)
    创建文件输出流以写入由指定的 File对象表示的文件。跟上一个布尔值,如true则表示如果原文件中有内容,则在此基础上追加写入数据而不覆盖。
FileOutputStream f = new FileOutputStream("D:\\a.txt",true)
  • FileOutputStream(FileDescriptor fdObj)
    创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。
  • FileOutputStream(String name)
    创建文件输出流以指定的名称写入文件。
  • FileOutputStream(String name, boolean append)
    创建文件输出流以指定的名称写入文件。跟上一个布尔值,如true则表示如果原文件中有内容,则在此基础上追加写入数据而不覆盖。

2.FileOutputStream的普通方法

  • write(int b) 将一个指定的整型数据写入输出流指向的文件
write(f.read()) //将输入流f当前读到的一个字节写入到输出流指向的文件中
write(99)  //将ASCLL码表中小写字母c对应的十进制整数99写入到输出流指向的文件中
  • write(byte b[]):从指定字节数组写入

  • write(byte b[], int off, int len)F:从指定字节数组写入。off:数据中的起始偏移量,len:写入的字数。

  • close():关闭流

  • getChannel():返回文件通道:

  • getFD():获得FileDescriptor文件描述符

2.6.4 使用字节流实现文件复制

public class TestFileInputStream {
	public static void main(String[] args) throws IOException {
		File name1 = new File("D:\\b.txt");
		name1.createNewFile();
		FileInputStream f = new FileInputStream(name1);
		FileOutputStream k = new FileOutputStream("D:\\新建文本.txt");
		byte[] b= new byte[(int) name1.length()];
		f.read(b);
		k.write(b);
		f.close();
		k.close();
	}
}

2.6.5 文件字符流_FileWriter类与FileReader类

2.6.5.1 FileReader类

FileReader继承自InputStreamReader
构造方法的使用

  • FileReader(File file)
    创建一个新的 FileReader ,给出 File读取。
  • FileReader(FileDescriptor fd)
    创建一个新的 FileReader ,给定 FileDescriptor读取。
  • FileReader(String fileName)
    创建一个新的 FileReader ,给定要读取的文件的名称。

普通方法的使用

  • close()
  • getEncoding()
  • int read() 读取一个字符,如读到末尾,则返回-1
  • int read(char[] cbuf,int offset,int length) cbuf为缓冲池数组,offset为要开始存储字符的偏移量,length为最大读取长达
  • boolean ready() 表示该流是否准备好被读取

2.6.5.2 FileWriter类

构造方法的使用

  • FileWriter(File file)
    给一个File对象构造一个FileWriter对象。
  • FileWriter(File file, boolean append)
    给一个File对象构造一个FileWriter对象。
  • FileWriter(FileDescriptor fd)
    构造与文件描述符关联的FileWriter对象。
  • FileWriter(String fileName)
    构造一个给定文件名的FileWriter对象。
  • FileWriter(String fileName, boolean append)
    构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。

普通方法的使用

  • close()
  • flush() 刷新流。当写入数据后使用此方法数据才能真的写进文件
  • getEncoding() 返回此流使用的字符编码的名称。
  • writer(char[] cbuf) 写入一个char数组cbuf中的内容
  • write(int c) 写入整型c对应的ASCII值 如c为99则写入小写字母c
  • write (char[] cbuf, int off, int len) - cbuf缓冲区 off -从中开始编写字符的偏移量 len - 要写入的 len数
  • write (String str, int off, int len) 写入一个字符串,off为字符串的起始偏移量,len为写入的最大长度
  • writer append(char c) 将字符c追加到writer并返回一个writer对象

2.6.6 缓冲字节流_缓冲字符流

缓冲流技术大大提高了数据读取效率,当读取的文件是文本时,推荐使用字符流,其它文件可使用字节流。

  • 运作方式

在使用BufferedInputStream时先将数据读入BufferedInput提供的输入缓冲池(默认大小为8192)中,中转站需要读取时先从输入缓冲池读取。当写入时,先写入到由BufferedOutputStream提供的输出缓冲池中(默认大小8192),最后再写入目的文件。

2.6.6.1 缓冲字节流——BufferedInputStream

构造方法

  • BufferedInputStream(InputStream in)
    其中InputStrem in 为一个输入流,举个例子:
FileInputStream fis = new FileInputStream("D:\\b.txt");
BufferedInputStream a =new  BufferedInputStream(fis);
  • BufferedInputStream(InputStream in, int size)
    创建 BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流 in ,供以后使用。

普通方法

  • int available()
    返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
  • void close()
    关闭此输入流并释放与流相关联的任何系统资源。关闭此流可顺带关闭比他低级的输入流
  • void mark(int readlimit)
    见的总承包 mark的方法 InputStream 。
  • boolean markSupported()
    测试这个输入流是否支持 mark和 reset方法。
  • int read()
    见 read法 InputStream的一般方法。
  • int read(byte[] b, int off, int len)
    从给定的偏移开始,将字节输入流中的字节读入指定的字节数组。
  • void reset()
    见 reset法 InputStream的一般方法。
  • long skip(long n)
    见 skip法 InputStream的一般方法。

2.6.6.2 缓冲字节流——BufferedOutputStream

构造方法

  • BufferedOutputStream(OutputStream out)
    创建一个新的缓冲输出流,以将数据写入指定的底层输出流。其中OutputStream为一个输出流实例
  • ufferedOutputStream(OutputStream out, int size)
    创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。

特有方法

  • flush()
    刷新缓冲输出流。
  • void write(byte[] b, int off, int len)
    从指定的字节数组写入 len个字节,从偏移 off开始到缓冲的输出流。
  • void write(int b)
    将指定的字节写入缓冲的输出流。
  • write(byte[] b)
  • close()方法,关闭此流可顺带关闭比他低级的输出流

2.6.6.3 缓冲字符流——BufferedReader

构造方法

  • BufferedReader(Reader in)
    创建使用默认大小的输入缓冲区的缓冲字符输入流。 Reader in 为Reader类型的输入流
  • BufferedReader(Reader in, int sz)
    创建使用指定大小的输入缓冲区的缓冲字符输入流。

一般方法

  • close()
    关闭流并释放与之相关联的任何系统资源。
  • Stream lines()
    返回一个 Stream ,其元素是从这个 BufferedReader读取的行。
  • void mark(int readAheadLimit)
    标记流中的当前位置。
  • boolean markSupported()
    告诉这个流是否支持mark()操作。
  • int read()
    读一个字符
  • int read(char[] cbuf, int off, int len)
    将字符读入数组的一部分。
  • String readLine()
    读一行文字。
  • boolean ready()
    告诉这个流是否准备好被读取。
  • void reset()
    将流重置为最近的标记。
  • long skip(long n)

2.6.6.4 缓冲字符流——BufferedWriter

构造方法

  • BufferedWriter(Writer out)
    创建使用默认大小的输出缓冲区的缓冲字符输出流。 Reader out 为Reader类型的输出流
  • BufferedWriter(Writer out, int sz)
    创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。

一般方法

  • void close()
    关闭流,先刷新。
  • void flush()
    刷新流。
  • void newLine()
    写一行行分隔符。
  • void write(char[] cbuf, int off, int len)
    写入字符数组的一部分。
  • void write(int c)
    写一个字符
  • void write(String s, int off, int len)

2.6.7 转换流

  • 转换流是将InputStream的数据转换成Reader的数据,将OutputStream的数据转换成Writer的数据,就是说转换流的作用是将字节流转换成字符流
  • 没有将字符流转换成字节流的类,因为没有必要
  • 转换流就是一种处理流

2.6.7.1 InputStreamReader

InputStream数据流转Reader数据流

构造方法

  • InputStreamReader(InputStream in)
    创建一个使用默认字符集的InputStreamReader。
  • InputStreamReader(InputStream in, Charset cs)
    创建一个使用给定字符集的InputStreamReader。 Charset cs 可以是“gbk","utf-8"等
  • InputStreamReader(InputStream in, CharsetDecoder dec)
    创建一个使用给定字符集解码器的InputStreamReader。
  • InputStreamReader(InputStream in, String charsetName)
    创建一个使用命名字符集的InputStreamReader。

一般方法

  • void close()
    关闭流并释放与之相关联的任何系统资源。
  • String getEncoding()
    返回此流使用的字符编码的名称。
  • int read()
    读一个字符
  • int read(char[] cbuf, int offset, int length)
    将字符读入数组的一部分。
  • boolean ready()
    告诉这个流是否准备好被读取。

2.6.7.3 OutputStreamWriter

OutputStream数据流转Writer数据流
** 构造方法**

  • OutputStreamWriter(OutputStream out)
    创建一个使用默认字符编码的OutputStreamWriter。
  • OutputStreamWriter(OutputStream out, Charset cs)
    创建一个使用给定字符集的OutputStreamWriter。Charset cs 可以是“gbk","utf-8"等
  • OutputStreamWriter(OutputStream out, CharsetEncoder enc)
    创建一个使用给定字符集编码器的OutputStreamWriter。
  • OutputStreamWriter(OutputStream out, String charsetName)
    创建一个使用命名字符集的OutputStreamWriter。

一般方法

  • void close()
    关闭流,先刷新。
  • void flush()
    刷新流。
  • String getEncoding()
    返回此流使用的字符编码的名称。
  • void write(char[] cbuf, int off, int len)
    写入字符数组的一部分。
  • void write(int c)
    写一个字符
  • void write(String str, int off, int len)
    写一个字符串的一部分。

2.6.8 打印流

2.6.8.1 打印流的概念

  • 在整个IO包中,打印流是输出信息最方便的类,主要包括字节打印流(PrintStream)和字符打印流(PrintWriter)。
  • 打印流提供了非常方便的打印功能,可以打印任何的数据类型。如:小数、整数、字符串等。
  • 打印流可以间数据打印至控制台,也可以打印至文件

2.6.8.2 字节打印流_PrintStream

基本概念

构造方法

  • PrintStream(File file)
    使用指定的文件创建一个新的打印流,而不需要自动换行。
  • PrintStream(File file, String csn)
    使用指定的文件和字符集创建新的打印流,而不需要自动换行。
  • PrintStream(OutputStream out)
    创建一个新的打印流。
  • PrintStream(OutputStream out, boolean autoFlush)
    创建一个新的打印流。
  • PrintStream(OutputStream out, boolean autoFlush, String encoding)
    创建一个新的打印流。
  • PrintStream(String fileName)
    使用指定的文件名创建新的打印流,无需自动换行。
  • PrintStream(String fileName, String csn)
    创建一个新的打印流,不需要自动换行,具有指定的文件名和字符集。

常用方法

  • PrintStream append(char c)
    将指定的字符附加到此输出流。
  • PrintStream append(CharSequence csq)
    将指定的字符序列附加到此输出流。
  • PrintStream append(CharSequence csq, int start, int end)
    将指定字符序列的子序列附加到此输出流。
  • boolean checkError()
    刷新流并检查其错误状态。
  • protected void clearError()
    清除此流的内部错误状态。
  • void close()
    关闭流。
  • void flush()
    刷新流。
  • PrintStream format(Locale l, String format, Object… args)
    使用指定的格式字符串和参数将格式化的字符串写入此输出流。
  • PrintStream format(String format, Object… args)
    使用指定的格式字符串和参数将格式化的字符串写入此输出流。
  • 各种类型的 print()方法,打印一个某种类型的数据,不换行
  • void printf() 点击查看printf的详细用法

2.6.8.3 字符打印流_PrintWriter

  • 字符打印流的底层是字节打印流
  • PrintWriter的方法也不抛出异常
  • 复制文件使用PrintWriter比BufferedWriter更简单

2.6.9 数据流

2.6.9.1 DataInputStream和DataOutputStream

  • 提供了可以存取所有java基本数据类型和String的方法
  • 处理流,只针对字节流,二进制文件
  • 输入流链和输出流链
  • 读写数据的顺序必须一致,否则会报异常
    注意:只要关闭上层流即可
graph LR
FileInputStream-->BufferedInputStream
BufferedInputStream-->DataInputStream
DataInputStream-->数据
graph LR
数据-->DataOutputStream
DataOutputStream-->BufferedOutputStream
BufferedOutputStream-->FileOouputStream

2.6.10 对象流

2.6.10.1 对象序列化(Serializable)

  • ObjectOutputStream写对象(序列化:对象的内存状态以字节的形式存储到磁盘的文件上)
  • ObjectInputStream读对象(反序列化:磁盘上的字节形式的数据还原成对象的内存状态)

2.6.10.2 为什么要对象序列化?

序列化后的对象可以保存到磁盘上,也可以在网络上传输,使得不同的计算机可以共享对象。

2.6.10.3 对象序列化的条件

要想使某个类的对象能够序列化,那么需要这个类实现序列化接口Serializable

2.6.11 序列化和反序列化

  • 对象序列化方法 writeObject(Object e)

  • 对象反序列化方法 readObject();

  • 除了对象还能操作一些基本数据类型和String类型

  • 同样输出与输入的顺序必须一致,否则报异常

  • 被transient修饰的成员变量不被序列化

  • 方法不会被序列化

  • 如果一个可序列化的对象包含某个不可序列化的对象的引用,那么整个序列化的操作都会失败,并抛出一个NotSerializableException

  • 修改了实例属性后,会影响版本号,导致反序列化不成功,解决方案:为java对象指定序列化版本号serialVersionUID(在类的左边点击黄色警告符号)

2.6.12 文件夹的复制

2.6.12.1 字节流和字符流的选择

如果要复制的文件夹下文件类型不同,则使用字节流

2.6.12.2 问题分解

  • 1.复制一个文件
  • 2.指定目录下的所有文件
  • 3.指定目录下的所有文件及子目录下的所有文件

2.6.13 字节数组流

2.6.13.1 字节数组流的特点

  • 数据源与目的地为字节数组
  • 只有字节流,没有字符流

2.6.13.2 ByteArrayInputStream

  • 构造方法的有一个形参,byte[],通常来自ByteOutputStream

2.6.13.3 ByteArrayOutputStream

  • ByteArrayOutputStream的构造方法会在底层创建以个大小为32的byte数组
  • 使用返回类型为byte[]型的方法来使用ByteArrayOutputStream
  • ByteArrayOutputStream的对象使用toByteArray()方法将对象转为byte数组

2.6.14 设计模式_装饰器模式

2.6.15 IO体系总结

2.6.16 Apache IOUtils的使用_Apache FileUtils的使用

Commons IO是apache的一个开源工具包,封装了IO操作的相关类,使用Commons IO可以很方便的读写文件。极大地提高工作效率。

工具包安装方法

点此访问安装及使用的方法教程

2.7 多线程技术

2.7.1 多线程的基本概念

2.7.2 通过继承Thread实现多线程

  • 实现多线程的步骤
  • 继承Thread类
  • 重写run()方法
  • 通过start()方法启动线程:start方法是用来启动线程的,启动线程后程序中就有了两条线程,main方法线程和被start启动的线程
    线程会相互抢占cpu的使用权

2.7.3 通过接口实现Runnable接口实现多线程

  • 使用Runnable接口实现多线程的步骤
    • 1)编写实现Runnable接口
    • 2)实现run方法
    • 3)通过Thread类的start方法启动线程
  package cn.sxt.test;

class MyRunnable2 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			System.out.println("MyRunnable---"+i);
		}
	}
}
public class Test {
	public static void main(String[] args) {
		MyRunnable2 my = new MyRunnable2();
		Thread t =new Thread(my);
		t.start();
		for (int i = 0; i < 13; i++) {
			System.out.println("main------"+i);
		}
		}
	}


  • 静态代理模式
    • Thread ->代理角色
    • MyRunnable ->真实角色
    • 代理角色与真实角色实现接口Runnable接口
  • 举例:你没结过婚,不知道流程,不知道该怎么办。婚庆公司办过很多婚礼。于是你请婚庆公司帮你弄一些流程工作:
    • 婚礼(结婚接口Marry)
    • 结婚(Marry下的结婚方法marry)
    • 婚庆公司(MarryCompany)
      • 婚前准备(before)
      • 结婚(实现结婚方法marry)
      • 善后工作(after)
    • 你(You)
      • 你只负责结婚(marry)
  public class You implements Marry{
@Override
public void marry() {
	// TODO Auto-generated method stub
	System.out.println("结婚了,好高兴!");
}
}
  public class MarryCompany implements Marry{
Marry m; //声明一个Marry接口的实例

public MarryCompany(Marry m) {
	super();
	this.m = m;
}
public void before() { //结婚前准备方法
	System.out.println("婚庆公司在做婚前准备");
}
public void after() { //善后工作
	System.out.println("婚庆公司在做善后工作");
}
@Override
public void marry() {
	// TODO Auto-generated method stub
	this.before();
	m.marry(); //具体的结婚对象m调用结婚方法
	this.after();
}
}

   public interface Marry {
	public abstract void marry();
  }
  public class Test {
	public static void main(String[] args) {
		You y = new You();
		MarryCompany mc = new MarryCompany(y);
		mc.marry();
		}
	}

2.7.4 线程的生命周期

  • 周期顺序
    • 新生状态:new一个线程
    • 就绪状态:新生状态的线程调用start方法进入就绪状态。具备执行的资格,只欠cpu
    • 运行状态:有资格,有资源
    • 终止状态/死亡状态
    • 阻塞状态:无资格,让出资源
  • 总结
    • 1.不知道什么时候会被选中执行
    • 2.遇到等待IO输入或其它耗时操作时,有运行状态进入阻塞状态
    • 3.导致阻塞的时间,就绪状态,等候cpu选中执行
    • 4.运行状态时,给定的时间片内没有执行完run方法中的代码,进入就绪状态,下一次被cpu选中时,从上次断掉的地方,继续执行

2.7.5 获取线程基本信息的方法

  • static Thread currentThread() 返回目前正在执行的线程
  • final String getName() 返回线程的名称
  • final boolean isAlive 判断线程是否处于活动状态
  • setName() 修改线程名称

2.7.6 多线程的安全问题

2.7.7 暂停线程执行sleep/yeild/stop

  • final void join() 调用该方法的线程强制执行,其他线程处于阻塞状态,该线程执行完毕后,其它线程再执行
  • static void sleep(long millis) 使用当前正在执行的线程休眠millis秒,线程处于阻塞状态
  • static void yield() 礼让一次:当前正在执行的线程暂停一次,允许其他线程执行,不阻塞,线程进入就绪状态,如果没有其它等待执行的线程,这个时候当前线程就会马上恢复执行
  • final void stop() 强迫线程停止执行,已过时,不推荐使用

2.7.8 线程的优先级问题

  • final int getPriority() 获取线程的优先级
  • final void setPriority(int priority) 设置线程的优先级
  • 线程的最高优先级:10 MAX_PRIORITY
  • 线程的最低优先级:0 MIN_PRIORITY
  • 线程的默认优先级:5 NORM_PRIORITY
  • 优先级越高,越容易被cpu调用执行

2.7.9 线程同步——具体实现

  • 同步实现的方式: 同步代码块
//同步代码块
synchronized(obj){
    //obj为同步监听器,必须是一个对象,推荐使用共享资源的对象
}
  • 同步方法:使用关键字 synchronized 修饰一个方法,方法中的代码为同步方法
private synchronized void saleTicked(){
//卖票方法
    
}

2.7.11 生产者消费者模式的实现1-基本功能实现

实现生产者生产商品,消费者取走商品的功能

  • 商品类
package cn.sxt.thread;

public class Goods {
private String brand;
private String name;
public String getBrand() {
	return brand;
}
public void setBrand(String brand) {
	this.brand = brand;
}
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}
public Goods(String brand, String name) {
	super();
	this.brand = brand;
	this.name = name;
}
public Goods() {
	super();
}
//加同步锁的生产方法
public synchronized void set(String brand,String name) {
	this.brand=brand;
	this.name=name;
}
//加同步锁的取出方法
public synchronized void get() {
	System.out.println("消费者取走了-------"+this.brand+"---"+this.name);
}
}

  • 生产者类
package cn.sxt.thread;

public class Productor implements Runnable {
	private Goods goods;
	public Productor(Goods goods) {
		this.goods=goods;
	}
@Override
public void run() {

	for (int i=0;i<10;i++) {

		if (i%2!=0) {
			goods.set("旺仔", "小馒头");
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			goods.set("娃哈哈", "矿泉水");
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("生产者线程生产了---------"+goods.getBrand()+"---"+goods.getName());
	}
}
}

  • 消费者类
package cn.sxt.thread;

public class Customer implements Runnable{
private Goods goods;
public Customer(Goods goods) {
	this.goods=goods;
}
public Customer() {
	
}
@Override
	public void run() {
	for (int i = 0; i < 10; i++) {
		goods.get();
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	}
}

  • 测试类
package cn.sxt.thread;

public class Test {
public static void main(String[] args) {
	//创建共享资源
	Goods g =new Goods();
	//创建生产者线程
	Productor p = new Productor(g);
	new Thread(p).start();
	//创建消费者线程
	Customer c= new Customer(g);
	new Thread(c).start();
}
}

这个模型,生产者会重复生产,消费者会重复取走商品,我们需要进行优化,就要引入线程通信。

2.7.12 生产者消费者模式的实现2-线程通信引入

  • 线程间通信的方法
    • wait():调用了wait()方法的线程进入等待池等待,等待池中的线程不会去竞争对象锁,直到其它的线程通知,才会进入锁池
    • notify():随机唤醒一个在该对象上等待的线程,被唤醒的线程进入锁池,开始竞争该对锁上的对象
    • notifyAll():唤醒所有在该对象上等待的线程,优先级高的线程有可能先竞争到对象锁,只能在同步方法代码块中使用
  • 生产者消费者问题引入线程通信的具体实现
    • 商品类
package cn.sxt.thread;

public class Goods {
	private String brand;
	private String name;
	private boolean flag;// 新增flag用于表示是否有商品

	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}

	public String getName() {
		return name;
	}

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

	public Goods(String brand, String name) {
		super();
		this.brand = brand;
		this.name = name;
	}

	public Goods() {
		super();
	}

//加同步锁的生产方法
	public synchronized void set(String brand, String name) {
		if (flag) { //如果有商品,那么我等着
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//被唤醒后的线程执行wait()后的代码,所有这里不能加else,else后的代码与wait()是同级的
		this.brand = brand;
		this.name = name;
		System.out.println("生产者线程生产了---------"+this.brand+"---"+this.name);
		flag=true;
		super.notify();
	}

//加同步锁的取出方法
	public synchronized void get() {
		if (!flag) { //如果没有商品,那我在等待池里等着
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println("消费者取走了-------" + this.brand + "---" + this.name);
		flag=false;//取完商品,把flag变为false
		super.notify();
	}
}

  • 生产者类
package cn.sxt.thread;

public class Productor implements Runnable {
	private Goods goods;
	public Productor(Goods goods) {
		this.goods=goods;
	}
@Override
public void run() {

	for (int i=0;i<10;i++) {
//轮流生产娃哈哈矿泉水与旺仔小馒头
		if (i%2!=0) {
			goods.set("旺仔", "小馒头");
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			goods.set("娃哈哈", "矿泉水");
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
}
}
  
  • 消费者类
package cn.sxt.thread;

public class Customer implements Runnable{
private Goods goods;
public Customer(Goods goods) {
	this.goods=goods;
}
public Customer() {
	
}
@Override
	public void run() {
	for (int i = 0; i < 10; i++) {
		goods.get();//调用取出商品方法
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	}
}

  • 测试类
package cn.sxt.thread;

public class Test {
public static void main(String[] args) {
	//创建共享资源
	Goods g =new Goods();
	//创建生产者线程
	Productor p = new Productor(g);
	
	//创建消费者线程
	Customer c= new Customer(g);
	new Thread(p).start();
	new Thread(c).start();
	
	
	
}
}

2.8 网络编程技术

2.8.1 网络编程的基本概念

  • 协议不同,端口号可以相同

2.8.2 端口和Socket含义

  • 端口号
    • 每一个端口号用来标识计算机中的一个应用程序
    • 端口号范围0-65535
    • 一个网络程序至少有一个端口号
  • Socket 套接字
    • 是计算机通信的一种约定或一种方式
    • 通过Socket这种约定,一台计算机可以接受和发送其他计算机的数据
    • 每一个客户端都是用一个Socket对象表示,服务器端则使用ServerSocket等待客户端的连接
    • 在生活中,写信寄信这个操作,邮筒就相当于套接字

2.8.3 TCP和UDP协议区别

  • 网络参考模型(也称七层参考模型) OSI(Open System Interconnect)
    • 第一层:应用层:封装数据
    • 第二层:表示层:加标识
    • 第三层:会话层
    • 第四层:传输层
    • 第五层:网络层
    • 第六层:数据链路层
    • 第七层:物理层
  • TCP/IP参考模型:传输控制/网际协议
    我们在网络编程中使用到的就是TCP/IP参考模型
    • 应用层:使用Http协议、ftp协议等
    • 传输层:常用UDP协议
    • 网际层:使用IP协议
    • 网络接口(链路层+物理层)
  • TCP/IP参考模型的特点
    • TCP协议
      一种面向连接的、可靠地、基于字节流的运输层通信协议
      • 面向连接
      • 点到点通信
      • 高可靠性:三次握手
      • 占用资源多,效率低
    • UDP协议
      一种面向无连接的传输层协议,提供面向事务的简单不可靠信息传输服务
      • 非面向连接,传输不可靠,数据可能丢失
      • 发送不管对方是否准备好,接收方收到也不确认
      • 数据包的大小限制在64K内

2.8.4 TCP协议数据传递细节

  • C/S模式与B/S模式
    • C/S:客户端/服务器
    • B/S:浏览器/服务器
  • 使用TCP协议编写的程序都是C/S模式的
  • TCP编程使用IO流
  • 服务器端
graph TD
创建ServerSocket(int port)对象-->在Socket上监听客户端的连接请求
在Socket上监听客户端的连接请求-->阻塞、等待连接的简历
阻塞、等待连接的简历-->接收并处理请求信息
接收并处理请求信息-->将处理结果返回给客户端
将处理结果返回给客户端-->关闭流和Socket对象

  • 客户端
graph TD
创建Socket(String host,int port)-->向服务器发送连接请求
向服务器发送连接请求-->向服务器发送连接请求
接收服务结果-->关闭流和Socket对象

2.8.5 UDP协议数据传递细节

  • 不需要利用IO流实现数据的传输
  • 每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻它的目的地
  • DatagramSocket:用于发送或接收数据包
    • 发送方无需指定一个端口,接收方需要指定一个端口
  • DatagramPacket:发送数据包,需要包含:
    • 发送的数据是什么 :转为byte[]数据
    • 发送的数据大小
    • 发送到哪里去
    • 对方的监听端口

2.8.6 InetAddress类与InetSocketAddress类

  • InetAddress类
    • 封装了计算机的IP地址,不包含端口号
    • InetAddress没有构造方法
    • InetAddress 有两个子类,分别代表ipv4与ipv6
    • 常用方法
      • String getHostAddress() : 获取IP地址
      • String getHostName() : 获取主机名
      • static InetAddress getByName(String host) : 根据主机名获得IP地址
    • 创建InetAddress的三个方法
      • 根据本地主机来获得InetAddress对象
 InetAddress i = InetAddress.getLocalHost();
	 System.out.println("获取主机IP:"+i.getHostAddress());
	 System.out.println("获取主机名称:"+i.getHostName());
- 根据域名得到InetAddress对象
 InetAddress ia = InetAddress.getByName("www.baidu.com");
	 System.out.println("获取对象IP:"+ia.getHostAddress());
	 System.out.println("获取对象主机名:"+ia.getHostName());
- 根据IP地址得到一个InetAddress对象
InetAddress ik = InetAddress.getByName("10.123.222.130");
	 System.out.println("获取对象主机名:"+ik.getHostName());
	 System.out.println("获取IP地址:"+ik.getHostAddress());

当这个IP地址没有配置DNS域名解析时,获取主机名返回IP地址

  • InetSocketAddress类
    封装了IP地址与端口号,用于套接字通信
    • InetSocketAddress的构造方法
      • InetSocketAddress(String,int)
//表示本地主机的三种写法,其中9999端口号
        InetSocketAddress isa =new  InetSocketAddress("hostname",9999);
		InetSocketAddress isa1 = new InetSocketAddress("127.0.0.1",9999);
		InetSocketAddress isa2 = new InetSocketAddress("192.168.0.112",9999);
- InetSocketAddress(InetAddress,int)
        InetAddress ia = InetAddress.getLocalHost();
		InetSocketAddress isa3 = new InetSocketAddress(ia,8888);

2.8.7 URL类

  • URL(Uniform Resource Locator) 统一资源定位符,由4个部分组成:协议、存放资源的主机域名、端口号和资源文件名,例如:https:/www.baidu.com:80/index.html
  • 构造方法
    • URL(String) :在括号中填入URL地址
  • 常用方法
    • String getProtocol() 获取URL的协议
    • String getHost() 获取此URl的主机名
    • int getPort() 获取此URL的端口号
    • String getFile() 获取此URL的文件名
    • getDefaultPort() 获取此URL的默认端口号
    • getPath() 获取此URL的路径部分
    • openStream() 打开此URL以连接读取
//抓取网页保存到本地
	public static void main(String args[]) throws IOException {
		URL url = new URL("http://www.baidu.com");
		InputStream is = url.openStream();
		//创建缓冲流
		BufferedReader br = new BufferedReader(new InputStreamReader(is,"utf-8")); 
		//创建输出流
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\index.html")));
		String line=null;
		while((line=br.readLine())!=null) {
			bw.write(line);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
	
	}

2.8.8 基于TCP协议的Socket编程 模拟登陆服务器

客户端封装账号与密码为对象。发送到服务器端。服务器端收到对象进行反序列化,验证账号与密码是否正确,然后做出相应。做出响应可以用字节流。

  • client project中创建User类,用于封装用户名和密码
//实现Serializable接口
包含:
①String name
②String password
③给定User类一个序列化编号
  • client project中,创建客户端应用程序Client类
//(1)创建socket对象,用于连接服务器
	Socket client = new Socket("localhost",10000);
	//(2)获取输出流
	ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream())
	//(3)创建User对象
	User u = new User("bjsxt","password");
	//(4)User对象发送到服务器
	oos.writeObject(u);
	//(5)获取输出流(数据流)用于接收服务器端的响应信息
	DataInputStream dis = new DataInputStream(client.getInputStream());
	System.out.println(dis.readUTF());
	//(6)关闭流
	if(dis!=null){
	    dis.close();
	}
	if(oos!=null){
	    oos.close();
	    
	}
	if(client!=null){
	    client.close();
	}
  • ServerProject中创建User类与ClientProject中的User类一致
  • ServerProject中新的一个包中创建Server类
//(1)创建ServerSocket对象
ServerSocket server= new ServerSocket(10000);//端口号为10000
Socket socket =server.accept();
//(2)获取输入流--(对象流)
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
User user = (User) ois.readObject();
String str="";
//(3)对用户名和密码进行验证
if("bjsxt".equals(user.getUsername())&&"password".equals(user.getPassword())){
    str="登陆成功";
}else{
    str="对不起,账号密码不正确"
}
//(4)获取输出流(数据流)
DateOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(str);
//(5)关闭流
if(dos!=null){
    dos.close();
}
if(ois!=null){
    ois.close();
}
if(socket!=null){
socket.close();
}

这样做下来的服务器一次只能接受一个客户端的接入请求,要实现多个客户端同时接入,则需要引入多线程技术

  • 将Server类下(2)到(5)的代码切到新的包新的类ServerThread中
public class ServerThread implements Runnable{
	@Override
	public void run() {
		(2)
		(3)
		(4)
		(5)
	}

}
  • 在原Server类中添加线程启动代码
while(true) {
		Socket socket =server.accept();
		//创建线程类的对象,并启动线程
		ServerThread st = new ServerThread(socket);
		new Thread(st).start();
	}
  • 源码
    源码链接:请点击此处
    提取码: bc4j

你可能感兴趣的:(JAVA笔记)