JAVA基础复习资料必备(二)

本文深入讲解了Java中的异常处理,包括异常的类型体系、处理方式、自定义异常和相关方法。接着,介绍了多线程的概念、实现方式、线程生命周期、Thread类的相关API以及volatile和synchronized关键字的使用。此外,还涉及了字符串对象的比较、可变字符序列StringBuilder和StringBuffer、网络编程中的IP、端口、协议以及Socket编程基础。
摘要由CSDN通过智能技术生成

第九章 异常

9.1 异常的类型的体系结构

1、异常系列的超父类:java.lang.Throwable
(1)只有它及它的子类对象才可以被JVM或throw语句抛出
(2)也只有它及它的子类对象才可以被catch捕获

2、Throwable分为两大派别
(1)Error:严重的错误,需要停下来重写设计
(2)Exception:一般的异常,可以通过判断检验避免,或者使用try…catch进行处理

3、Exception分为两大类
(1)RuntimeException:运行时异常,编译器不会提醒进行throws或try…catch进行处理,但运行时可能导致程序崩溃
(2)Exception:编译时异常,编译器强制性要求进行throws或try…catch进行处理,否则编译不通过,异常除了运行时异常以外都是编译时异常

4、常见的异常
(1)运行时异常:ArrayIndexOutOfBoundsException、RuntimeException、NullPointerException、ArithmeticException、ClassCaseException、NumberFormatException…
(2)编译时异常:SQLException、FileNotFoundsException、IOException…

9.2 异常的处理

1、使用try…catch…finally
语法格式

//形式一:try...catch
try{
    可能发生异常的代码
}catch(异常类型  异常名e){
    处理异常的代码(一般都是打印异常的信息的语句)
}catch(异常类型  异常名e){
    处理异常的代码(一般都是打印异常的信息的语句)
}。。。


//形式二:try...finally
try{
    可能发生异常的代码
}finally{
    无论try中是否有异常,也不管是不是有return,都要执行的部分
}

//形式三:try..catch..finally
try{
    可能发生异常的代码
}catch(异常类型  异常名e){
    处理异常的代码(一般都是打印异常的信息的语句)
}catch(异常类型  异常名e){
    处理异常的代码(一般都是打印异常的信息的语句)
}。。。
finally{
    无论try中是否有异常,也不管catch是否可以捕获异常,也不管trycatch中是不是有return,都要执行的部分
}

执行特点:
(1)如果try中代码没有异常,那么try中的代码正常执行,catch中代码不执行,finally中代码执行
(2)如果try中有异常,那么try异常后的代码将不会执行,找到对应的catch分支进行执行,finally也会执行
在这里插入图片描述
2、finally与return混合使用时
(1)如果finally中有return,一定是从finally中的return返回
(2)如果finally中没有return,finally中的语句会执行,但是不影响最终的返回值

3、使用throws抛出异常给调用者
语法格式

【修饰符】 返回值类型 方法名(【形参列表】) throws 异常列表{
}

9.3 手动抛出异常:throw

语法格式

throw 异常对象;

①throw抛出来的异常对象,和JVM抛出来的异常对象一样,也要用try…catch处理或者throws。
②如果是运行时异常,编译器不会强制要求你处理,如果是编译时异常,那么编译器会强制要求你处理。

9.4 自定义异常

1、必须继承Throwable或它的子类,大多数继承RuntimeException和Exception
(1)如果继承RuntimeException或者它的子类,那么自定义的异常就是运行时异常,编译器不会提醒你进行处理
(2)如果继承Exception,那么自定义异常就是编译时异常,编译器会强制你进行处理,否则编译不通过

2、建议保留父类的两个构造器

//无参构造
public 自定义异常名(){
    
}

//有参构造
public 自定义异常名(String message){
    super(message);
}

3、自定义异常对象,必须手动抛出,用throw抛出

9.5 关于异常的几个方法

(1)public String getMessage():只是获取异常的message信息
(2)public void printStackTrace():打印异常对象的详细信息,包括异常类型,message,堆栈跟踪信息。这个对于调试,或者日志跟踪是非常有用的

关于异常信息的打印:
①用System.err打印和用e.printStackTrace()都是会标记红色的突出。
②用System.out打印,当成普通信息打印。
③这两个打印是两个独立的线程,顺序是不能精确控制的。

第十章 多线程

10.1 相关的概念

1、程序:为实现一个功能,完成一个任务而选择一种编程语言编写的一组指令的集合
2、进程:程序的一次运行,进程是操作系统分配资源的最小单位,进程与进程之间的内存是独立的,无法直接共享
3、线程:
①线程是进程中的一条执行路径,一个程序至少有一个线程,也可以有多个线程,有的时候把线程称为轻量级进程
②同一个进程的多个线程之间有些内存是可以共享的(方法区,堆),也有些内存是独立的(栈,程序计数器)

4、并行:多个处理器同时执行多条执行路径
5、并发:多个任务同时进行,但是可能存在先后顺序关系

10.2 两种实现多线程的方式

1、继承Thread类
实现步骤:
(1)编写线程类,继承Thread类
(2)重写public void run(){]方法
(3)创建线程类对象
(4)调用start()

示例代码

class MyThread extends Thread {
    public void run(){
        //...
    }
}
class Test{
    public static void main(String[] args){
        MyThread my = new MyThread();
        my.start();//有名字的线程对象启动
        
        new MyThread().start();//匿名线程对象启动
        
        //匿名内部类的匿名对象启动
        new Thread(){
            public void run(){
                //...
            }
        }.start();
        
        //匿名内部类,但是通过父类的变量多态引用,启动线程
        Thread t =  new Thread(){
            public void run(){
                //...
            }
        };
        t.start();
    }
}

2、实现Runnable接口
实现步骤:
(1)编写线程类,实现Runnable接口
(2)重写public void run(){}方法
(3)创建线程类对象
(4)借助Thread类的对象启动线程

实例代码

class MyRunnable implements Runnable{
    public void run(){
        //...
    }
}
class Test {
    public static void main(String[] args){
        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        t1.start();
        t2.start();
        
        //两个匿名对象
        new Thread(new MyRunnable()).start();
        
        //匿名内部类的匿名对象作为实参直接传给Thread的构造器
        new Thread(new Runnable(){
            public void run(){
                //...
            }
        }).start();
            
    }
}

10.3 线程的生命周期

在这里插入图片描述

10.4 Thread的常用相关API

1、构造器
(1)public Thread()
(2)public Thread(Runnable target):target - 其 run 方法被调用的对象。
(3)public Thread(String name):name - 新线程的名称。
(4)public Thread(Runnable target,String name):target - 其 run 方法被调用的对象。name - 新线程的名称。
2、其他方法
(1)public void run()
(2)public void start()
(3)public static Thread currentThread():获取当前线程对象
(4)public String get/setName():获取/设置当前线程名称
(5)public final int ge/settPriority():获取/设置当前线程优先级(优先级的范围:[1,10],Thread类中有三个常量:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5),优先级只是影响概率。)
(6)public void interrupt():打断当前线程
(7)public static void sleep(long millis):线程休眠(millis:毫秒值)
(8)public staitc void yield():暂停当前正在执行的线程对象,并执行其他线程。
(9)public final void join():等待该线程终止。 (xx.join()这句代码写在哪个线程体中,哪个线程被加塞,和其他线程无关。)
(10)public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

10.5 volatile

1、概念:易变,不稳定,不一定什么时候会变
2、修饰:成员
3、作用:当多个线程同时去访问的某个成员变量时,而且是频繁的访问,再多次访问时,发现它的值没有修改,Java执行引擎就会对这个成员变量的值进行缓存。一旦缓存之后,这个时候如果有一个线程把这个成员变量的值修改了,Jav执行引擎还是从缓存中读取,导致这个值不是最新的。如果不希望Java执行引擎把这个成员变的值缓存起来,那么就可以在成员变量的前面加volatile,每次用到这个成员变量时,都是从主存中读取。

10.6 synchronized

1、以下情况会发生线程安全问题
(1)多个线程
(2)共享数据
(3)多个线程的线程体中,多条语句再操作这个共享数据时

2、使用同步锁解决线程安全问题
(1)同步代码块
锁对象:
①任意类型的对象
②确保使用共享数据的这几个线程,使用同一个锁对象

synchronized(锁对象){
    //一次任务代码,这其中的代码,在执行过程中,不希望其他线程插一脚
}

(2)同步方法
锁对象:
①非静态方法:this(谨慎)
②静态方法:当前类的Class对象

synchronized 【修饰符】 返回值类型  方法名(【形参列表】)throws 异常列表{
    //同一时间,只能有一个线程能进来运行
}

10.7 线程通信

1、概念:为解决生产者和消费者问题。当一些线程负责往“数据缓冲区”放数据,另一个线程负责从“数据缓冲区”取数据。
2、条件
(1)生产者线程与消费者线程使用同一个数据缓冲区,就是共享数据,那么要考虑同步
(2)当数据缓冲区满的时候,生产者线程需要wait(), 当消费者消费了数据后,需要notify或notifyAll
(3)当数据缓冲区空的时候,消费者线程需要wait(), 当生产者生产了数据后,需要notify或notifyAll

3、线程通信锁相关的方法
(1)wait():必须由“同步锁”对象调用
(2)notfiy()和notifyAll():必须由“同步锁”对象调用

4、sleep()和wait的区别
(1)sleep()不释放锁,wait()释放锁
(2)sleep()在Thread类中声明的,wait()在Object类中声明的
(3)sleep()是静态的,wait()是非静态的
(4)sleep()导致线程阻塞后需要通过interrupt()或时间到醒来,wait()导致线程阻塞后需要通过notify()或notifyAll()唤醒;

5、释放锁的操作
(1)同步代码块或同步方法正常执行完一次会自动释放锁
(2)同步代码块或同步方法遇到return提前结束
(3)wait()

第十一章 常用类

11.1 包装类

1、概念:当要使用只针对对象设计的api或者新特性(例如泛型),那么基本数据类型就需要用到包装类来包装

序号基本数据类型包装类
1byteByte
2shortShort
3intInteger
4longLong
5floatFloat
6doubleDouble
7charCharacter
8booleanBoolean
9voidVoid

2、装箱与拆卸(JDK1.5之后,可以自动装箱与拆箱)
(1)装箱:把基本数据类型转为包装类对象(转为包装类对象,是为了使用专门为对象设计的api和特新)
(2)拆箱:把包装类对象拆为基本数据类型(转为基本数据类型,一般是因为要运算)

11.1.1 包装类常用的api

1、基本数据类型和字符串之间的转换

(1)把基本数据类型转为字符串

//方式一:
String str = a + "";
//方式二:
String str = String.valueOf(a);

(2)把字符串转为基本数据类型

int a = Integer.parseInt("整数的字符串");
double a = Double.parseDouble("小数的字符串");
boolean b = Boolean.parseBoolean("true或false");

2、数据类型的最大最小值

Integer.MAX_VALUE和Integer.MIN_VALUE
Long.MAX_VALUE和Long.MIN_VALUE
Double.MAX_VALUE和Double.MIN_VALUE

