Java基础

Starter

特点:

  • 跨平台
  • 面向对象
  • 无指针 (无法直接操作内存,速度无法加快)
  • 自动收集内存(占用系统资源,且无法实时收集无用资源)

缺点(相较于c,c++):

  • 运行速度慢
  • 占用系统资源多

跨平台

  • 跨平台:编译后的文件跨平台编译为中间语言,再由解释器二次编译,解释执行)
  • 平台:CPU处理器+操作系统OS,软硬件的结合 (不同的OS支持不同的CPU指令集)

对比:

  • 源程序(.c):编译执行,速度快,无法跨平台,eg:

    • 编译生成可执行文件
      • windows编译器(VC) => .exe
      • linux编译器(GCC,ICC) => .elf
      • 其他OS编译器 => 其他OS运行程序
  • 源程序(.java):解释执行,速度慢,跨平台(暴露源程序)

    • 编译生成中间码(平台无关)
      • 执行javac命令: .java=>.class
    • 解释执行(依赖平台)
      • windows下执行java命令:调用windows的Java解释器,生成windows平台运行码执行
      • linux下执行java命令:调用linux的Java解释器,生成linux平台运行码执行
      • 其他平台下执行java命令:调用其他OS的Java解释器,生成其他OS运行码执行
    • PS: 这里编译器和解释器均由Sun公司提供

JDK介绍

JDK(Java Developent ToolKit)包含:

  • Java编译器
  • JRE (Java Runtime Environment):Java运行时环境
    • JVM (Java Virtual Machine):Java虚拟机(平台相关),包含:
      • 类加载器(装入代码)
      • 字节码校验器(检查)
      • Java解释器(解释执行)
    • Java 运行支持文件 (类似内置函数)

PS:

  • JAVA SDK (Java Software develop kit):JDK的另一个称呼而已
  • 若不需要进行Java程序开发,只需安装JRE即可
  • JVM会执行垃圾回收,安全性检查等

Java程序运行过程

JDK安装

windows下

  1. 安装exe(eg: jdk-7u25-windows-i586.exe)
     //路径
     D:\Soft\Java                    (JDK)
    D:\Program Files\Java    (JRE,另放一个目录下或无需更改)
    
  2. 配置环境变量
    Java_Home= D:\Soft\Java            (JDK的安装路径)
    Path=%Java_Home%\bin                (JDK的bin目录,D:\Soft\Java\bin)
    ClassPath=%Java_Home%\jre\lib\rt.jar;.    (D:\Soft\Java\jre\lib\rt.jar)
    
  3. 测试JDK是否安装成功(cmd下)
     > javac
    

Linux下

  1. 安装rpm(eg: jdk-8u45-linux-x64.rpm)
     > rpm -ivh jdk-8u45-linux-x64.rpm     # 默认安装到/usr/java
     > rpm -q --whatprovides java
    
  2. 配置环境变量

    vi /etc/profile

     export JAVA_HOME=/usr/java/jdk1.8.0_45
     export PATH=$PATH:$JAVA_HOME/bin
     export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    

    source /etc/profile

  3. 测试JDK是否安装成功

     > java -version
     > javac -version
    
  4. 切换使用另一版本的JDK(设置默认JDK)

     > cd /usr/bin
     > ln -s -f /usr/java/jdk1.7.0_10/jre/bin/java
     > ln -s -f /usr/java/jdk1.7.0_10/bin/javac
     > java -version
     > javac -version
    

