第九章 异常
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是否可以捕获异常,也不管try和catch中是不是有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或者新特性(例如泛型),那么基本数据类型就需要用到包装类来包装
序号 | 基本数据类型 | 包装类 |
---|---|---|
1 | byte | Byte |
2 | short | Short |
3 | int | Integer |
4 | long | Long |
5 | float | Float |
6 | double | Double |
7 | char | Character |
8 | boolean | Boolean |
9 | void | Void |
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 | 没有 |
Character | 0~127 |
Boolean | true和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
DataOutputStream | DataInputStream |
---|---|
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("没有匹配到你的需求");
}
}
}