3、转大小写

Character.toUpperCase('x');
Character.toLowerCase('X');

4、转进制

Integer.toBinaryString(int i) 
Integer.toHexString(int i)
Integer.toOctalString(int i)
11.1.2 包装类对象的缓存问题
包装类缓存对象
Byte-128~127
Short-128~127
Integer-128~127
Long-128~127
Float没有
Double没有
Character0~127
Booleantrue和false

11.2 字符串

11.2.1 字符串的特点

1、字符串String类型本身是final声明的,所以不能被继承
2、字符串的对象也是不可变的,所以一旦修改,就会产生新的对象

我们修改了字符串后,如果想要获得新的内容,必须重新接受。
如果程序中涉及到大量的字符串的修改操作,那么此时的时空消耗比较高。可能需要考虑使用StringBuilder或StringBuffer。

3、String对象内部是用数组进行保存的

JDK1.9之前有一个char[] value数组,JDK1.9之后byte[]数组

4、String类中的char[] values数组也是final修饰的,意味着这个数组不可变,且是private修饰的,外界不可以直接操作它,String类提供的所有的方法都需要用新对象来接收修改后的内容,所有保证了String对象的不可变

5、因为String对象设计为不可变,所以字符串有常量池来保持很多常量对象

常量池所在的区域
(1)JDK1.6及以前:方法区
(2)JDK1.7:堆
(3)JDK1.8:元空间

11.2.2 字符串对象的比较

1、==:比较是对象的地址,只有两个字符串对象指向同一个字符串常量对象才会返回true

2、equals:比较对象的内容,因为String类型重写了equals,区分大小写,只要两个字符串的内容相同,就会返回true

3、equalsIgnoreCase:比较的是对象的内容,不区分大小写

4、compareTo:String类型实现了Comparable接口中的抽象方法,自然排序,按照字符的Unicode编码值进行比较大小,严格区分大小写

5、compareToIgnoreCase:不区分大小写,其他按照字符的Unicode编码值进行比较大小

11.2.3 空字符的比较

1、空字符串的表示方式

String str1 = “”;
String str2 = new String();
String str3 = new String("");

2、判断空字符串

if("".equals(str))
if(str != null && str .isEmpty())
if(str != null && str.equals(""))
if(str != null && str.length() == 0)

11.2.4 字符串的对象的个数

1、字符串常量对象(一个对象,常量池中)

String str = “hello”

2、字符串的普通对象(两个对象,一个是常量池中,一个是堆中)

String str = new String("");

3、字符串的普通对象和常量对象一起(首先指向堆中的一个字符串对象,然后堆中字符串的value数组指向常量池中常量对象的value数组)

String str = new String(“hello”)

11.2.5 字符串拼接结果

原则:
(1)常量 +常量:结果在常量池
(2)常量+变量/变量 + 变量:结果在堆
(3)拼接后调用intern():结果在常量池

11.2.6 字符串常用API

(1)public char charAt(int index):返回指定索引处的 char 值。索引范围为从 0 到 length() - 1。
(2)public String concat(String str):将指定字符串连接到此字符串的结尾。
(3)public String replace(xx,xxx):返回一个新的字符串,它是通过用 xxx替换此字符串中出现的所有 xx 得到的。
(4)public boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
(5)public boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true。
(6) public String replaceFirst(String regex,String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
(7) public String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
(8)public String[] split(String regex,int limit):根据匹配给定的正则表达式来拆分此字符串。
(9)public boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束。
(10)public boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始。
(11)public int indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引。
(12)public int lastIndexOf(int ch):返回指定字符在此字符串中最后一次出现处的索引。
(13)public String substring(int beginIndex,int endIndex):返回一个新的字符串,它是此字符串的一个子字符串。该子字符串从指定索引处的字符开始,直到此字符串末尾。
(14)public String trim():返回字符串的副本,忽略前导空白和尾部空白。
(15)public String toLowerCase():使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
(16)public String toUpperCase():使用默认语言环境的规则将此 String 中的所有字符都转换为大写
(17)public byte[] getBytes():使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
(18)public boolean equals(Object anObject):将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。
(19)public int length():返回此字符串的长度。长度等于字符串中 Unicode 代码单元的数量。

11.3 可变字符序列

1、可变字符序列:StringBuilder和StringBuffer
StringBuilder:线程不安全
StringBuffer:线程安全

2、String和StringBuffer、StringBuilder的区别
String:不可变对象,不可变字符序列
StringBuffer、StringBuilder:可变字符序列

3、常用的API(StringBuilder、StringBuffer的API是完全一致的)
(1)public StringBuffer append(xxx xx):将xx参数的字符串表示形式追加到序列。
(2)public StringBuffer insert(int index,xxx xx):将xx参数的字符串表示形式插入此序列中。
(3)public StringBuffer reverse():将此字符序列用其反转形式取代
(4)public StringBuffer delete(int start,int end):移除此序列的子字符串中的字符。该子字符串从指定的 start 处开始,一直到索引 end - 1 处的字符
(5)public void setCharAt(int index,char ch):将给定索引处的字符设置为 ch。此序列将被转换,以表示等同于原字符序列的新字符序列,唯一的不同在于新序列在 index 处包含 ch。

11.4 和数学相关的

1、java.lang.Math类
(1)public static double sqrt(xxx xx):返回正确舍入的 xx值的正平方根
(2)public static double abs(xxx xx):返回xx值的绝对值
(3)public static xxx max(xxx xx,xxx xx):返回两个xxx值中较大的一个
(4)public static xxx min(xxx xx,xxx xx):返回两个xxx值中较小的一个
(5)public static double random():返回带正号的double值,该值大于等于0.0且小于1.0
(6)public static double ceil(double a):进一
(7)public static double floor(double a):退一

2、java.math包
(1)BigInteger:大整数
(2)BigDecimal:大小数、
运算通过方法完成:add(),subtract(),multiply(),divide()…

11.5 日期时间API

11.5.1 JDK1.8之前

1、java.util.Date
(1)new Date():当前系统时间、
(2)new Date(long 毫秒):把该毫秒值换算成日期时间对象
(3)public long getTime():获取当前系统时间距离1970年1月1日00:00:00的毫秒值

2、java.util.Calendar:
(1)public static Calendar getInstance():获取Calender的实例对象
(2)get(常量)

3、java.text.SimpleDateFormat:日期时间的格式化
y:表示年
M:月
d:天
H: 小时,24小时制
h:小时,12小时制
m:分
s:秒
S:毫秒
E:星期
D:年当中的天数

	@Test
	public void test10() throws ParseException{
		String str = "2019年06月06日 16时03分14秒 545毫秒  星期四 +0800";
		SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒  E Z");
		Date d = sf.parse(str);
		System.out.println(d);
	}
	
	@Test
	public void test9(){
		Date d = new Date();

		SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒  E Z");
		//把Date日期转成字符串,按照指定的格式转
		String str = sf.format(d);
		System.out.println(str);
	}
	
	@Test
	public void test8(){
		String[] all = TimeZone.getAvailableIDs();
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i]);
		}
	}
	
	@Test
	public void test7(){
		TimeZone t = TimeZone.getTimeZone("America/Los_Angeles");
		
		//getInstance(TimeZone zone)
		Calendar c = Calendar.getInstance(t);
		System.out.println(c);
	}
	
	@Test
	public void test6(){
		Calendar c = Calendar.getInstance();
		System.out.println(c);
		
		int year = c.get(Calendar.YEAR);
		System.out.println(year);
		
		int month = c.get(Calendar.MONTH)+1;
		System.out.println(month);
		
		//...
	}
	
	@Test
	public void test5(){
		long time = Long.MAX_VALUE;
		Date d = new Date(time);
		System.out.println(d);
	}
	
	@Test
	public void test4(){
		long time = 1559807047979L;
		Date d = new Date(time);
		System.out.println(d);
	}
	@Test
	public void test3(){
		Date d = new Date();
		long time = d.getTime();
		System.out.println(time);//1559807047979
	}
	
	@Test
	public void test2(){
		long time = System.currentTimeMillis();
		System.out.println(time);//1559806982971
		//当前系统时间距离1970-1-1 0:0:0 0毫秒的时间差,毫秒为单位
	}
	
	@Test
	public void test1(){
		Date d = new Date();
		System.out.println(d);
	}
11.5.2 JDK1.8之后

java.time及其子类包
1、LocalTime、LocalDateTime、LocalDate
(1)now():获取当前系统时间;
(2)of(xxx):指定日期或者时间
(3)运算:运算后得到新对象,需要重新接受
① plusXxx():在当前日期或时间对象上加xx
② minusXxx() :在当前日期或时间对象上减xx

方法描述
now() / now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear()获得月份(1-12) /获得年份
getHours()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
with(TemporalAdjuster t)将当前日期时间设置为校对器指定的日期时间
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时
plus(TemporalAmount t)/minus(TemporalAmount t)添加或减少一个 Duration 或 Period
isBefore()/isAfter()比较两个 LocalDate
isLeapYear()判断是否是闰年(在LocalDate类中声明)
format(DateTimeFormatter t)格式化本地日期、时间,返回一个字符串
parse(Charsequence text)将指定格式的字符串解析为日期、时间

2、DateTimeFormatter:日期时间格式化
三种格式化方法:
(1)预定义的标准格式。如:ISO_DATE_TIME;ISO_DATE
(2)本地化相关的格式。如:ofLocalizedDate(FormatStyle.MEDIUM)
(3)自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

	@Test
	public void test10(){
		LocalDateTime now = LocalDateTime.now();
		
//		DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);//2019年6月6日 下午04时40分03秒
		DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);//19-6-6 下午4:40
		String str = df.format(now);
		System.out.println(str);
	}
	@Test
	public void test9(){
		LocalDateTime now = LocalDateTime.now();
		
		DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;//2019-06-06T16:38:23.756
		String str = df.format(now);
		System.out.println(str);
	}
	
	@Test
	public void test8(){
		LocalDateTime now = LocalDateTime.now();
		
		DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒  SSS毫秒  E 是这一年的D天");
		String str = df.format(now);
		System.out.println(str);
	}
	
	@Test
	public void test7(){
		LocalDate now = LocalDate.now();
		LocalDate before = now.minusDays(100);
		System.out.println(before);//2019-02-26
	}
	
	@Test
	public void test06(){
		LocalDate lai = LocalDate.of(2019, 5, 13);
		LocalDate go = lai.plusDays(160);
		System.out.println(go);//2019-10-20
	}
	
	@Test
	public void test05(){
		LocalDate lai = LocalDate.of(2019, 5, 13);
		System.out.println(lai.getDayOfYear());
	}
	
	
	@Test
	public void test04(){
		LocalDate lai = LocalDate.of(2019, 5, 13);
		System.out.println(lai);
	}
	
	@Test
	public void test03(){
		LocalDateTime now = LocalDateTime.now();
		System.out.println(now);
	}
	
	@Test
	public void test02(){
		LocalTime now = LocalTime.now();
		System.out.println(now);
	}
	
	@Test
	public void test01(){
		LocalDate now = LocalDate.now();
		System.out.println(now);
	}