Java cmd

  • 编译:javac <Options> <Sourcefile>
    • -classpath <路径>:指定用户类文件的位置
    • -encoding <编码>:指定源文件中所用的字符集编码
    • -d <目录>:指定输出类文件的位置
  • 执行:java <class>,java -jar <jar>
  • 打包:jar {ctxu}[vfm0M] [destination] [manifest] [input-files]

    • {ctxu}:子命令,每次jar命令只能包含其中的一个
      • -c:创建新的JAR文件包
      • -t:列出JAR文件包的内容列表
      • -x:展开JAR文件包的指定文件或者所有文件
      • -u:更新已存在的JAR文件包(添加文件到JAR文件包中)
    • [vfm0M]:可选项
      • -v:生成具体报告并打印到标准输出
      • -f:指定JAR文件名,通常这个参数是必须的
      • -m:指定需要包含的MANIFEST清单文件
      • -0:只存储,不压缩,这样产生的JAR文件包会比不用该参数产生的体积大,但速度更快
      • -M:不产生所有项的清单(MANIFEST〕文件,此参数会忽略-m参数。
    • [destination]:需要生成、查看、更新或者解开的包,-f参数的附属参数
    • [manifest]:MANIFEST清单文件,它是-m参数的附属参数
    • PS:使用JDK的jar命令打包,会自动在压缩包中生成一个META-INF目录,其中有一个MANIFEST.MF文件,eg:
      Manifest-Version: 1.0
      Main-Class: com.cj.mytest.DailyMail
      Class-Path: lib/slf4j-log4j12-1.6.6.jar lib/slf4j-api-1.6.6.jar lib/log4j-1.2.17.jar
      
  • 使用示例:

      javac Hello.java      # 生成Hello.class
      java Hello            # 执行Hello的main方法
    
      jar -cvf test.jar *.*         # 打jar包
      java -jar test.jar            # 执行test.jar包的main方法
    
      jar -cvf test.war *.*          # 打war包
    

三个分支

根据应用方向解决问题的不同,Java分为三个分支:

  • JavaSE(Java Standard Edition)
    • 为创建和运行Java提供了最基本的环境
      • Java基础
      • 支持C/S架构
  • JavaEE(Java Enteriprise Edition)
    • 为基于服务器的分布式企业应用提供开发和运行环境
      • 支持B/S架构
      • 企业级功能
  • JavaME(Java Micro Edition)
    • 为嵌入式应用提供开发和运行环境,一套比JavaSE精简高效的类库

数据类型

数据类型

  • 值类型:基本数据类型
    • 整数:long,int,short,byte
    • 浮点数:float,double
    • 字符:char
    • 布尔:boolean
  • 引用类型:底层封装指针的数据类型
    • 数组
    • 接口

注意:字符串 String为引用类型

存放结构

  • 栈:
    • 先进后出,{}后回收清空,存放变量
    • 内容可以为:值类型的值,引用对象的地址
  • 堆:
    • 自动回收存放(哪空放哪)
    • 内容:引用对象的具体内容
  • 可通过Runtime类获取JVM内存信息
    • getRuntime():获取Runtime对象
    • totalMemory():获取JVM分配给程序的内存数量
    • freeMemory():获取当前可用的内存数量
    • maxMemory():获取JVM可以申请到的最大内存数量
  • PS:JVM通过OS管理内存空间(创建,收集,...)

jvm jvm

PS:方法中变量传递:

  • 会在栈中备份一份数值给参数变量
  • 值传递:使用替身(因为栈中存放的是具体内容)
  • 引用传递:使用真身(因为栈中存放的是内存地址)

异常处理

Throwable

异常处理:

  • 自行处理:try,catch,finally
  • 回避处理: throw,throws

字符串

String

String是类(引用数据类型)

  • 使用new关键字创建
      String a=new String("Hello");
      String b=new String("Hello");
      System.out.println(a==b);            //false
      System.out.println(a.equals(b));    //true
    
  • 直接创建(会自动使用字符串池)
      String a="Hello";
      String b="Hello";
      System.out.println(a==b);            //true
      System.out.println(a.equals(b));    //true
    
      String a=new String("Hello");
      String b="Hello";
      System.out.println(a==b);             //false
      System.out.println(a.equals(b));    //true
    

说明:

  • ==:比较的是两个变量是否指向同一块空间
  • equals():比较的是指向的两个内存空间的值是否相等
  • 字符串池
    • 存放直接创建的字符串(没有使用new关键字创建的)
    • 对于直接创建的字符串,首先会去字符串池找有没有相同值的字符串:
      • 如果有就直接指向它;
      • 如果没有就创建新的空间,且放入字符串池

StringBuffer & StringBuilder

字符串的拼接时,JVM每次都再给分配一块新的内存空间,原来分配的那块空间会变为垃圾内存等待回收,造成浪费

StringBuffer & StringBuilder

  • 创建时系统默认分配一定长度的内存
  • 使用append方法往其中放入字符串
  • 如果放入字符串超出分配的范围,则会自动在后面追加一倍长度的内存(这样前面的内存就可以继续使用,没有浪费)
  • 区别:
    • StringBuffer 同步,线程安全
    • StringBuilder 线程非安全,但效率高

String.format

格式化数字:

@Test
public void testDigit()
{
    System.out.println(String.format("%1$,09d", -3123)); // -0003,123
    System.out.println(String.format("%1$9d", -31));     //      -31
    System.out.println(String.format("%1$-9d", -31));     //-31      
    System.out.println(String.format("%1$(9d", -31));     //     (31)
    System.out.println(String.format("%1$#9x", 5689));     //   0x1639

    // 数字9表示九位
    System.out.println(String.format("%9d", 12345678));        // 不足9时,也要空出空间: 12345678
    System.out.println(String.format("%-9d", 12345678));    //12345678 
    System.out.println(String.format("%,09d", 123456));        //00123,456

    // 如为负数,则用括号括起来,如不是则不处理
    System.out.println(String.format("%(9d", -123456));        // (123456)
    System.out.println(String.format("% 9d", -123456));        //  -123456

    // 选择第二个args,即选择”1234“,而非”12345678“
    System.out.println(String.format("%2$9d", 12345678, 1234));    //     1234
    System.out.println(String.format("%#9x", 255));                //     0xff
    System.out.println(String.format("%#9o", 255));                //     0377
    System.out.println(String.format("%9x", 255));                //       ff
    System.out.println(String.format("%9o", 255));                //      377
    System.out.println(String.format("%d%%", 12));                //12%
}

格式化日期:

@Test
public void testDate()
{
    Date date = new Date();

    System.out.println(String.format("%td", date));    // 输出两位数字的日期,不够位补零:06
    System.out.println(String.format("%te", date));    // 输出两位数字的日期,不补零:6
    System.out.println(String.format("%tm", date));    // 输出两位数字的月份,不够位自动补零:06

    System.out.println(String.format("%ty", date));    // 输出两位数字的年份:14
    System.out.println(String.format("%tY", date));    // 输出四位数字的年份:2014
    System.out.println(String.format("%tj", date));    // 输出一年中的第几天(001-366):157

    System.out.println(String.format("%ta", date));    // 输出指定语言环境的星期简称:Fri
    System.out.println(String.format("%tA", date));    // 输出指定语言环境的星期全称:Firday

    System.out.println(String.format("%tb", date));    // 输出指定语言环境的月份简称:Jun
    System.out.println(String.format("%tB", date));    // 输出指定语言环境的月份全称:June

    System.out.println(String.format("%tc", date));    // 输出指定语言环境的全部日期和时间信息:Fri Jun 06 09:07:56 CST 2014
}

格式化时间:

@Test
public void testTime()
{
    Date date = new Date();
    System.out.println(date); //Fri Jun 06 09:08:11 CST 2014

    System.out.println(String.format("%tH", date));    // 输出两位24小时制的小时:09
    System.out.println(String.format("%tk", date));    // 输出不补零24小时制的小时:9

    System.out.println(String.format("%tI", date));    // 输出两位12小时制的小时:09
    System.out.println(String.format("%tl", date));    // 输出不补零12小时制的小时:9

    System.out.println(String.format("%tM", date));    // 输出两位的分钟:08
    System.out.println(String.format("%tS", date));    // 输出两位的秒钟:11
    System.out.println(String.format("%tZ", date));    // 输出时区缩写形式的字符串:CST
    System.out.println(String.format("%tp", date));    // 输出特定语言环境的上午或下午标记:am
}

格式化时间日期:

@Test
public void testDateTime()
{
    Date date = new Date();

    System.out.println(String.format("%tR", date));    // 输出24小时制,被格式化为”%tH:%tM:%tS“:09:10
    System.out.println(String.format("%tT", date));    // 输出24小时制,被格式化为”%tH:%tM“:09:10:23
    System.out.println(String.format("%tr", date));    // 输出24小时制,被格式化为”%tH:%tM:%tS:%tp“:09:10:23 AM

    System.out.println(String.format("%tD", date));    // 输出日期,被格式化为”%tm/%td/%ty“:06/06/14
    System.out.println(String.format("%tF", date));    // 输出日期,ISO 8061格式的完整日期,被格式化”%tY-%tm-%td“:2014-06-06
    System.out.println(String.format("%tc", date));    // 输出日期和时间,被格式化”%ta%tb%td%tT%Z%tY“:Fri Jun 06 09:10:23 CST 2014
}

时间处理

Date Format

SimpleDateFormat

@Test
public void testFormatDateTime() throws ParseException{
    SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss a",Locale.ENGLISH);
    Date now=new Date();
    System.out.println(sdf.format(now));
}

@Test
public void testParseTime() throws ParseException{
    SimpleDateFormat sdf = new SimpleDateFormat ("h:mma",Locale.ENGLISH); 
    Date d=sdf.parse("6:20pm");
    System.out.println(d);
}

ISO8601Utils

@Test
public void testISOTime() throws ParseException{
    System.out.println("-----------1------------------");
    String isoStr="2014-12-17T00:00:00Z";
    Date date=ISO8601Utils.parse(isoStr);
    System.out.println(date);

    System.out.println("-----------2------------------");
    String dateStr="2014-12-17";
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    date=sdf.parse(dateStr);
    System.out.println(date);
    System.out.println(ISO8601Utils.format(date));

    System.out.println("-----------3------------------");
    Calendar c=Calendar.getInstance();
    c.set(Calendar.YEAR, 2014);
    c.set(Calendar.MONTH,11);
    c.set(Calendar.DAY_OF_MONTH,17);
    date=c.getTime();
    System.out.println(date);
    System.out.println(ISO8601Utils.format(date));

    System.out.println("-----------4------------------");
    c.set(Calendar.HOUR_OF_DAY,0);
    c.set(Calendar.MINUTE,0);
    c.set(Calendar.SECOND,0);
    date=c.getTime();
    System.out.println(date);
    System.out.println(ISO8601Utils.format(date));
}

封装

封装: 隐藏属性,方法和方法实现细节的过程

包与访问修饰符:

  • public :所有类都能访问
  • protected :子类可以访问(可在不同包)
  • 默认 :同包中可访问
  • private :同类中看访问

继承

继承: 在一个类基础上定义一个新类 (Java中只有单继承)

  • 子类拥有父类的属性和方法
    • this调用本类中的属性和方法
    • super调用父类中的属性和方法
  • 子类可以覆盖父类的方法(方法重写,方法覆盖)
    • 方法覆盖(方法重写):子类方法与父类方法具有相同的方法声明(方法头),不同的实现(方法体)
    • 可是用关键字@Override约束(便于在编译时就发现错误)
  • 可以声明父类,创建子类
    • 创建时会先调用父类构造方法
      • 默认调用父类无参构造方法
      • 在构造方法中,可使用super调用父类其他构造方法(必须置于第一行)
    • 只能调用声明类型所包含的属性和方法签名
    • 具体执行时调用创建类型的方法

示例:

public class Father{
    private String name="Father";
    public void t1(){
        System.out.println(this.name+" t1");
    }
    public void t2(){
        System.out.println(this.name+" t2");
    }
}

public class Son extends Father{
    private String name="Son";
    @Override
    public void t2(){
        System.out.println(this.name+" t2-extends "+super.name);
    }
    public void t3(){
        System.out.println(this.name+"t3"+);
    }
}

@Test
public void testExtend(){
    Father father= new Son();    //声明父类,创建子类
    father.t1();    //Father t1
    father.t2();    //Son t2-extends Father
    ((Son)father).t3()    //Son t3
}

Object类

  • 老祖宗
  • 如果某个类没有父类,那它就默认的继承自Object类
  • public String toString():当打印对象时只写对象名不写方法名,默认调用此方法
  • protected void finalize() throws Throwable:类似C++的析构方法,在对象消亡时由系统自动调用
  • public boolean equals(Object obj):比较两个对象的数值(堆空间中存储的对象数值)是否一致,一般也由子类覆盖
  • hashCode
  • wait()
  • ...

多态

多态: 具有表现多种形态能力的特征

同一个接口,使用不同的实例,执行不同的操作 (运行时,JVM根据实际创建的对象类型,动态调用对应方法)

抽象类

抽象类

  • 使用abstract关键字修饰类,eg: public abstract class A{...}
  • 可包含抽象方法(无方法体)和非抽象方法
  • 子类必须实现所有抽象方法

接口

接口

  • 使用interface关键字,eg:public interface A{...}
  • 高度抽象类,方法特征的集合,只能继承自接口
  • 成员自动设为public,不能设为private
  • 属性自动设为public final static,即为公有常量
  • 一个类可以实现多个接口

Enum 枚举

  • 实际是继承自java.lang.Enum<E>,且不可被继承(final)的一个类
  • 继承自Enum的方法
    • ordinal() 返回枚举值在枚举类种的顺序(根据枚举值声明的顺序而定)
    • compareTo(enum) 返回的是两个枚举值的顺序之差(Enum实现了Comparable接口)
    • values() 静态方法,返回一个包含全部枚举值的数组
    • toString() 返回枚举常量的名称
    • valueOf(enumName) 返回带指定名称的指定枚举类型的枚举常量 (和toString方法是相对应的)
    • equals(enum) 比较两个枚举类对象的引用
  • 其枚举值都是类静态常量(public static final
  • 构造函数:只能私有private,不能有public
    • 保证外部代码无法新构造枚举类的实例
    • 只是在构造枚举值的时候被调用

示例:

public enum Color{
    RED,BLUE,BLACK,YELLOW,GREEN
}

扩展:

public enum Color{
    RED(255,0,0),BLUE(0,0,255),BLACK(0,0,0),YELLOW(255,255,0),GREEN(0,255,0);  

    //自定义数据域
    private int redValue;
    private int greenValue;
    private int blueValue;

    //构造枚举值,比如RED(255,0,0)
    private Color(int rv,int gv,int bv){
     this.redValue=rv;
     this.greenValue=gv;
     this.blueValue=bv;
    }
}

使用:

@Test
public void testEnum(){
    Color.RED.ordinal();     // return: 0
    Color.BLUE.ordinal();  //return: 1
    Color.RED.compareTo(Color.BLUE);  //return: -1

    Color[] colors=Color.values();
     for(Color c:colors){
            System.out.print(c+",");
     }//print: RED,BLUE,BLACK YELLOW,GREEN

    Color c=Color.RED;
    System.out.println(c);    //print: RED

    Color.valueOf("BLUE");   //return: Color.BLUE
}

final

  • 属性上:常量
  • 方法上:不能被子类重写
  • 类上:不能被继承

static

static:在类中使用,方便在没有创建对象的情况下来进行调用(方法/变量)

static方法 & static变量

  • 属于类,类初次加载时初始化,在内存中只有一个副本
  • 不依赖于任何对象就可以进行访问
  • 可通过类.xxx调用,也可使用该类的对象.xxx调用
  • static方法中不能使用this,不能访问类的非静态方法和非静态成员方法(因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用)
  • final结合使用:
    • 修饰成员变量:全局常量,不可改值
    • 修饰成员方法:可通过类名访问,且不可覆盖
    • PS:通过static final修饰的容器类型变量中所“装”的对象是可改变的
  • 注意:
    • static不会改变变量和方法的访问权限
    • static不允许用来修饰局部变量
    • 类的构造器实际上也是静态方法(只是没有显示地声明而已)

static代码块(可以用来优化程序性能)

  • 在类加载的时候按static代码块的顺序各执行一次
  • 可以置于类中的任何地方

示例:

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }
    public Test() {
        System.out.println("test constructor");
    }
    public static void main(String[] args) {
        new MyClass();
    }
}

public class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}
public class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }
    public MyClass() {
        System.out.println("myclass constructor");
    }
}

Result: (各个类的static块->某对象成员变量->某对象构造方法)

test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor

自定义注解

示例:

定义注解

@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelResources {
    String title() default "";
    int[] order() default {};
    public Class<? extends ExcelFormat> format() default ExcelFormat.None.class;
    public boolean isRefClass() default false;
}

使用注解

public class Schedule{
    @ExcelResources(title="标识",order=1)
    public int getId() {
        return id;
    }
    @ExcelResources(title="Boarding Time",order=3,format=ScheduleFormat.class)
    public CrossSchedule getSchedule(){
        return schedule;
    }
    ...
}
public class ScheduleFormat extends ExcelFormat{
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    @Override
    public String format(Object obj){
        if(obj!=null && obj instanceof CrossSchedule){
            CrossSchedule schedule=(CrossSchedule)obj;
            return sdf.format(schedule.getServiceDate())+" ("+schedule.getDayOfWeekCn()+")";
        }
        return null;
    }
}

解析处理注解

Method[] methods = Schedule.class.getDeclaredMethods();
for(Method method:methods) {
    if(method.isAnnotationPresent(ExcelResources.class)) {
        ExcelResources er = method.getAnnotation(ExcelResources.class);
        String title=er.title();
        int[] orders=er.order();
        ExcelFormat format=er.format();
        boolean isRef=er.isRefClass();
        ...
    }
}