第十二章 集合

1、概念
数据结构:存储数据的某种结构
(1)层的物理结构
  ①数组:开辟连续的存储空间,每个元素使用[下标]进行区分
  ②链表:不需要开辟连续的存储空间,但是需要“结点”来包装要存储的数据
结点的组成内容:
  A、数据
  B、记录其他结点的地址,例如next、pre、left、right、parent等
(2)表现出来的逻辑结构:动态数组、单向链表、双向链表、队列、栈、二叉树、哈希表、图等

12.1 手动实现逻辑结构

1、动态数组
(1)内部使用一个数组来存储数据
(2)使用一个total来记录实际存储元素的个数

示例代码

import java.util.Arrays;

public class MyArrayList {

	private Object[] date;// 底层存储数据的数组
	private int total;// 实际记录存储个数

	// 创建对象时初始化数组
	public MyArrayList() {
		date = new Object[5];
	}

	// 判断是否需要扩容
	public void checkCapacity() {
		if (date.length <= total) {
			date = Arrays.copyOf(date, date.length * 2);
		}
	}

	// 检查下标是否合法
	public void checkIndex(int index) {
		if (index < 0 || index >= total) {
			throw new ArrayIndexOutOfBoundsException(index + "对应位置的元素不存在");
		}
	}

	// 添加数据
	public void add(Object obj) {
		checkCapacity();
		date[total++] = obj;
	}

	// 根据下标插入数据
	public void insert(int index, Object obj) {
		checkIndex(index);
		checkCapacity();
		System.arraycopy(date, index, date, index + 1, total - index);
		date[index] = obj;
		total++;
	}

	// 根据下标删除数据
	public void remove(int index) {
		checkIndex(index);
		System.arraycopy(date, index + 1, date, index, total - index - 1);
		date[total - 1] = null;
		total--;
	}

	// 查找元素的下标
	public int indexOf(Object obj) {
		if (obj == null) {
			for (int i = 0; i < total; i++) {
				if (date[i] == obj) {
					return i;
				}
			}
		} else {
			for (int i = 0; i < total; i++) {
				if (date[i].equals(obj)) {
					return i;
				}
			}
		}
		return -1;
	}

	// 根据数据进行删除
	public void remove(Object obj) {
		int index = indexOf(obj);
		if (index != -1) {
			remove(index);
		}
	}

	// 根据下标对数据进行修改
	public void set(int index, Object obj) {
		checkIndex(index);
		date[index] = obj;
	}

	// 根据元素对数据进行修改
	public void set(Object old, Object value) {
		int index = indexOf(old);
		if (index != -1) {
			set(index, value);
		}
	}

	// 查询单个元素
	public Object get(int index) {
		checkIndex(index);
		return date[index];
	}

	// 查询实际存储的所有元素
	public Object[] getAll() {
		return Arrays.copyOf(date, total);
	}

	// 返回数组的实际容量
	public int length() {
		return date.length;
	}

	// 返回实际存储的元素个数
	public int size() {
		return total;
	}
}

2、单向链表
(1)包含一个Node类型的成员变量first:用来记录第一个结点的地址,如果这个链表是空的,没有任何结点,那么first是null,最后一个和结点的特征:next是null
(2)内部使用一个total,记录实际存储的元素的个数
(3)使用一个内部类Node

示例代码

public class SingleLinkedList {

	private Node first;// 记录第一个结点
	private int total;// 实际元素存储个数

	// 内部类实现:因为结点在外部不需要使用,这里声明为内部类
	class Node {
		Object date;// 数据可以为任意类型
		Node next;// 下一个结点的地址,所以是结点类型

		public Node(Object date, Node next) {
			super();
			this.date = date;
			this.next = next;
		}
	}

	// 添加新结点
	public void add(Object obj) {
		Node newNode = new Node(obj, null); // 假设当前添加的是最后一个结点
		if (first == null) {// 如果first为空,那么说明Node内没有元素
			first = newNode;// 说明添加的元素为第一个结点
		} else {
			Node node = first;// 若不是第一个,找到当前结点的最后一个,然后链接到最后一个结点的next中
			while (node.next != null) {
				node = node.next;
			}
			node.next = newNode;
		}
		total++;// 实际存储元素个数+1
	}

	// 删除结点
	public void remove(Object obj) {
		if (obj == null) {// 如果当前删除的结点的而数据值为null,这里是使用等号判断,比较地址值
			if (first != null) {// 判断第一个结点非空,如果非空表示存在结点
				if (first.date == null) {// 若第一个结点的数据值为null
					first = first.next;// 将第一个结点指向下一个结点
					total--;// 实际存储元素个数减一
					return;
				}

				Node node = first.next;// 用一个变量记录第一个结点的下一个结点的地址
				Node last = first;// 用一个变量记录第一个结点的地址
				while (node.next != null) {// 通过循环找出除最后一个结点的所有结点
					if (node.date == null) {// 若当前结点的数据为null
						last.next = node.next;// 将当前结点的上一个结点指向下一个结点
						total--;// 实际存储元素个数减一
						return;
					}
					last = node;// 当前结点指向下一个结点
					node = node.next;// 下一个结点指向下一个结点的下一个结点
				}

				if (node.date == null) {// 执行到当前,当前结点指向最后一个结点,若最后一个结点的数据值为null
					last.next = null;// 删除当前结点
					total--;// 实际存储元素个数减一
					return;
				}
			}
		} else {
			if (first != null) {
				if (obj.equals(first.date)) {
					first = first.next;
					total--;
					return;
				}
			}

			Node node = first.next;
			Node last = first;
			while (node.next != null) {
				if (obj.equals(node.date)) {
					last.next = node.next;
					total--;
					return;
				}
				last = node;
				node = node.next;
			}

			if (obj.equals(node.date)) {
				last.next = null;
				total--;
				return;
			}
		}
	}

	// 根据元素获取当前元素的结点的位置
	public int indexOf(Object obj) {
		if (obj == null) {// 若当前元素为null
			Node node = first;// 设置一个新变量接收第一个结点的地址
			for (int i = 0; i < total; i++) {// 循环遍历
				if (node.date == obj) {// 若当前结点的数据值==obj返回i;
					return i;
				}
				node = node.next;// 当前结点指向下一个结点
			}
		} else {
			Node node = first;
			for (int i = 0; i < total; i++) {
				if (obj.equals(node.date)) {
					return i;
				}
				node = node.next;
			}
		}
		return -1;
	}

	// 获取实际存储元素的个数
	public int size() {
		return total;
	}

	// 获取所有的元素
	public Object[] getAll() {
		Object[] arr = new Object[total];//创建一个和实际存储个数一样的数组
		Node node = first;
		for (int i = 0; i < total; i++) {
			arr[i] = node.date;
			node = node.next;
		}
		return arr;
	}
}

12.2 Collection

概念:集合的类型有很多种,那么我们把他们称为集合框架
集合框架分为两大家族:Collection和Map

1、Collection:代表一种对象的集合,它是Collection系列的根接口。

2、常用方法
(1)boolean add(E e):添加一个元素
(2)boolean addAll(Collection<? extends E> c):添加多个元素
(3)int size():返回实际存储的元素个数
(4)boolean isEmpty():是否为空
(5)boolean contains(Object o):是否包含某个元素
(6)boolean containsAll(Collection<?> c):是否所有
(7)Iterator iterator():获取当前集合的迭代器对象
(8)boolean remove(Object o):删除某个元素
(9)boolean removeAll(Collection<?> c):删除多个元素
(10)Object[] toArray():获取所有元素
(11)void clear():清空集合
(12)retainAll(Collection c):求当前集合与c集合的交集

12.2.1 Collection系列集合遍历的方法

1、使用iterator迭代器遍历
Iterator接口的方法
(1)boolean hasNext():是否有下一个元素
(2)Object next():获取下一个元素
(3)void remove():移除返回的最后一个元素,每次调用 next 只能调用一次此方法。

示例代码

Collection c = Collection及子类;
Iterator iterator = c.iterator();
while(iterator.hasNext()){		
	System.out.println(iterator.next());
	//....
}

2、使用foreach

示例代码

Collection c = Collection及子类;
for(Object obj : c){
	//....
}

以下集合或容器可以使用foreach进行遍历
(1)数组
(2)实现了java.lang.Iterable接口
Iterator中的抽象方法:Iterator iterator(): 返回一个在一组 T 类型的元素上进行迭代的迭代器。
Iterator也是一个接口,它的实现类通常在集合(容器)类中使用内部类实现。并在Iterator()的方法中创建它的对象。

import java.util.Iterator;

public class MyIterable implements Iterable<Object> {//实现Iterable接口,重写iterator方法

	private Object[] date;//创建存储数据的数组
	private int total;//记录存储个数

	@Override
	public Iterator<Object> iterator() {
		return new MyIterator();//返回一个内部类对象
	}

	private class MyIterator implements Iterator<Object>{
		private int cursor;//记录迭代器中存储的对象

		@Override
		public boolean hasNext() {//是否有下一个元素
			return cursor!=total;
		}

		@Override
		public Object next() {//返回下一个元素
			return date[cursor++];
		}
	}
} 
12.2.2 遍历的选择

遍历数组:foreach和for的选择
(1)当操作设计到下标时,使用for循环
(2)只是查看元素内容,使用foreach更简洁

遍历Collection系列集合:foreach和for的选择
(1)首先考虑使用foreach,如果该集合有索引信息的话可以使用for循环操作
(2)如果该集合的物理结构是数组,那么可以使用for
(3)如果物理结构是链式,那么使用下标操作效率很低

遍历Collection系列集合:foreach和Iterator的选择
(1)如果只是查看集合的元素,使用foreach更加简洁
(2)如果涉及到遍历集合的同时根据某个条件删除元素等操作,选择Iterator

12.3 List

12.3.1 List概念

(1)List:是Collection的子接口
(2)List系列的集合:有序,可重复
(3)List系列的常用集合:ArrayList、Vector、LinkedList、Stack

12.3.2 List常用API

1、常用方法:
(1)boolean add(Object obj):添加一个元素
(2)boolean addAll(Collection c):添加多个元素
(3)void add(int index,Object obj):按照指定位置添加元素
(4)void addAll(int index,Collection c):按照指定位置添加多个元素
(5)boolean remove(Object obj):删除一个元素
(6)Object remove(int index):删除指定位置的元素并返回删除的元素
(7)boolean removeAll(Collection c):删除多个元素
(8)boolean contains(Object obj):是否包含某个元素
(9)boolean containsAll(Collection c):是否包含所有元素
(10)boolean isEmpty():是否为空
(11)int size():获取元素个数
(12)void clear():清空集合
(13)Object[] toArray():获取所有元素
(14)Iterator iterator(): 获取遍历当前集合的迭代器对象
(15)retainAll(Collection c):求当前集合与c集合的交集
(16)ListIterator listIterator():获取遍历当前集合的迭代器对象,这个迭代器可以往前、往后遍历
(17)ListIterator listIterator(int index):从[index]位置开始,往前或往后遍历
(18)Object get(int index):返回index位置的元素
(19)List subList(int start, int end):截取[start,end)部分的子列表

12.3.3 ListIterator接口

1、Iteratoe接口的方法:
(1)boolean hasNext():是否有下一个元素
(2)Object next():返回下一个元素
(3)void remove():删除当前末尾元素

2、ListIterator是Iterator子接口:
(4)void add(Object obj):添加元素
(5)void set(Object obj):替换元素
(6)boolean hasPrevious():是否有上一个元素
(7)Object previous():返回上一个元素
(8)int nextIndex():返回对 next 的后续调用所返回元素的索引
(9)int previousIndex():返回对 previous 的后续调用所返回元素的索引

12.3.4 List实现类们的区别

(1)ArrayList、Vector:都是动态数组
  ①Vector是最早版本的动态数组,线程安全,默认的扩容机制是2倍,支持旧版的迭代器Enumeration
  ②ArrayList是后增的动态数组,线程不安全,默认的扩容机制是1.5倍
(2)动态数组和LinkedList的区别
  ①动态数组:底层物理结构是数组
   优点:根据下标访问的速度很快
   缺点:需要开辟连续的存储空间,一旦确认其长度不能在其基础上进行操作,而且需要扩容,移动元素等操作
   ②LinkedList:底层物理结构是双向链表
  优点:在对元素进行操作时,不需要移动元素,只需要修改前后元素的引用关系
  缺点:查找元素时,只能从first或last开始查找
(3)Stack:Vector的子类,比Vector多几个方法,能够表示先进后出或者后进先出的特点
   ①public E push(E item):把元素压入栈顶
   ②public E pop():移除堆栈顶元素
   ③public E peek():访问堆栈顶元素
(4)LinkedList可以作为很多数据结构使用
单向链表:只关注next
队列:先进先出
双端队列(JDK1.6加入):两头都可以进出
栈:先进后出,后进先出

建议:虽然LinkedList是支持对索引进行操作,因为它实现List接口的所有方法,但是不建议调用类似的方法,因为效率比较低

12.4 Set

12.4.1 Set概念

1、Set系列的集合:不可重复
2、Set系列的集合:有有序的也无序的
(1)HashSet:无序的
(2)TreeSet:按照元素的大小顺序遍历
(3)LinkedHashSet:按照元素的添加顺序遍历

12.4.2 实现类的特点

(1)HashSet:底层是HashMap实现,添加到hashSet的元素作为hashMap的key,value是Object类型的常量对象PRESENT
  依赖元素的hashCode()和equals()保证元素的不可重复,存储位置和hashCode()值有关,根据hashCode()来计算它在底层table数组中的[index]
  
(2)TreeSet:底层是TreeMap实现,添加到TreeSet的元素作为TreeMap的key,value是Object类型的常量对象PRESENT
  依赖于元素的大小,通过java.lang.Comparable接口CompareTo(Object obj)或java.util.Comparator接口Compare(Object o1,Object 02)来比较元素的大小,认为大小相同的两个元素就是重复的元素
 
(3)LinkedHashSet:底层是LinkedHashMap实现,添加到LinkedHashSet的元素作为LinkedHashMap的key,value是Object类型的常量对象PRESENT
  HashSet的子类,比HashSet多维护了添加顺序

12.5 Map

12.5.1 Map概念

1、Map表示一对映射关系(键值对)的集合,所有的Map集合的key都不可以重复

2、映射关系类型:Entry
Entry接口是Map接口的内部接口。所有的Map的键值对的类型都实现了这个接口。

12.5.2 Map常用API

(1)int size():获取有效映射关系的对数
(2)boolean isEmpty():判断此映射中是否包含映射关系
(3)boolean containsKey(Object key):是否包含某个Key值
(4)boolean containsValue(Object value):是否包好某个value值
(5)V get(Object key):根据key获取value值
(6)V put(K key,V value):添加一对映射关系
(7)V remove(Object key):删除一对映射关系
(8)void putAll(Map<? extends K,? extends V> m):添加多对映射关系
(9)void clear():清空映射中的所有映射关系
(10)Set keySet():获取此映射关系中所有的key值
(11)Collection values():获取此映射关系中所有的value值
(12)Set<Map.Entry<K,V>> entrySet():获取此映射中所有的映射关系

12.5.3 Map实现类们的区别

1、hashMap:通过key的hashCode()和equals()来保证key是否重复,如果key重复,那么新value值会代替旧value值
  hashCode()决定了映射关系在table数组中的存储位置,index=hash(key.hashCode())&table.length-1
  hashMap的底层实现:
  ①JDK1.7:数组+链表
  ②JDK1.8:数组+链表/红黑树
  
2、TreeMap:通过key的大小来保证key是否重复,如果key重复那么新的value会替换旧的value
  key的大小依赖于java.lang.Comparable或java.util.Comparator

3、LinkedHashMap:HashMap的子类,通过key的hashCode()和equals()来保证key是否重复,如果key重复,那么新value值会代替旧value值,比HashMap多了添加顺序

12.5.4 HashMap的源码分析
12.5.4.1 JDK1.7

1、put(key,value)
(1)当第一次添加映射关系时,数组初始化为一个长度为16的HashMap&Entry的数组,这个HashMap&Entry类型实现了java.util.Map.Entry接口
(2)特殊考虑:如果key为null,index直接是[0]
(3)在计算index之前会对key的hashCode()值,做一个hash(key)再次hash的运算,这样可以使得Entry对象更加散列的存储到table中
(4)计算index = table.length-1 & hash
(5)如果table[index]下面,已经有映射关系的key与我要添加的新映射关系的key相同时,会用新的value值替换旧的value
(6)如果没有相同的,会把新的映射关系添加到链表的头,原来table[index]下面的Entry对象连接到新的映射关系的next中
(7)添加之前,先判断是否需要扩容
  if(size >= threshold && table[index] != null){
①扩容
②重新计算key的hash
③重新计算index  
}

2、get(key)
(1)计算key的hash值,用这个方法的hash(key)
(2)找index = table.length-1 & hash
(3)如果table[index]不为空,那么就依次比较Entry中的key,若相同,返回对应的value

3、remove(key)
(1)计算key的hash值,用这个方法的hash(key)
(2)找index = table.length-1 & hash
(3)如果table[index]不为空,那么就一次比较Entry中key,若相同直接删除此映射关系,把它前面的Entry的next的值修改为被删除Entry的next

12.5.4.2 JDK1.8

1、put(key,value)
(1)当第一次添加映射关系时,数组初始化为一个长度为16的HashMap&Node的数组,这个HashMap&Node数组实现了java.util.Map.Entry接口
(2)在计算index之前,会对hashCode()值,做一个hash(key)再次hash运算,这样可以使得Entry对象更加散列的存储到table中

JDK1.8关于hash(key)方法的实现比JDK1.7要简洁: key.hashCode() ^ key.Code()>>>16

(3)计算index = table.length-1 & hash
(4)如果table[index]下面,已经有映射关系的key与我要添加的新的映射关系的key相同了,会用新的value替换旧的value。
(5)如果没有相同的,
  ①table[index]链表的长度没有达到8个,会把新的映射关系添加到链表的尾
  ②table[index]链表的长度达到8个,但是table.length没有达到64,会先对table进行扩容,然后再添加
  ③table[index]链表的长度达到8个,并且table.length达到64,会先把该分支进行树化,结点的类型变为TreeNode,然后把链表转为一棵红黑树
  ④table[index]本来就已经是红黑树了,那么直接连接到树中,可能还会考虑考虑左旋右旋以保证树的平衡问题
(6)添加完成后判断if(size > threshold ){
​ ①会扩容
​ ②会重新计算key的hash
​ ③会重新计算index
​ }
2、remove(key)
(1)计算key的hash值,用这个方法hash(key)
(2)找index = table.length-1 & hash;
(3)如果table[index]不为空,那么就挨个比较哪个Entry的key与它相同,就删除它,把它前面的Entry的next的值修改为被删除Entry的next
(4)如果table[index]下面原来是红黑树,结点删除后,个数小于等于6,会把红黑树变为链表

12.6 框架集关系图

在这里插入图片描述

第十三章 泛型

13.1 泛型概念

1、泛型:参数化类型
2、类型形参:< T >,< K >,< V >,< E >,< U >…
3、类型实参:< String >,< Integer >,< Student >,< ArrayList< String > >…

泛型实参必须是引用数据类型,不能是基本数据类型

13.2 泛型类和泛型接口

1、声明语法格式

【修饰符】 class 类名<类型形参列表>{

}
【修饰符】 class 类名<类型形参 extends 父类上限>{

}
【修饰符】 class 类名<类型形参 extends 父类上限 & 父接口上限>{
    
}
【修饰符】 interface 接口名<类型形参列表>{

}

在类名或接口后面声明泛型形参类型,可以在当前类或接口中使用,用作声明成员变量,方法的形参,方法的返回值
但是不能用于静态成员上

2、使用语法格式
(1)创建泛型类、泛型接口的对象时,为泛型形参指定具体类型
(2)在继承泛型类或实现泛型类接口时,为泛型形参指定具体类型

3、泛型如果没有指定,会被擦拭,按照最左边的上限处理,如果没有指定上限,按照Object处理

13.3 泛型方法

1、声明语法格式

【修饰符】 <泛型类型形参> 返回值类型 方法名(【形参列表】)throws 异常列表】{

}
【修饰符】 <泛型形参 extends 父类上限 & 父接口上限> 返回值类型 方法名(【形参列表】)throws 形参列表】{
}

(1)在方法返回值类型前面声明的泛型形参类型,只能在当前方法中使用,用于表示形参的类型或返回值类型,或方法局部变量的类型,和别的方法无关。
(2)泛型方法可以是静态方法,也可以是非静态方法

2、使用语法格式:当调用方法,会根据具体的数据的实参的类型,来确定泛型实参的类型。

13.4 通配符

(1)?:表示任意引用数据类型
(2)?extends 上限:代表上限本身及其子类
(3)?super 下限:代表下线本身及其父类

13.5 Collections 工具类

(1)public static boolean addAll(Collection<? super T> c,T… elements):添加elements元素到c中,T是elements对象的类型,要求Collection集合的元素类型必须是T或者T的父类
(2)public static int binarySearch(List<? extends Comparable<? super T>> list,T key) :在list集合中用二分查找key的下标,要求集合实现Comparable接口
(3)public static boolean disjoint(Collection<?> c1, Collection<?> c2):判断c1和c2没有交集就为true
(4)public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll):求coll集合中最大元素,要求T或T的父类实现Comparable接口
(5)public static <T extends Comparable<? super T>> void sort(List list) :给list集合排序 ,要求T或T的父类实现Comparable接口
(6)public static Collection synchronizedCollection(Collection c):以synchronizedXX开头的方法,表示把某种非线程安全集合转为一个线程安全的集合。
(7)public static List unmodifiableList(List<? extends T> list):以unmodifiableXx开头的方法,表示返回一个“只读”的集合。

第十四章 IO流

14.1 java.io.File类