数组 && 集合框架

数组

创建数组(赋初值与赋长度只能出现一个):

数据类型[] 数组名 = new 数据类型[长度];
数据类型 数组名[] = new 数据类型[长度];

数据类型[] 数组名 = new 数据类型[]{初始值,初始值};
 数据类型[] 数组名 = {初始值,初始值};

eg:

一维数组:

int[] a=new a[3];
a[0]=1;
a[1]=2;
a[2]=3;
int[] a={1,2,3};

二维数组:

int[][] a=new a[2][3];
a[0]={1,2,3};
a[1]={4,5,6};
int[][] a={
    {1,2,3},
    {4,5,6}
}

集合框架

Collection

说明:

  • Collection & Map
    • Collection:直接把元素放入集合
    • Map:存放的是键值对,键不能重复,值可以重复
  • List & Set
    • List:有下标,元素可重复
    • Set: 无下标,元素不可重复
  • Vector & ArrayList
    • Vector:同步,线程安全
    • ArrayList:线程非安全
  • Hashtable & HashMap
    • Hashtable:同步,线程安全,不允许key或value为null
    • HashMap:线程非安全,允许key或value为null
  • Collections

    • Collection的算法类
    • 全是静态方法,eg:
      • 排序:sort(list),sort(list, comparator)
      • 求最大值:max(list)
      • 求最小值:min(list)
  • 均在java.util包中(包括Collections)

Collection使用示例:

Student stu1=new Student(1,"Tom");
Student stu2=new Student(2,"Jerry");
Student stu3=new Student(3,"Lucy");

List<Student> students=new ArrayList<Student>();
students.add(stu1);
students.add(stu2);
students.add(0,stu3);    //students:(3,Lucy),(1,Tom),(2,Jerry)
students.add(stu1);        //students:(3,Lucy),(1,Tom),(2,Jerry),(1,Tom)
stu1.setName("Susan");    //students:(3,Lucy),(1,Susan),(2,Jerry),(1,Susan)

students.remove(1);            //students:(3,Lucy),(2,Jerry),(1,Susan)
students.remove(stu1);    //students:(3,Lucy),(2,Jerry)

students.contains(stu1);    //return false;
students.contains(stu2);    //return true;
students.contains(new Students(2,"Jerry"));        //false,若重写Student的equals方法可为true

for(int i=0;i<students.size();i++){
    System.out.println(students.get(i));
}

for(Student stu:students){
    System.out.println(stu);
}

Interator<Student> it=students.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}

Map使用示例:

Map<Integer,Student> studentMap=new HashMap<Integer,Student>();
studentMap.put(1,stu1);
studentMap.put(2,stu2);
studentMap.put(3,stu3);
studentMap.put(null,null);

studentMap.containsKey(2);
Student stu=studentMap.get(3);

Set<String> keys=studentMap.keySet();
Collection<Student> values=studentMap.values();

for(String key:keys){
    System.out.println(studentMap.get(key));
}

for (Map.Entry<Integer, Student> entry : map.entrySet()) {  
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

Collections使用:排序

方式一:

Collections.sort(students);
Collections.reverse(students);

public class Student implements Comparable<Student>{
    ...
    @Override
    public int compareTo(Student o){
        return  id - o.getId();        //正数:一个比一个大
    }
}

方式二:

Collections.sort(students,new StudentComparator());

public class StudentComparator implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o2.id-o1.id;
    }
}

IO && 反射 && 序列化

IO

  • File类:处理文件、文件夹(不能编辑文件)

      File file=new File(fileUrl);
      if(file.exists())
          file.delete();
      file.createNewFile();
      file.mkdir();
    
      File dir=new File(dirUrl);
      dir.isDirectory();
      String[] list=dir.list();
      File[] str=dir.listFiles();
    
    • 字节流(节点流):直接创建的流 InputStream/OutputStream
      InputStream in=FileInputStream(fileUrl);
      OutputStream out=FileOutputStream(fileUrl2);
      int len=0;
      byte[] buffer=new byte[512];
      while((len=in.read(buffer))!=-1){
        System.out.println(buffer);
        out.write(buffer,0,len);
      }
      in.close();
      out.close();
      
    • 字符流(过滤流):需依赖已创建的节点流 Reader/Writer
      BufferedReader reader=new BufferedReader(new FileReader(fileUrl));
      String str=null;
      while ((str=reader.readLine())!=null) {
        System.out.print(str);
      }
      
    • 转换流:字节流=>字符流 InputStreamReader/OutputStreamWriter
      File file=new File(fileUrl);
      Reader reader=InputStreamReader(new FileInputStream(file));
      String str=null;
      while ((str=reader.readLine())!=null) {
        System.out.print(str);
      }
      reader.close();
      

反射

程序运行中,动态获取对象的信息,或构造某个对象 (可加载运行时才得知名称的class,并创建)

Class cls=class.forName("com.cj.my.MyTest");
MyTest myTest=(MyTest)cls.newInstance();

序列化

序列化:读取内存中对象的机制(把对象写到流里)

  • implements Serializableimplements Externalizable
  • 引用类型的成员也需可序列化
  • 可使用transient关键字标识不序列化
  • 在类中实现以下方法,可以以更细粒度的方式控制序列化/反序列化的过程
    • private void writeObject(ObjectOutputStream out) throws IOException
    • private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException
  • 注意:static的也无法序列化(序列化保存的是对象的状态,static属于类的状态)

反序列化:序列化的反过程,将对象读取到内存的机制(把对象从流中读出来)

使用示例: 将Java 对象序列化为二进制文件,使用ObjectInputStreamObjectOutputStream 进行对象的读写

public class Person implements Serializable
{
    int age;
    String name;
    double height;
    public Person(int age, String name, double height){
        this.age = age;
        this.name = name;
        this.height = height;
    }

    //序列化控制
    private void writeObject(ObjectOutputStream out) throws IOException
        out.writeInt(age);
        out.writeUTF(name);
        System.out.println("write object");
    }

    //反序列化控制
    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException{
        age = in.readInt();
        name = in.readUTF();
        System.out.println("read object");
    }
}
@Test
public void serializeTest(){

    //序列化
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.txt"));
    Person p = new Person2(20, "zhangsan", 4.55);
    oos.writeObject(p);
    oos.close();

    //反序列化
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));
    p = (Person) ois.readObject();
    System.out.println(p.age + "," + p.name + "," + p.height);
    ois.close();
}

JDBC