常用API
(1)getName():获取文件或目录的名称
(2)getPath():获取文件或目录的路径
(3)getAbsolutePath():获取文件或目录的绝对路径(4)getCanontcalPath():获取文件或目录的相对路径
(5)long length():获取文件的长度(字节)
(6)long lastModified():最后的修改时间(毫秒)
(7)String getParent():获取上级或父目录
(8)File getPatentFile():获取上级或父目录
(9)isFile():判断是否是文件
(10)isDirectory():判断是否是目录
(11)exists():是否存在
(12)isHidden():是否隐藏
(13)canWirte():是否可写
(14)canRead():是否可读
(15)String[] list():获取下一级
(16) File[] listFiles():获取下一级
(17) File[] listFiles(FileFilter f):获取下一级,但是会过滤掉一下文件和目录
(18)CreateNewFile():创建新文件
(19)CreateTempFile(String prefix,String suffix):在默认临时文件目录中创建一个空文件,prefix文件前缀字符串,suffix后缀字符串
(20)mkdir():创建一级目录,如果父目录不存在,就失败,但是不报异常
(21)mkdirs():创建多级目录
(22)delete():删除文件或空目录
(23)renameTo(File f):重命名文件或目录

14.2 IO流的四大抽象基类

1、四大超类
(1)InputStream:字节输入流
(2)OutputStream:字节输出流
(3)Reader:字符输入流
(4)Writer:字符输出流

2、分类
(1)按照方向分类:输出流,输入流
(2)按照处理方式分类:字符流,字节流
(3)按照角色不同:节点流,处理流

处理流是建立在节点流的基础上的,处理流需要包装一个节点流的对象。

3、常用API
InputStream
(1)int read():一次读取一个字节,返回本次读取的字节值,流末尾返回-1
(2)int read(byte[] date):一次读取多个字节,读取的数据存储到date字节数组中,最多读取date.length个字节,返回实际读取字节的个数,流末尾返回-1
(3)int read(byte[]date,int offset,int len):一次读取多个字节,读取的数据存储到date字节数组中,从date[offset]开始存储,最多读取len个字节,返回的是实际本次读取的字节的个数,流末尾返回-1
(4)void close():关闭

OutputStream
(1)void writer():一次写入一个字节
(2)void writer(byte[] date):一次写入一个字节数组
(3)void writer(byte[] date,int offset,int len):一次写入一个字节数组,从date[offset]开始写,写入len个字节
(4)void close():关闭
(5)void flush():刷新

Reader
(1)void read():一次读取一个字符,返回本次读取的字符的Unicode值,流末尾返回-1
(2)void rear(char[] date):一次读取多个字符,读取的字符存储到date字符数组中,最多读取date.length个字符,返回实际读取的字符个数,流末返回-1
(3)int read(char[] data,int offset, int len):一次读取多个字符,读取的数据存储到data字符数组中,最多读取len个字符,返回的是实际本次读取的字符的个数,如果流末尾返回-1。从data[offset]开始存储。
(4)void close():关闭

Writer
(1)void writer():一次写入一个字符
(2)void write(char[] data):一次写整个字符数组
(3)void write(char[] data, int offset, int len):一次字符数组的一部分,从[offset]开始,一共len个
(4)void write(String str):一次写整个字符串
(5)void write(String str, int offset, int count):一次写字符串的一部分,从[offset]开始,一共count个
(6)void close():关闭
(7)void flush():刷新

14.3 文件IO流

类型
(1)FileInputStream:文件字节输入流,可以读取任意数据类型的文件
(2)FileOutputStream:文件字节输出流,可以将字节数据输出到任意类型的文件
(3)FileReader:文件字符输入流,只能读取纯文本文件,按照平台默认的字符编码进行解码
(4)FileWriter:文件字符输出流,苦于将字符数据输出到纯文本文件,按照平台默认字符编码进行编码

14.4 缓冲IO流

1、类型
(1)BufferedInputStream:字节输入缓冲流,给InputStream系列IO流增加缓冲效果
(2)BufferedOutputStream:字节输出缓冲流,给OutputStream系列IO流增加缓冲效果
(3)BufferedReader:字符输入缓冲流,给Reader系列IO流增加缓冲效果
(4)BufferWriter:字符输出缓冲流,给Writer系列IO流增加缓冲效果
2、默认的缓冲区大小是8192 = 1024 * 8(字节/字符)
3、缓冲IO流的作用:可以给读写的过程提高效率,不仅仅是可以给文件IO流增加缓冲效果,可以给任意符合对应类型的IO流增加缓冲效果。

14.5 转换IO流

1、OutputStreamWriter:可以把字符流转为字节流输出,并且在转换的过程中,可以指定字符编码。

2、InputStreamReader:可以把字节输入流转为字符输入流,并且在转换的过程中,可以指定字符编码。

示例代码

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import org.junit.Test;

public class Test01 {

	// 数据流向:内存 --> osw (字符流)-->在写入fos过程中进行编码-->fos(字节流)-->文件
	// OutputStreamWriter
	@Test
	public void test01() throws IOException {
		String date = "File类可以使用文件路径字符串来创建File实例";
		FileOutputStream fos = new FileOutputStream("src/com/exer/test/File概述.txt");
		OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
		osw.write(date);
		osw.close();
		fos.close();
	}

	// 文件-->fis(字节流)-->解码-->isr(字符流)-->br ->读取的是字符
	// InputStreamReader
	@Test
	public void test02() throws IOException {
		FileInputStream fis = new FileInputStream("src/com/exer/test/File概述.txt");
		InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
		BufferedReader br = new BufferedReader(isr);
		String line;
		while ((line = br.readLine()) != null) {
			System.out.println(line);
		}
		br.close();
		isr.close();
		fis.close();
	}
}

14.6 数据IO流

1、类型
(1)DateInputStream:允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
(2)DataOutputStream:允许应用程序以适当方式将基本 Java 数据类型写入输出流中。

IO流要匹配使用且读写顺序一致

2、常用API

DataOutputStreamDataInputStream
writeInt(xx):输出int类型整数int readInt()
writeDouble(xx):输出double类型double readDouble()
writeBoolean(xx):输出boolean类型boolean readBoolean()
writeLong(xx):输出long类型long readLong()
writeChar(xx):输出char类型char readChar()
writeByte(xx):输出byte类型byte readByte()
writeShort(xx):输出short类型short readShort()
writeFloat(xx):输出float类型float readFloat()
writeUTF(String str):输出字符串的String readUTF()

14.7 对象IO流

1、类型
(1)ObjectInputStream:对象序列化,输出对象,把对象转为字节序列输出

(2)ObjectOutputStream:对象反序列化,读取对象,把字节序列化重构成Java对象

实例代码

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.junit.Test;

public class Test01 {
@Test
	public void test03() throws IOException {
		String string = new String("hello");
		FileOutputStream fos = new FileOutputStream("src/com/exer/test/对象序列化.dat");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(string);
		oos.close();
		fos.close();
	}

	@Test
	public void test04() throws IOException, ClassNotFoundException {
		FileInputStream fis = new FileInputStream("src/com/exer/test/对象序列化.dat");
		ObjectInputStream ois = new ObjectInputStream(fis);
		System.out.println(ois.readObject());
		ois.close();
		fis.close();
	}
}

2、常用API
ObjectOutputStream:writeObject(对象)
ObjectInputStream:Object readObject()

3、序列化需要注意哪些内容?
(1)所有要序列化的对象的类型都必须实现java.io.Serializable接口

如果对象的属性类型也是引用数据类型,那么也要实现java.io.Serializable接口

(2)希望类的修改对象反序列化不产生影响,那么我们最后能够增加一个序列化版本ID

private static final long serialVersionUID = 1L;

(3)如果有些属性不想要序列化,可以加transient
(4)如果某个属性前面有static修饰,也不参与序列化
4、除了Serializable接口之外,还可以实现java.io.Externalizable接口,但是要求重写:
①void readExternal(ObjectInput in)
②void writeExternal(ObjectOutput out)

14.8 其他的IO流相关内容

1、如果要实现按行读取,可选择什么类型?
(1)BufferedReader:String readLine()
(2)Scanner:String nextLine()
2、如果要按行输出,可以选择什么类型?
(1)\r\n
(2)BufferedWriter:newLine()
(3)PrintStream和PrintWriter:println()

14.9 IO流try…catch

语法格式:

try(需要关闭的资源对象的声明){
    业务逻辑代码
}catch(异常类型 e){
    处理异常代码
}catch(异常类型 e){
    处理异常代码
}
....

它没有finally,也不需要程序员去关闭资源对象,无论是否发生异常,都会关闭资源对象

第十五章 网络编程

学习它为了更好的理解:web(服务器端和客户端的通信)、数据库(服务器端和客户端的数据传输)等原理。

15.1 主机IP

1、值的表示
(1)IPV4:32位整数,8位一组,用.分割,例如:192.168.xx.xx(每个8位的范围[0,255])
(2)IPV6:128位整数,16位一组,用:分割,例如:X:X:X:X:X:X:X:X(每个16位用十六进制值表示)

2、对象表示
(1)Inet4Address:对应IPV4
(2)Inet6Address:对应IPV6

3、常用API
(1)public void InetAddress getLocalHost(): 返回本地主机。
(2)public static inetAddress getByAddress(byte[] addr): 在给定原始 IP 地址的情况下,返回 InetAddress 对象。
(3)public static InetAddress getByName(String host):根据提供的主机名和 IP 地址创建 InetAddress。
(4)public String getHostAddress():返回IP地址字符串
(5)public String getHostName():获取IP地址主机名
(6)public String getCanonicalHostName():获取此IP地址的完全限定域名
(7)public boolean isReachable(int timeout):测试是否可以达到该地址

15.2 端口号

1、取值范围[0,65535]
2、常见端口号
  tomcat/JBoss:8080
  http:80
  mysql:3306
  oracle:1521
  sql server:1433

15.3 网络协议

  • 应用层:网络服务与最终用户的一个接口。协议有:HTTP、FTP、SMTP、DNS、TELNET、HTTPS、POP3等等。
  • 表示层:数据的表示、安全、压缩。格式有:JPEG、ASCll、DECOIC、加密格式等。
  • 会话层:建立、管理、终止会话。对应主机进程,指本地主机与远程主机正在进行的会话
  • 传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP、UDP。
  • 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。协议有:ICMP、IGMP、IP(IPV4 IPV6)、ARP、RARP。
  • 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
  • 物理层:建立、维护、断开物理连接。

IP协议是一种非常重要的协议。IP(internet protocal)又称为互联网协议。IP的责任就是把数据从源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。经常与IP协议放在一起的还有TCP(Transmission Control Protocol)协议,即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。而通常我们说的TCP/IP协议,其实是指TCP/IP协议族,因为该协议家族的两个最核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准,所以简称为TCP/IP协议。

分类:
(1)TCP:Transmission Control Protocol 传输控制协议
(1)面向连接的:连接之前有三次握手,断开时四次挥手
(2)可靠的
(3)基于字节流
(4)可以传输大量数据

(2)UDP:User Datagram Protocol 用户数据报协议
(1)非面向连接的
(2)不可靠的
(3)基于数据报的
(4)数据量大小有限制的64K

15.4 Socket编程