JDBC:Java DataBase Connectivity,提供连接各种数据库的能力

JDBC

  • JDBC API :提供了Java与各种不同数据库交互的标准接口
    • Connection 与数据库建立连接
    • Statement 发送SQL语句
    • ResultSet 处理结果
  • JDBC 驱动:由各个数据库厂商提供,负责连接对应的数据库(实现了JDBC API 中定义的各种接口)
  • JDBC-ODBC桥接:将对JDBC API的调用,转换为对另一组数据库(ODBC)API的调用(rt.jar 中有实现,无需再导入)
  • JDBC Driver Manager:管理各种不同的JDBC驱动

使用:

  1. 加载JDBC驱动
     Class.forName(“JDBC 驱动类的名称”);  // JDBC 驱动字符串:“包名.类名”
    
  2. 建立连接
     Connection  conn= DriverManager.getConnection(URL,user,password);
    
  3. 发送SQL语句,获取执行结果

     //使用Statement
     Statement  stmt = conn.createStatement();
     ResultSet  rs = stmt.executeQuery(“查询的SQL语句”);      //返回结果集对象
     int  rows = stmt.executeUpdate(“增删改的SQL语句”);     //返回受影响的行数
     //循环结果集
     while(rs.next()){
         int x= rs.getInt("id"); // 或rs.getInt(1);
         String  strName =rs.getString(“name”);
         …
     }
    
     //使用预处理器PreparedStatement(继承自Statement接口,快,灵活)
     String  strSql=“UPDATE  TABLE1  SET  a=?  WHERE  b=?”;
     PreparedStatement  pstmt = conn.prepareStatement(strSql);
     pstmt.setString(1,”Hello”);
     pstmt.setInt(2,10);
     int rows = pstmt.executeUpdate();    //ResultSet  rs = pstmt.executeQuery();
    
  4. 关闭资源(一般放于finally 块中)

     rs.close();        //关闭记录集
     stmt.close();    //关闭处理器对象
     conn.close();    //关闭连接
    

线程