1、概念
  Socket:套接字,代表网络通信的一端,负责和网卡驱动程序沟通的对象。

2、分类
(1)ServerSocket和Socket:流套接字
(2)DatagramSocket:数据套接字

3、常用API

ServerSocket的常用构造方法和其他方法:
(1)ServerSocket(int port) :指定在某个端口号监听客户端的连接和通信
(2)Socket accept() :监听和接收客户端的连接
(3)void close() :关闭

Socket类的常用构造方法:
(1) public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
(2)public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号。

Socket类的常用方法:
(1)public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
(2) public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消息
(3)public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
(4) public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。
(5) public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
(6) public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。

注意:先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。

DatagramSocket常用构造方法和其他方法:
(1)public DatagramSocket(int port):指定端口
(2)public DatagramSocket(int port, InetAddress laddr):指定端口和IP地址
(3)public void send(DatagramPacket p):从此套接字发送数据报包
(4)public void receive(DatagramPacket p):从此套接字接收数据报包

DatagramPacket常用构造方法和其他方法:
(1)DatagramPacket(byte[] buf, int length) : 构造 DatagramPacket,用来接收长度为 length 的数据包。
(2)DatagramPacket(byte[] buf, int length, InetAddress address, int port) :构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
(3)public byte[] getData():用来接收或发送数据的缓冲区
(4)public int getLength():返回将要发送或接收到的数据的长度。

15.5 TCP协议

15.5.1 编程示例一

一个客户端连接服务器,连接成功后,服务器给客户端发送“欢迎你登录"

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {
		
		//开启服务器,监听端口为9999的数据
		ServerSocket server = new ServerSocket(9999);

		//等待客户端连接,执行一次就代表有一个客户连接了该服务器
		Socket socket = server.accept();
		System.out.println("连接成功");

		//获取输出流
		OutputStream os = socket.getOutputStream();
		
		//发送数据,字符串转为字节数组
		os.write("欢迎您登录".getBytes());

		//断开连接
		socket.close();
		//关闭服务器
		server.close();
	
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client{
	public static void main(String[] args) throws UnknownHostException, IOException {
		//连接服务器
		Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);
		
		//获取输入流
		InputStream is = socket.getInputStream();
		//转换流,字节流转字符流
		InputStreamReader isr = new InputStreamReader(is);
		//缓冲流,可以按行接收数据
		BufferedReader br = new BufferedReader(isr);
		String str;
		while ((str = br.readLine()) != null) {
			System.out.println(str);
		}

		//断开连接
		socket.close();

	}
}
15.5.2 编程示例二

一个客户端连接服务器,连接成功后,客户端给服务器先传一个“你好”,服务器给客户端返回“欢迎你登录"

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {

		// 开启服务器,监听端口为9999的数据
		ServerSocket server = new ServerSocket(9999);

		// 等待客户端连接,执行一次就代表有一个客户连接了该服务器
		Socket socket = server.accept();
		System.out.println("连接成功");

		// 获取输入流
		InputStream is = socket.getInputStream();

		// 用一个数组存储接收的数据
		byte[] data = new byte[1024];
		// 实际接收数据的字节个数
		int length;
		while ((length = is.read(data)) != -1) {
			System.out.println(new String(data, 0, length));
		}

		// 获取输出流
		OutputStream os = socket.getOutputStream();

		// 发送数据,字符串转为字节数组
		os.write("欢迎您登录".getBytes());
		
		// 断开连接
		socket.close();
		// 关闭服务器
		server.close();
	}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		//连接服务器
		Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);
		
		//获取输出流
		OutputStream os = socket.getOutputStream();
		//发送数据
		os.write("你好".getBytes());
		
		//禁用此套接字的输出流
		socket.shutdownOutput();
		
		System.out.println("客户端收到:");
		//获取输入流
		InputStream is = socket.getInputStream();
		
		byte[] data = new byte[1024];
		int len;
		while ((len = is.read(data)) != -1) {
			System.out.println(new String(data,0,len));
		}

		//断开连接
		socket.close();
	}
}
15.5.3 编程示例三

一个客户端连接服务器,连接成功后:
(1)客户端从键盘输入词语,给服务器发送,直到bye为止;
(2)服务器每次手动词语,反转词语 ,然后返回给客户端,直到接收到bye为止

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {

		// 开启服务器,监听9999端口的数据
		ServerSocket server = new ServerSocket(9999);

		// 获取连接服务器的客户端
		Socket socket = server.accept();

		// 获取输入流并包装
		InputStream is = socket.getInputStream();
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader br = new BufferedReader(isr);

		// 获取输出流并包装
		OutputStream os = socket.getOutputStream();
		PrintStream ps = new PrintStream(os);

		//定一个变量接收获取的内容
		String data;
		
		//按行获取
		while ((data = br.readLine()) != null) {
			
			//若是bye直接退出
			if ("bye".equals(data)) {
				break;
			}
			
			//使用可变字符序列将获取的内容进行反转
			StringBuilder sb = new StringBuilder(data);
			sb.reverse();
			ps.println(sb.toString());
		}
		socket.close();
		server.close();
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {

		//连接服务器
		Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);

		//从键盘输入
		Scanner input = new Scanner(System.in);

		//获取输出流并包装
		OutputStream os = socket.getOutputStream();
		PrintStream ps = new PrintStream(os);

		//获取输入流并包装
		InputStream is = socket.getInputStream();
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader br = new BufferedReader(isr);

		while (true) {
			System.out.print("请输入要反转的内容:");
			String data = input.next();
			
			//打印当前输入的内容
			ps.println(data);
			
			//如果是bye退出循环
			if ("bye".equals(data)) {
				System.out.println("bye");
				break;
			}

			//获取服务器反转后的内容
			System.out.println("服务器返回的反转后的结果:" + br.readLine());

		}
		input.close();
		socket.close();
	}
}
15.5.4 编程示例四

多个客户端同时连接服务器,连接成功后:
(1)客户端从键盘输入词语,给服务器发送,直到bye为止;
(2)服务器每次手动词语,反转词语 ,然后返回给客户端,直到接收到bye为止

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {

		try (
			// 开启服务器,监听9999端口的数据
			ServerSocket server = new ServerSocket(9999);
		) {
			while (true) {
				// 获取连接服务器的客户端
				Socket socket = server.accept();
				ClientConnect clinet = new ClientConnect(socket);
				clinet.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}

class ClientConnect extends Thread {
	
	//接收独立的客户端
	private Socket socket;

	public ClientConnect (Socket socket) {
		super();
		this.socket = socket;
	}

	@Override
	public void run() {

		try {
			// 获取输入流
			InputStream is = socket.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);

			// 获取输出流
			OutputStream os = socket.getOutputStream();
			PrintStream ps;
			ps = new PrintStream(os);

			// 定一个变量接收获取的内容
			String data;

			// 按行获取
			while ((data = br.readLine()) != null) {

				// 若是bye直接退出
				if ("bye".equals(data)) {
					break;
				}

				// 使用可变字符序列将获取的内容进行反转
				StringBuilder sb = new StringBuilder(data);
				sb.reverse();
				ps.println(sb.toString());
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {

		//连接服务器
		Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);

		//从键盘输入
		Scanner input = new Scanner(System.in);

		//获取输出流并包装
		OutputStream os = socket.getOutputStream();
		PrintStream ps = new PrintStream(os);

		//获取输入流并包装
		InputStream is = socket.getInputStream();
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader br = new BufferedReader(isr);

		while (true) {
			System.out.print("请输入要反转的内容:");
			String data = input.next();
			
			//打印当前输入的内容
			ps.println(data);
			
			//如果是bye退出循环
			if ("bye".equals(data)) {
				System.out.println("bye");
				break;
			}

			//获取服务器反转后的内容
			System.out.println("服务器返回的反转后的结果:" + br.readLine());

		}
		input.close();
		socket.close();
	}
}
15.5.5 编程示例五

一个客户端连接服务器,连接成功后,给服务器上传一个文件,服务器接收到文件后存到upload的文件夹中。

import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {

		// 开启服务器
		ServerSocket server = new ServerSocket(9999);

		// 获取客户端连接
		Socket socket = server.accept();

		// 获取输入流及数据输入流
		InputStream is = socket.getInputStream();
		DataInputStream dis = new DataInputStream(is);

		// 获取文件名.后缀
		String fileName = dis.readUTF();

		// 获取.的下标
		int index = fileName.lastIndexOf(".");

		// 获取文件名
		String name = fileName.substring(0, index);

		// 获取后缀名
		String ext = fileName.substring(index);

		// 对文件名进行处理 时间戳
		long currentTimeMillis = System.currentTimeMillis();

		// 新文件名
		String newFileName = name + currentTimeMillis + ext;

		// 判断文件夹是否存在,不存在则新建一个文件夹
		File file = new File("upload");
		if (!file.exists() && !file.isDirectory()) {
			file.mkdir();
		}
		// 创建文件输出流
		FileOutputStream fos = new FileOutputStream(file + "/" + newFileName);

		byte[] data = new byte[1024];
		int len;
		while ((len = dis.read(data)) != -1) {
			fos.write(data, 0, len);
		}

		//发送反馈
		OutputStream os = socket.getOutputStream();
		PrintStream ps = new PrintStream(os);
		ps.println("接收完毕");

		fos.close();
		socket.close();
		server.close();
	}
}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {

		// 连接服务器
		Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);

		//键盘获取
		Scanner input = new Scanner(System.in);

		// 输入要发送的文件的路径
		System.out.println("请选择要发送文件的全路径:");
		String url = input.nextLine();
		File file = new File(url);

		// 获取文件名.后缀
		String FileName = file.getName();

		// 发送文件名.后缀
		OutputStream os = socket.getOutputStream();
		DataOutputStream dos = new DataOutputStream(os);
		dos.writeUTF(FileName);

		// 获取文件内容,发送文件内容
		FileInputStream fis = new FileInputStream(file);
		byte[] data = new byte[1024];
		int len;
		while ((len = fis.read(data)) != -1) {
			dos.write(data, 0, len);
		}

		//文件发送完毕关闭输出流
		socket.shutdownOutput();

		//获取发送反馈
		InputStream is = socket.getInputStream();
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader br = new BufferedReader(isr);
		System.out.println(br.readLine());

		fis.close();
		input.close();
		socket.close();
	}
}
15.5.6 编程示例六

多个客户端连接服务器,连接成功后,给服务器上传一个文件,服务器接收到文件后存到upload的文件夹中。

import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

//多个客户端连接服务器,连接成功后,给服务器上传一个文件,服务器接收到文件后存到upload的文件夹中。
public class Server {
	public static void main(String[] args) {

		try (ServerSocket server = new ServerSocket(9999);) {
			while (true) {
				Socket socket = server.accept();
				ClientConnect client = new ClientConnect(socket);
				client.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

class ClientConnect extends Thread {

	private Socket socket;

	public ClientConnect(Socket socket) {
		super();
		this.socket = socket;
	}

	@Override
	public void run() {
		try (
				// 获取输入流
				InputStream is = socket.getInputStream();
				// 键盘输入
				) {

			// 获取文件名.后缀
			DataInputStream dis = new DataInputStream(is);
			String fileName = dis.readUTF();

			// 获取时间戳
			long currentTimeMillis = System.currentTimeMillis();

			// 获取.的下标
			int index = fileName.lastIndexOf(".");

			// 获取文件名
			String name = fileName.substring(0, index);

			// 获取文件后缀
			String ext = fileName.substring(index);

			// 处理后的文件名
			String newFileName = name + currentTimeMillis + ext;

			Scanner input = new Scanner(System.in);
			System.out.print("存储的文件路径:");
			String path = input.nextLine();
			File file = new File(path);
			if (!file.exists() && !file.isDirectory()) {
				file.mkdirs();
			}
			FileOutputStream fis = new FileOutputStream(file + "/" + newFileName);

			byte[] data = new byte[1024];
			int len;
			while ((len = is.read(data)) != -1) {
				fis.write(data, 0, len);
			}

			// 获取输出流
			OutputStream os = socket.getOutputStream();
			PrintStream ps = new PrintStream(os);
			ps.println("接收完毕");

			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client {
	public static void main(String[] args) {
		try (
				// 连接服务器
				Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);

				// 键盘输入
				Scanner input = new Scanner(System.in);
		) {
			System.out.print("请输入要传输文件的路径:");
			File file = new File(input.nextLine());

			// 获取文件名.后缀
			String fileName = file.getName();

			// 使用数据流输出文件名
			OutputStream os = socket.getOutputStream();
			DataOutputStream dos = new DataOutputStream(os);
			dos.writeUTF(fileName);

			// 文件输入流 读取文件内容
			FileInputStream fis = new FileInputStream(file);
			byte[] data = new byte[1024];
			int len;
			while ((len = fis.read(data)) != -1) {
				os.write(data, 0, len);
			}

			socket.shutdownOutput();
			
			// 文件输入流 获取反馈
			InputStream is = socket.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			System.out.println(br.readLine());

			fis.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
15.5.7 编程示例七

群聊

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;

public class Server {

	// 创建一个ArrayList用来管理记录客户端
	private static ArrayList<Socket> onLine = new ArrayList<Socket>();

	public static void main(String[] args) throws IOException {

		// 开启服务器
		ServerSocket server = new ServerSocket(9999);

		// 一直等待客户端连接
		while (true) {
			// 获取一个用户
			Socket socket = server.accept();

			// 将连接的客户端添加到ArrayList中
			onLine.add(socket);

			// 开启线程
			MessageDispose md = new MessageDispose(socket);
			md.start();
		}

	}

	// 使用内部类的方式,可以访问外部类的成员
	private static class MessageDispose extends Thread {

		private Socket socket;
		private String ip;

		// 传值
		public MessageDispose(Socket socket) {
			super();
			this.socket = socket;
			this.ip = socket.getInetAddress().getHostAddress();
		}

		@Override
		public void run() {
			try {
				// 一个客户端连接,则告诉其他客户端该客户端上线了
				SendToOthers(ip + "上线了");

				// 获取输入流和包装输入流
				InputStream is = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(is);
				BufferedReader br = new BufferedReader(isr);

				// 使用一个变量接收内容
				String content;
				while ((content = br.readLine()) != null) {
					// 若为bye,则给当前对象发送bye,并结束循环
					if ("bye".equals(content)) {
						OutputStream os = socket.getOutputStream();
						PrintStream ps = new PrintStream(os);
						ps.println("bye");
						break;
					}
					// 转发给其他在线的客户端
					SendToOthers(ip + ":" + content);
				}
				SendToOthers(ip + "下线了");
			} catch (IOException e) {
				SendToOthers(ip + "掉线了");
			}
		}

		public void SendToOthers(String str) {
			// 获取ArrayList的迭代器对象,通过迭代器对象获取ArrayList中所有的Socket对象
			Iterator<Socket> iterator = onLine.iterator();
			while (iterator.hasNext()) {
				Socket on = iterator.next();
				// 若当前对象和迭代器中的Socekt不同,则进行发送信息
				if (!on.equals(socket)) {
					try {
						OutputStream os = on.getOutputStream();
						PrintStream ps = new PrintStream(os);
						ps.println(str);
					} catch (IOException e) {
						// 若发生异常说明该客户端掉线或者下线
						iterator.remove();
					}
				}
			}
		}
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Clinet {
	public static void main(String[] args) throws UnknownHostException, IOException {

		// 连接服务器
		Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 9999);

		// 创建发送信息和接收信息的线程
		sendTread st = new sendTread(socket);
		ReceiveThread rt = new ReceiveThread(socket);

		// 启动线程
		st.start();
		rt.start();

		// 等待发送线程执行完毕
		try {
			st.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 关闭接收数据
		rt.setFlag(false);

		// 等待接收线程执行完毕
		try {
			rt.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 断开客户端
		socket.close();

	}
}

class sendTread extends Thread {
	private Socket socket;

	// 传值
	public sendTread(Socket socket) {
		super();
		this.socket = socket;
	}

	@Override
	public void run() {
		try {
			// 键盘输入和发送内容
			Scanner input = new Scanner(System.in);
			OutputStream os = socket.getOutputStream();
			PrintStream ps = new PrintStream(os);

			// 一直发送,直到遇到bye结束发送
			while (true) {
				System.out.print("请输入你要发送的内容:");
				String content = input.nextLine();
				ps.println(content);
				if ("bye".equals(content)) {
					break;
				}
			}

			// 关闭输入流
			input.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

class ReceiveThread extends Thread {
	private Socket socket;
	// 使用一个变量来操作是否继续接收内容
	private boolean flag = true;

	public ReceiveThread(Socket socket) {
		super();
		this.socket = socket;
	}

	@Override
	public void run() {
		try {
			// 获取输入流和包装输入流
			InputStream is = socket.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);

			// 接收内容,若为bye,结束循环,或者flag为false也结束循环(当发送线程结束时)
			while (flag) {
				String content = br.readLine();
				System.out.println(content);
				if ("bye".equals(content)) {
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

15.6 UDP协议

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Send {
	public static void main(String[] args) throws IOException {

		//创建发送方数据套接字
		DatagramSocket socket = new DatagramSocket();

		//发送的内容
		String str = "我好像在哪见过你";

		//创建一个数据包对象指定端口号,自动获取ip
		DatagramPacket packet = new DatagramPacket(str.getBytes(), str.getBytes().length, InetAddress.getLocalHost(),9999);

		//发送数据包
		socket.send(packet);
		System.out.println("发送完毕");

		//关闭连接
		socket.close();
	}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Receive {
	public static void main(String[] args) throws IOException {
		
		//创建接收方数据包套接字
		DatagramSocket socket = new DatagramSocket(9999);

		//接收数据的容量
		byte[] data = new byte[1024];
		
		//数据包
		DatagramPacket packet = new DatagramPacket(data, data.length);
		
		//接收数据
		socket.receive(packet);
		
		//获取实际接收数据
		byte[] bs = packet.getData();
		
		//实际接收数据的长度
		int len = packet.getLength();
		System.out.println(new String(bs,0,len));
		
		System.out.println("接收完毕");
		
		//关闭连接
		socket.close();
	}
}

第十六章 反射

16.1 类加载

1、类在内存中的生命周期:加载–>使用–>卸载
2、类的加载:
(1)加载
(2)连接
  ①校验
  ②准备:准备对应的内存(方法区),创建class对象,为类的变量赋予默认值,为静态常量赋予初始值
  ③解析:把字节码中的符号引用替换为对应的直接地址引用
(3)初始化:大多数情况下,类加载就完成了类初始化

3、会导致类初始化
(1)主方法所在的类会先初始化
(2)第一次使用某个类型(也就是new这个类型的对象),若这个类没有类初始化,那么先完成类初始化,再完成实例初始化
(3)调用某个类的静态成员,此时这个类没有初始化,那么先完成类初始化
(4)子类初始化时,发现它的父类没有初始化,会先初始化它的父类
(5)通过反射操作某个类时,若这个类没有初始化,也会导致该类初始化

4、不会导致类初始化
(1)使用某个类的静态常量(static final)
(2)子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化
(3)创建对象数组

5、类加载器
(1)引导类加载器:负责加载jre/rt.jar核心库,它本身不是java代码实现的,也不是classLoader的子类,获取它的对象时往往会返回null
(2)扩展类加载器:负责加载jre/lib/ext扩展库,是classLoader的子类
(3)应用程序类加载器:负责加载项目的classpath路径下的类,是classLoader的子类
(4)自定义类加载器:当你的程序需要加载特定目录下的类,可以定义一个自定义类加载器,例如tomcat

6、双亲委托模式:当接到任务时,会从下一级的类加载器开始依次往上一级上传,并进行匹配是否加载,若已经在某一个类加载器中匹配成功,直接返回Class对象,若直根加载器中没有匹配成功,那么会往回传,若最后一级没有匹配成功,则会报ClassNotFoundException或NoClassDefError

16.2 Class

1、概念:Class对象是反射的根源
2、所有的类型都可以获取Class对象
3、获取Class对象的方式
(1)类型名.class(要求编译期间是已知类型)
(2)对象名.getClass() (只能获取已经存在的对象的运行时类型)
(3)Class.forName(类型全名称) (可以获取编译期间未知的类型)
(4)ClassLoader的类加载器对象.loadClass(类型全名称) (可以使用自定义类加载器对象加载指定路径下的类型)

16.3 反射的运用

16.3.1 获取类型的详细信息

示例代码

public class LoadBean {
	private String name;
	public char gender;
	int age;
	boolean married;

	public LoadBean() {
		super();
	}
	
	public LoadBean(String name, char gender, int age) {
		super();
		this.name = name;
		this.gender = gender;
		this.age = age;
	}

	public LoadBean(String name, char gender, int age, boolean married) {
		super();
		this.name = name;
		this.gender = gender;
		this.age = age;
		this.married = married;
	}

	public String getName() {
		return name;
	}

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

	public char getGender() {
		return gender;
	}

	public void setGender(char gender) {
		this.gender = gender;
	}

	public int getAge() {
		return age;
	}

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

	public boolean isMarried() {
		return married;
	}

	public void setMarried(boolean married) {
		this.married = married;
	}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;

public class ClassInfo {
	public static void main(String[] args) throws ClassNotFoundException {

		/*
		 * 获取class对象 4选1
		 */
		// 方法一
		// Class<LoadBean> clazz = LoadBean.class;

		// 方法二
		// LoadBean loadBean = new LoadBean();
		// Class<? extends LoadBean> clazz = loadBean.getClass();

		// 方法三:需要捕获异常
		Class<?> clazz = Class.forName("com.exer.test.LoadBean");

		// 方法四
		// Class<?> clazz =
		// ClassInfo.class.getClassLoader().loadClass("com.exer.test.LoadBean");

		// 获取包信息
		System.out.println("包名:" + clazz.getPackage().getName());

		// 获取类的信息
		System.out.println("类的权限修饰符:" + Modifier.toString(clazz.getModifiers()));
		System.out.println("类型全名称:" + clazz.getName());
		System.out.println("简名称:" + clazz.getSimpleName());

		// 获取父类信息
		Class<?> superclass = clazz.getSuperclass();
		System.out.println("父类:" + superclass);

		// 获取父接口们信息
		Class<?>[] interfaces = clazz.getInterfaces();
		for (Class<?> class1 : interfaces) {
			System.out.println("父接口:" + class1);
		}

		// 获取属性信息
		Field[] fields = clazz.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			// 获取属性的权限修饰符
			System.out.println("第" + (i + 1) + "个属性修饰符:" + Modifier.toString(fields[i].getModifiers()));
			// 获取属性数据类型
			System.out.println("第" + (i + 1) + "个属性数据类型信息:" + fields[i].getType());
			// 获取属性名称
			System.out.println("第" + (i + 1) + "个属性名:" + fields[i].getName());
		}

		// 获取构造器信息
		Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
		for (int i = 0; i < declaredConstructors.length; i++) {
			// 获取构造器的权限修饰符
			System.out.println("第" + (i + 1) + "个构造器修饰符:" + Modifier.toString(declaredConstructors[i].getModifiers()));
			// 获取构造器的名称
			System.out.println("第" + (i + 1) + "个构造器名称:" + declaredConstructors[i].getName());
			// 获取构造器的形参列表
			Type[] parameterTypes = declaredConstructors[i].getParameterTypes();
			System.out.print("第" + (i + 1) + "个构造器形参列表数据类型:");
			for (Type type : parameterTypes) {
				System.out.print(type.getTypeName() + "  ");
			}
			System.out.println();
		}
		
		// 获取方法信息
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (int i = 0; i < declaredMethods.length; i++) {
			// 获取方法的权限修饰符
			System.out.println("第" + (i + 1) + "个方法修饰符:" + Modifier.toString(declaredMethods[i].getModifiers()));
			// 获取方法的返回值数据类型
			System.out.println("第" + (i + 1) + "个方法数据类型信息:" + declaredMethods[i].getReturnType());
			// 获取方法名称
			System.out.println("第" + (i + 1) + "个方法名:" + declaredMethods[i].getName());
			// 获取方法形参列表
			System.out.print("第" + (i + 1) + "个方法形参列表:");
			Class<?>[] parameterTypes = declaredMethods[i].getParameterTypes();
			for (Class<?> parameterType : parameterTypes) {
				System.out.print(parameterType + " ");
			}
			System.out.println();
			// 获取方法异常列表
			System.out.print("第" + (i + 1) + "个方法异常列表:");
			Class<?>[] exceptionTypes = declaredMethods[i].getExceptionTypes();
			for (Class<?> exceptionType : exceptionTypes) {
				System.out.println(exceptionType + " " );
			}
			System.out.println();
		}
	}
}
16.3.2 创建任意引用数据类型的对象

实现一:获取Class对象,通过获取构造器对象进行实例化(要求必须有空参
构造器)
实现二:获取Class对象,获取构造器对象,创建对象

初始化时发生异常:Caused by:Java.lang.NosuchMethodException…说明引用数据类型实例没有空参构造器
当创建对象权限不够时可以调用setAccessible(true)修改权限

16.3.3 操作任意引用数据类型的属性

(1)获取引用数据类型的Class对象:
(2)创建实例对象
(3)获取属性对象
(4)若属性不可见时,可以通过setAccessible(true)进行修改权限
(5)设置属性
(6)获取属性(如果操作静态变量,那么实例对象可以省略,用null表示)

实例代码

import java.io.Serializable;

public class LoadBean implements Serializable, Comparable<LoadBean> {

	private static final long serialVersionUID = 1L;
	private String name;
	public char gender;
	int age;
	boolean married;

	
	public LoadBean() {
		super();
	}


	public LoadBean(int age) {
		super();
		this.age = age;
	}


	public LoadBean(String name, char gender, int age) {
		super();
		this.name = name;
		this.gender = gender;
		this.age = age;
	}

	public LoadBean(String name, char gender, int age, boolean married) {
		super();
		this.name = name;
		this.gender = gender;
		this.age = age;
		this.married = married;
	}

	public String getName() {
		return name;
	}

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

	public char getGender() {
		return gender;
	}

	public void setGender(char gender) {
		this.gender = gender;
	}

	public int getAge() {
		return age;
	}

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

	public boolean isMarried() {
		return married;
	}

	public void setMarried(boolean married) {
		this.married = married;
	}

	@Override
	public String toString() {
		return "LoadBean [name=" + name + ", gender=" + gender + ", age=" + age + ", married=" + married + "]";
	}

	@Override
	public int compareTo(LoadBean o) {
		return this.age - o.age;
	}
}



import java.lang.reflect.Field;
import org.junit.Test;

public class LoadGetSet {

	@Test
	public void test01() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
			NoSuchFieldException, SecurityException {
		// 获取引用数据类型的Class对象
		Class<?> clazz = Class.forName("com.exer.test.LoadBean");
		// 创建实例对象
		Object obj = clazz.newInstance();
		// 获取属性对象
		Field name = clazz.getDeclaredField("name");
		Field age = clazz.getDeclaredField("age");
		Field id = clazz.getDeclaredField("serialVersionUID");
		// 设置权限
		name.setAccessible(true);
		age.setAccessible(true);
		id.setAccessible(true);
		// 设置属性
		name.set(obj, "张三");
		age.set(obj, 20);
		// 获取属性
		System.out.println(name.get(obj));
		System.out.println(age.get(obj));
		System.out.println(id.get(null));
		// 实例对象信息
		System.out.println(obj);
	}
}
16.3.4 操作任意引用数据类型的方法

(1)获取引用数据类型的Class对象
(2)创建实例对象
(3)获取方法对象
(4)设置权限
(5)调用方法

实例代码

import java.io.Serializable;

public class LoadBean implements Serializable, Comparable<LoadBean> {

	private static final long serialVersionUID = 1L;
	private String name;
	public char gender;
	int age;
	boolean married;

	
	public LoadBean() {
		super();
	}


	public LoadBean(int age) {
		super();
		this.age = age;
	}


	public LoadBean(String name, char gender, int age) {
		super();
		this.name = name;
		this.gender = gender;
		this.age = age;
	}

	public LoadBean(String name, char gender, int age, boolean married) {
		super();
		this.name = name;
		this.gender = gender;
		this.age = age;
		this.married = married;
	}

	public String getName() {
		return name;
	}

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

	public char getGender() {
		return gender;
	}

	public void setGender(char gender) {
		this.gender = gender;
	}

	public int getAge() {
		return age;
	}

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

	public boolean isMarried() {
		return married;
	}

	public void setMarried(boolean married) {
		this.married = married;
	}

	@Override
	public String toString() {
		return "LoadBean [name=" + name + ", gender=" + gender + ", age=" + age + ", married=" + married + "]";
	}

	@Override
	public int compareTo(LoadBean o) {
		return this.age - o.age;
	}
}


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

public class LoadGetSet {

	@Test
	public void test01() throws ClassNotFoundException, NoSuchMethodException, SecurityException,
			InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// 获取任意引用数据对象
		Class<?> clazz = Class.forName("com.exer.test.LoadBean");
		// 创建实例对象
		Constructor<?> constructor = clazz.getDeclaredConstructor();
		Object obj = constructor.newInstance();
		// 获取方法对象
		Method method = clazz.getDeclaredMethod("setName", String.class);
		// 调用方法
		method.invoke(obj, "张三");
		// 实例对象信息
		System.out.println(obj);
	}
}
16.3.5 获取泛型父类信息

(1)获取任意引用数据类型的Class对象
(2)获取父类泛型对象
(3)向下转型(获取单个的形参类型)
(4)遍历输出

实例代码

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class LoadGenericitySuperClass {
	public static void main(String[] args) {
		// 获取Class对象
		Class<Son> clazz = Son.class;
		// 获取泛型父类对象
		Type genericSuperclass = clazz.getGenericSuperclass();
		// 由于ParameterizedType是Type的子类,向下转型可以获得子类独有的方法
		ParameterizedType pt = (ParameterizedType) genericSuperclass;
		// 获取实际参数的数组
		Type[] actualTypeArguments = pt.getActualTypeArguments();
		for (Type type : actualTypeArguments) {
			System.out.println(type);
		}
	}
}

class Father<S, I> {

}

class Son extends Father<String, Integer> {

}
16.3.6 获取注解信息

(1)获取任意引用数据类型的Class对象
(2)获取注解类型对象
(3)获取配置参数

实例代码

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class LoadAnnotation {
	public static void main(String[] args) {
		// 获取任意引用数据类型的对象
		Class<People> clazz = People.class;
		//获取注解对象
		MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
		//获取配置
		String name = annotation.name();
		int age = annotation.age();
		System.out.println(name + age);
	}
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@interface MyAnnotation {
	String name();

	int age();
}

@MyAnnotation(name = "张三", age = 18)
class People {
	

}

第十七章 设计模式

17.1 模板设计模式

概念:大部分代码是确定的,只有几个小步骤不确定,需要其他的类来确定时

实例代码:计算任意一段代码的运行时间

//测试类
public class Test {
	public static void main(String[] args) {
		useRuntimeCount urc = new useRuntimeCount();
		long workTime = urc.getTime();
		System.out.println(workTime);
	}
}

//模板类
abstract class RuntimeCount {
	public long getTime() {
		// 开始时间
		long start = System.currentTimeMillis();
		// 实现过程
		work();
		// 结束时间
		long end = System.currentTimeMillis();
		return end - start;
	}

	protected abstract void work();
}

//使用模板类
class useRuntimeCount extends RuntimeCount {

	// 具体的执行步骤
	@Override
	protected void work() {
		// ...
	}
}

17.2 单例模式

概念:只能创建一个实例对象

17.2.1 饿汉式

(1)形式一

public enum Single{
	INSTANCE
}

(2)形式二

public class Single{
	public static final INSTANCE = new Single();
	
	private Single(){
	}
}

(3)形式三

public class Single{
	private static final INSTANCE = new Single();
	
	private Single{
	}

	public static Single getInstance(){
		return INSTANCE;
	}
}
17.2.2 懒汉式

(1)方式一

public class Single{
	private  Single(){
	}

	private static class Inner{
		static final Single INSTANCE = new Single();
	}

	public static Single getInstance(){
		return Inner.INSTANCE;
	}
}

(二)方式二

public class Single{
	
	private static Single instance;
	
	private Single(){
		
	}
	
	public static Single getInstance() {
		if(instance == null) {
			synchronized (Single.class) {
				if(instance == null) {
					instance = new Single();
				}
			}
		}
		return instance;
	}
}

17.3 工厂模式

概念:生产与消费分离

17.3.1 简单工厂
interface Car {
	void run();
}

class Bike implements Car {
	@Override
	public void run() {
		System.out.println("人力");
	}
}

class Bus implements Car {
	@Override
	public void run() {
		System.out.println("物力");
	}
}

public class Factory {

	public Car getCar(String str) throws Exception {
		switch (str) {
		case "人力":
			return new Bike();
		case "物力":
			return new Bus();
		default:
			throw new Exception("没有匹配到你的需求");
		}
	}
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值