线程的5个状态:

  1. 创建(new
    • new时
  2. 可运行(runnable
    • 调用start()后
    • sleep结束后
    • 被notify唤醒后
    • I/O完成
  3. 运行(running
  4. 不可运行 (blocked/waiting
    • 调用了sleep()
    • 调用了wait()等待特定通知
    • I/O阻塞
  5. 消亡(dead
    • run()执行完成后(注意:不能使用stop()终止线程的执行)

Thread

说明:

  • thread.sleep(ms):睡眠,线程阻塞(不释放锁)
  • object.wait():等待,放入与该对象相关的等待池中(执行线程释放锁)
  • object.notify()/notifyAll():唤醒对应等待池中的线程(被唤醒的线程重新获得锁)
  • thread.join()/join(ms)
    • 等待此线程死亡后再继续(可使异步线程变为同步线程)
    • 不会释放锁
  • thread.yield():退让,把运行机会让给了同等优先级的其他线程
    • 让当前线程暂时交出CPU执行时间
    • 不能控制具体的交出时间
    • 不会释放锁
    • 只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行
  • thread.interrupt():中断处于阻塞状态的线程(抛出InterruptedException),配合isInterrupted()能够中断正在运行的线程

线程类

线程类实现方式:

  • 方式一:
    • implements Runnable
    • @Override run()
    • 示例:
      public class MyRunnable implements Runnable{
        @Override
        public void run(){
            ...
        }
      }
      //创建使用
      Thread myThread=new Thread(new MyRunnable());
      myThread.start();
      
  • 方式二:
    • extends Thread(实际上public class Thread implements Runnable
    • @Override run()
    • 示例:
      public class MyThread extends Thread{
        @Override
        public void run(){
            ...
        }
      }
      //创建使用
      MyThead myThread=new MyThread();
      myThread.start();
      

线程同步

  • Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor)
  • 不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制
  • 当访问某个对象的synchronized方法时,将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法
  • 在synchronized方法中:使用wait()释放锁,暂停执行;直到被notify(),重新获取锁,继续执行
  • 当synchronized方法执行完或发生异常时,会自动释放锁
  • 注意:
    • synchronized声明不会被继承
    • 在一个非静态方法中 ,使用this(当前的实例对象)代表锁对象
    • 在静态方法中,锁对象是类(用Object.class,而不能用this)

示例1:创建10个线程,每个线程都打印从0到99这100个数字,希望线程之间不会出现交叉乱序打印,而是顺序地打印

实现方式1:每个线程的传入唯一的共享的对象锁

public class MyThread implements Runnable{
    private int threadId;
    private Object lock;
    public MyThread(int id,Object lock){
        this.threadId = id;
        this.lock=lock;
    }
    @Override
    public  void run() {
        synchronized(lock){
            for (int i = 0; i < 100; ++i){
                System.out.println("Thread ID: " + this.threadId + " : " + i);
            }
        }
    }
}

@Test
public static void main(String[] args) throws InterruptedException{
    Object obj=new Object();
    for (int i = 0; i < 10; ++i){
        new Thread(new MyThread(i,obj)).start();
        Thread.sleep(1);
    }
}

使用方式二:使用静态变量或静态方法

//静态方法或变量是所有类实例对象所共享的,因此线程对象在访问此静态方法时是互斥访问的,从而可以实现线程的同步
public class MyThread implements Runnable{
    private int threadId;
    //定义静态变量作为锁
    private static Object lock = new Object();

    public MyThread(int id){
        this.threadId = id;
    }
    @Override
    public  void run() {
        //方式一:使用静态方法
        //taskHandler(this.threadId);

        //方式二:使用静态变量
        synchronized(lock){
            for (int i = 0; i < 100; ++i){
                System.out.println("Thread ID: " + this.threadId + " : " + i);
            }
        }

    }
    //在静态方法中加入sychronized关键字封锁的就是类本身
    private static synchronized void taskHandler(int threadId) {
        for (int i = 0; i < 100; ++i) {
            System.out.println("Thread ID: " + threadId + " : " + i);
        }
    }
}

@Test
public static void main(String[] args) throws InterruptedException{
    for (int i = 0; i < 10; ++i){
        new Thread(new MyThread(i)).start();
        Thread.sleep(1);
    }
}

示例2:

@Test
public void testThread(){
    Operation operation = new Operation();

    Thread t1 = new IncreaseThread(operation);
    Thread t2 = new DecreaseThread(operation);
    Thread t3 = new IncreaseThread(operation);
    Thread t4 = new DecreaseThread(operation);

    t1.start();
    t2.start();
    t3.start();
    t4.start();
}
public class Operation{
    private int number;
    public synchronized void increase(){
        while (0 != number){
            try{
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        number++;
        System.out.println(number);
        notify();
    }

    public synchronized void decrease(){
        while (0 == number){
            try{
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        number--;
        System.out.println(number);
        notify();
    }
}
public class IncreaseThread extends Thread{
    private Operation operation;
    public IncreaseThread(Operation operation){
        this.operation = operation;
    }
    @Override
    public void run(){
        for(int i = 0; i < 20; i++){
            try{
                Thread.sleep((long)(Math.random() * 1000));
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            operation.increase();
        }
    }
}

public class DecreaseThread extends Thread{
    private Operation operation;
    public DecreaseThread(Operation operation){
        this.operation = operation;
    }
    @Override
    public void run(){
        for(int i = 0; i < 20; i++){
            try{
                Thread.sleep((long)(Math.random() * 1000));
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            operation.decrease();
        }
    }
}

线程池

Java通过class Executors提供的静态方法,创建四种线程池(interface ExecutorService extends Executor

  • Executors.newSingleThreadExecutor(..)
    • 创建唯一的工作线程来执行任务
    • 如果这个线程异常结束,会有另一个取代它
    • 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
  • Executors.newFixedThreadPool(...)
    • 创建一个指定工作线程数量的线程池
    • 不会释放空闲线程
    • 定长线程池的大小最好根据系统资源进行设置(如Runtime.getRuntime().availableProcessors()
  • Executors.newCachedThreadPool(...)
    • 创建一个可缓存的线程池
    • 会回收空闲线程,释放相应资源
  • Executors.newScheduleThreadPool(...)
    • 创建一个定长的线程池
    • 支持定时及周期性的任务执行,类似于Timer
  • 使用:
    • 直接使用execute(Runnable command)方法
      ExecutorService pool = Executors.newXxxThreadExecutor();
      pool.execute(new Runnable(){
        public void run(){
           //执行任务
          }
      }
      
    • 使用submit(Runnable task),submit(Callable<T> task)提交任务并等待获取执行结果
      ExecutorService pool = Executors.newXxxThreadExecutor();
      Future<?> future=pool.submit(new Runnable(){
        //执行任务
      })
      //获取执行结果
      future.get();
      
  • 注意:
    • Executor并不是一个线程池,而只是一个执行线程的工具
    • 真正的线程池接口是ExecutorService
    • Executors实现的线程池,底层使用的就是ThreadPoolExecutorinterface ExecutorService的一种实现)
      • 构造方法:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
      • corePoolSize :池中所保存的线程数,包括空闲线程
      • maximumPoolSize:池中允许的最大线程数
      • keepAliveTime : 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
      • unit : keepAliveTime 参数的时间单位
      • workQueue : 执行前用于保持任务的队列,此队列仅保持由 execute方法提交的 Runnable任务
      • threadFactory : 执行程序创建新线程时使用的工厂
      • handler : 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序

Executors

使用示例:

@Test
public void testSingleThreadPool(){
    //结果依次输出,相当于顺序执行各个任务
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        singleThreadExecutor.execute(new Runnable() {  
            public void run() {
                 try {
                      System.out.println(index);
                      Thread.sleep(2000);
                 } catch (InterruptedException e) {
                        e.printStackTrace();
                 }
            }
        });
    }
}
@Test
public void testFixedThreadPool() {
    //线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字
    //若任务数量大于 poolSize ,任务会被放在一个 queue 里顺序执行
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int index = i;
        fixedThreadPool.execute(new Runnable() {
            public void run() {
                 try {
                      System.out.println(index);
                      Thread.sleep(2000);
                 } catch (InterruptedException e) {
                      e.printStackTrace();
                 }
            }
        });
    }
}
@Test
public void testCachedThreadPool(){
    //线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        try {  
            Thread.sleep(index * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cachedThreadPool.execute(new Runnable() {  
            public void run() {
                 System.out.println(index);
            }
        });
    }
}
@Test
public void testScheduleThreadPool(){
    //延迟1秒后每3秒执行一次
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {  
        public void run() {  
            System.out.println("delay 1 seconds, and excute every 3 seconds");  
        }
    }, 1, 3, TimeUnit.SECONDS);
}

PS:可以使用JDK自带的监控工具(JDK安装目录/bin/jconsole.exe)来监控创建的线程

ThreadLocal

线程本地变量/线程局部变量:

  • 相当于一个工具类(辅助管理各个线程的本地变量们),通常是全局的,支持泛型
  • 各线程保存自己使用的对象们(每一个线程都维护各自的变量副本们)
    • 各个Thread内部都有一个ThreadLocalMap成员变量
    • ThreadLocalMap:键为ThreadLocal对象(因为每个线程中可有多个threadLocal变量),值为对应线程的变量副本的值
  • 提供的方法:
    • void set(T value):将此ThreadLocal的当前线程副本中的值设置为指定值
    • void remove():移除此ThreadLocal的当前线程的值
    • protected T initialValue():返回此ThreadLocal的当前线程的“初始值”
    • T get():返回此ThreadLocal的当前线程副本中的值(若无,则初始化,内部会调用initialValue方法返回的值进行设置)
      //ThreadLocal中get方法的定义:
      public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
      }
      
  • 应用场景: 数据库连接、Session管理
      public class ConnectionManager{
          private static ThreadLocal<Connection> theadLocal= new ThreadLocal<Connection>() {
              public Connection initialValue() {
                  return DriverManager.getConnection(DB_URL);
              }
          }
          public static Connection getConnection() {
              return theadLocal.get();
          }
          public static void closeConnection(){
              Connection conn = threadLocal.get();
              try {
                  if(conn != null && !conn.isClosed()) {
                      conn.close();
                      threadLocal.remove();
                      conn = null;
                  }
              } catch (SQLException e) {
              }
          }
      }
    

使用示例:

@Test
public void testThreadLocal(){
    //3个线程共享sn,各自产生序列号
    MyThread t1 = new MyThread();
    MyThread t2 = new MyThread();
    MyThread t3 = new MyThread();
    t1.start();
    t2.start();
    t3.start();
}

public class SequenceUtils{
    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
        protected Integer initialValue(){
           return 0;
        }
    }
    public static Integer genNextNum(){
       threadLocal.set(threadLocal.get()+1);
       return threadLocal.get();
    }
}

public class MyThread extends Thread{
      public void run(){
          //每个线程打印5个序列号
          for(int i=0;i<5;i++){
              System.out.println(Thread.currentThread().getName()+":"+SequenceUtils.genNextNum());
          }
      }
    }
}

PS:对于多线程资源共享的问题(多线程中相同变量的访问冲突问题)

  • Synchronized用于线程间的数据共享:仅提供一份变量,让不同的线程排队访问(“以时间换空间”)
  • ThreadLocal用于线程间的数据隔离:为每一个线程都提供一个独立的变量副本,从而可以同时访问而互不影响(“以空间换时间”)
  • 两者处理不同的问题域!!
  • 使用示例:使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题(为每个线程创建一个SimpleDateFormat变量的副本)
      public class DateUtil {
          private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
          @SuppressWarnings("rawtypes")
          private static ThreadLocal threadLocal = new ThreadLocal() {
              protected synchronized Object initialValue() {
                  return new SimpleDateFormat(DATE_FORMAT);
              }
          };
          public static DateFormat getDateFormat() {
              return (DateFormat) threadLocal.get();
          }
          public static Date parse(String textDate) throws ParseException {
              return getDateFormat().parse(textDate);
          }
      }
    

网络

ISO/OSI 7层参考模型(解决异质性问题):

  1. 应用层 Application:(可使用协议:Telnet,FTP,SMTP,Http,。。)
  2. 表示层 Presention:数据表示
  3. 会话层 Session:主机间通信
  4. 传输层 Transport:端到端的连接(可使用协议:TCP,UDP,。。)
  5. 网络层 IP:寻址和最短路径
  6. 链路层 Link:介质访问(接入)
  7. 物理层 Physical:网线,无线信号收发...传输电信号(二进制传输)

TCP/IP 模型:

  1. 应用层
  2. 传输层:解释数据(端)
  3. 网络层:定位IP,确定连接路径
  4. 网络接口:与硬件驱动对接

OSI

eg: Hello=>app/Hello=>tcp/app/Hello=>ip/tcp/app/Hello=>帧头/ip/tcp/app/Hello/帧尾

对等通讯:

  • 对等实体键虚拟通讯
  • 下层向上层提供服务,实际通讯在最底层完成

端口:

  • 在互联网上传输的数据都包含有用来识别目的地的IP地址和端口号
    • IP地址用来标识网络上的计算机
    • 端口号用来指明该计算机上的应用程序
  • 端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)
  • 应用程序通过系统调用与某端口建立连接(binding)
  • 传输层接收输出数据都通过该端口

Port

Socket 套接字:

  • 抽象概念(地址簇,网络程序设计接口,异步函数)
  • 是连接运行在网络上的两个程序间的双向通讯的端点
  • 使用Socket进行网络通信的过程(相当于电话机)

    • 基于TCP协议:

      • 服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户的连接请求(server app:bind socket on port)
      • 客户程序根据服务器程序所在的主机名和端口号发出连接请求(client:send connect request depends server host & ip)
      • 服务器接受连接请求,并获得一个新的绑定到不同端口地址的套接字(server app:accept connection and return a new socket which binded on a new port)
      • 客户和服务器通过读、写套接字进行通讯(client and server communication via the new socket) Port Port

        //Server
        ServerSocket ss = new ServerSocket(5000);
        //accept connection
        Socket socket = ss.accept();
        
        //send & receive msg
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();
        //...
        
        socket.close();
        
        //Client
        Socket socket = new Socket("127.0.0.1", 5000);
        
        //send and receive msg
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();
        //...
        
        socket.close();
        
    • 基于UDP协议: Port

        //Server:必须指定端口号
        DatagramSocket socket = new DatagramSocket(7000);
      
        //receive msg:接受消息(作用类似于TCP方式中accept方法,接受连接)
        byte[] buffer = new byte[1000];
        DatagramPacket receivePacket = new DatagramPacket(buffer, 1000);
        socket.receive(receivePacket);
      
        //send msg: 从接收到的数据包中可以获得客户端ip,port
        String str="send Message";
        DatagramPacket sendPacket = new DatagramPacket(str.getBytes(),
                str.length(), receivePacket.getAddress(), receivePacket.getPort());
        socket.send(sendPacket);
      
        socket.close();
      
        //Client: 使用系统随机分配的一个本地计算机的未用端口号,当然也可以指定端口号
        DatagramSocket socket = new DatagramSocket();
      
        //send msg
        String str="hello";
        DatagramPacket sendPacket = new DatagramPacket(str.getBytes(),
                str.length(), InetAddress.getByName("localhost"), 7000);
        socket.send(packet);
      
        //receive msg
        byte[] buffer = new byte[1000];
        DatagramPacket receivePacket = new DatagramPacket(buffer,1000);
        socket.receive(packet2);
      
        socket.close();
      

TCP & UDP:

TCP UDP
传输控制协议 用户数据报协议
面向连接,可靠 无连接,不可靠
顺序传输,无重复,无差错(有重传机制) 实时性较高
应用举例:下载安装程序 应用举例:视频会议
JDK Class: ServerSocket,Socket JDK Class: DategramSocket,DatagramPacket

TCP综合示例:

//Server端
public static void main(String[] args) throws Exception{
    ServerSocket serverSocket = new ServerSocket(4000);

    //可以不断接受新的Client端连接
    while(true){
        Socket socket = serverSocket.accept();
        new InputThread(socket).start();
        new OutputThread(socket).start();
    }
}
//Client端
public static void main(String[] args) throws Exception, IOException{
    Socket socket = new Socket("127.0.0.1", 4000);
    new InputThread(socket).start();
    new OutputThread(socket).start();
}
public class InputThread extends Thread
{
    private Socket socket;
    public InputThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run(){
        try{
            InputStream is = socket.getInputStream();
            while(true){
                byte[] buffer = new byte[1024];
                int length = is.read(buffer);
                String str = new String(buffer, 0, length);
                System.out.println(str);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

public class OutputThread extends Thread{
    private Socket socket;
    public OutputThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run(){
        try{
            OutputStream os = socket.getOutputStream();
            while(true){
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                String line = reader.readLine();
                os.write(line.getBytes());
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

使用URL访问网上资源:

URL url=new URL(location);

//方式一:
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();

//方式二:
InputStream is = url.openStream();

Servlet

是一个可以部署到webServer,可以被客户端访问调用的Java类(可以处理客户端请求)

与普通Java类的比较:

Servlet 普通Java类
extends HttpServlet extends Object
须在WebServer中才能运行 不需要WebServer支持也能运行
可以处理客户端请求 不能处理客户端请求
启动不需要main函数 启动需要main函数
由容器创建(new)实例 一般我们自己创建实例
由容器调用doGet或doPost方法 程序员自己调用

Servlet的等级继承结构:

  1. interface Servlet
    • init(servletConfig):调用一次,第一次访问或web程序启动(配置<load-on-startup>)时
    • service(request,response):可调用多次,每次访问时调用
    • destroy():调用一次,服务器或应用程序关闭时
  2. abstract class GenericServlet:与协议无关的Servlet
  3. abstract class HttpServlet:实现了Http协议的Servlet
  4. 自定义Servlet,步骤:
    1. extends HttpServlet
    2. @Override doGet & doPost
    3. web.xml中配置Servlet
      <servlet>
       <servlet-name>myServlet</servlet-name>
       <servlet-class>com.cj.MyServlet</servlet-class>
      </servlet> 
      <servlet-mapping>
       <servlet-name>myServlet</servlet-name>
       <url-pattern>/service/*</url-pattern>
       <url-pattern>/myservice/*</url-pattern>
      </servlet-mapping>
      

Servlet (Get请求)

WebServer

Servlet需运行在支持Servlet规范的WebServer上,由WebServer:

  1. 管理Servlet的生命周期(比如创建servlet 对象,调用servlet的init,service,destroy等方法);
  2. 生成request的实例

三种基本的Servlet引擎:

  • 大多数的JavaEE服务器(也称中间件服务器),如:
    • BEA WebLogic;
    • IBM WebSphere Application;
    • JBoss
  • 具有内置Servlet容器的Web服务器,如:
    • Tomcat
  • 使用Servlet引擎插件的产品(需要安装Web插件来集成Java运行时环境)
    • Apache Web服务器;
    • Microsoft IIS

Tomcat:

  • 是JSP/Servlet规范的一个实现;
  • 是WebServer的一种,它又称为Servlet引擎,web包容器。
  • 运行只需要JRE(本身是使用java 开发的)

JSP

JSP本质:Servlet (每个JSP都会转译为Servlet)

JSP (JSP执行过程)

第二次请求:不存在转译与编译的过程直接访问上次生成的class类,速度变快

JSP页面构成

Jsp页面=java+DHTML(html+css+js)

JSP Page

//jsp转换为的Servlet:
public final class 名_jsp extends ...{
    ...     // 使用<%! xxx %> 声明的方法或变量

    public void _jspService(request,response){
        ...     // 使用<% xxx %> 编写的Java Scriptlet
        out.print(...)    // 使用<%=xxx> 编写的Expression
        ...
    }
}

JSP内置对象

内置对象:又称隐式对象,由Web容器创建的一组类的实例(可直接在JSP页面中使用的对象)

4类9个:

  1. 输入输出

    • out:向客户端输出数据(eg: out.print("<br/>")
    • request:封装客户端请求
      • 获取页面提交数据:
        String value=request.getParameter(param);    //单值
        String[] values=request.getParameterValues(param);  //多值
        
      • 请求转发 (客户端为建立新的连接,地址栏URL不变,属于一次请求范围)
        request.getRequestDispather(url).forward(request, response);
        
    • response:响应客户端请求(向客户端输出信息)
      • 向客户端打印数据
        PrintWriter out = response.getWriter();
        out.println("<br/>");
        
      • 重定向(客户端URL地址改变,属于二次请求范围)
        reponse.sendRedirect(url);
        
    • 请求转发与重定向对比:
      请求转发 重定向
      是一次请求,只有一个request对象(所以数据不会丢失) 多次请求,生成多个request对象(所以数据会丢失)
      处理完,地址栏url路径不变 处理完,地址栏为最后一个url路径
      只能在一个web应用程序内部转发 可以到其他应用程序
      前后调用的方法类别相同 会变为get请求,与之前的没关系
  2. 作用域通讯级别(4个作用域),由低到高:

    • 页面级 pageContext :刷新时新页面
    • 请求级 request:除转发外新页面
    • 会话级 session :浏览器关闭新会话(HttpSession,跟踪同一浏览器发出的连续请求,一种会话跟踪)
    • 应用级 application
    • 操作:
      void setAttribute(String key,Object value);
      Object getAttribute(String key);
      void removeAttribute(String key);
      void invalidate();     //销毁
      
      //使用示例:
      session.setAttribute("Login_User",loginUser);    //以键值方式,在session中存放用户登录信息
      User user=(User)session.getAttribute("Login_User");    //获取session中存放的对象
      session.removeAttribute("Login_User");    //移除已登录用户
      session.invalidate();    //销毁会话
      
  3. Servlet对象
    • page
    • config
  4. 错误对象
    • exception
      <!-- 指定页面为错误页面 -->
      <%@ page ... isErrorPage="true" %>
      

PS: request中parameter & attribute

parameter attribute
客户端传过来的(client请求参数)数据 Server端放入的数据
只读 可读写
String getParamenter(String)
String[] getParameterValues(String)
Object getAttribute(String)
setAttribute(String,Object)
removeAttribute(String)

PS: ServletContext

  • 只有一份,单态类
  • 启动时创建,停止时消亡
  • 获取:
    • Jsp中,eg: request.getServletContext()
    • Servlet中,eg: this.getServletContext()

附录:Request其他常用方法

方法 说明
getParameterNames() 得到客户端所有请求参数名称的集合
getContextPath() 得到web应用程序名称,或者叫虚拟路径
getServletPath() 1,如果请求的是servlet,那么返回的该servlet在web.xml中配置的url-pattern;2,如果是jsp,返回jsp文件名。
getRequestURI() = request.getContextPath() + request.getServletPath();请求的相对路径
getRequestURL() 请求的全部路径,绝对路径
getMethod() 请求类别,默认是get请求,只有使用form表单提交才可以使用post请求
getRemotePort() 客户端请求端口
getRemoteAddr() 得到客户端的IP地址
getServerPort() 得到服务器端口号
getServerName() 得到服务器名称
getRealPath("/") 得到当前Web应用程序的绝对路径
getHeader(paramName) 得到客户端浏览器的信息
getHeaderNames() 得到所有请求头名称的Enumeration
getRequestDispatcher(url) 得到请求转发对象

附录: Session常用方法

方法名 返回值 定义
getId() String 获得当前Session的SessionID
isNew() boolean 判断Session是不是新创建的
getCreationTime() long 获得当前Session创建的时间
getLastAccessedTime() long 获得客户最后一次请求的时间
setMaxInactiveInterval(intinterval) void 设置Session最大请求间隔时间
getMaxInactiveInterval() int 获得Session最大请求间隔时间
setAttribute(String key,Objectvalue) void 向Session空间中存储对象
getAttribute(String key) Object 从Session中获取对应的对象
removeAttribute(String key) void 从Session中删除对应的对象
getAttributeNames Enumeration 返回session存储的key的集合
invalidate() void 强制Session过期

附录:HTTP响应状态码

状态码 说明
1xx 临时响应
2xx 成功
3xx 已重定向
4xx 请求错误
eg:
401 (未授权)
404 (未找到)
5xx 服务器错误

会话跟踪

解决同一时刻不同客户端会话私有数据保存问题

有两种实现:

  • Session

    • 服务器分配的保存客户端私有信息的一块内存空间
    • sessionId是Session的唯一标识
    • 服务器通过sessionId将客户端与Session数据对应起来
    • 跟踪实现机制:
      • Client第一次请求
        • Server端为此Client产生session对象,生成sessionId
        • 应答时传递sessionId
        • Client保存此sessionId
      • 同一Client再次发送请求:
        • 将上次得到的sessionId一同发出
        • Server端检查sessionId,取得对应session对象
      • sessionId保存方式(客户端)
        • Cookie (默认,可显示设置)
        • url重写 (客户端禁用cookie情况下使用,给每个超链接后增加请求参数,值为sessionId)
        • 隐藏表单域(不推荐)
  • Cookie

两种实现方式比较:

比较内容 Session Cookie
保存方式 数据内容保存在服务器端 数据内容保存在客户端
安全性 比较安全 相对不安全
生命周期 使用内存存放数据,当用户长时间未请求服务器或服务器重启,内容可能丢失 保存在客户端的内存或文件中,可以指定Cookie的生存周期
资源占用 服务器的内存 每次请求时发送Cookie内容,占用带宽
存放内容 可以存放各种数据类型的数据 只能存放字符串类型的数据

MVC

  • MVC演化史: JSP
  • MVC框架:Structs1,Struct2,SpringMVC
  • 事件驱动型框架:JSF(Java Server Face)