JAVA基础

day01

1.一个java文件中可以有多个class 但是只能有一个声明为public,且必须和文件名同名的类方可加public
2.Array工具类的使用

day02

面向对象
JAVA类的成员:属性,方法,构造器,代码块,内部类
面向对象三大特征:封装性,继承性,多态性。
面向过程POP 面向对象OOP
面向过程强调的是行为过程,面向对象的基础单位是类,强调谁去做。
所有的对象实例都要在堆上分配
栈用于存放存储局部变量,对象的首地址
方法区用于存在已经加载的类,常量

day03

instanceof 关键字 的使用
a instanceof A判断a对象是不是A的实例
如果B是A的父类 a instanceof B也是true
单例设计模式 只生成一个私有的实例 减少系统性能开销
单例模式 应用场景 网站计数器 应用程序日志应用 读取配置文件的类 任务管理器 以及回收站

day04

代码块
定义多个按先后顺序执行

静态代码块
内部可以写输出语句
随着类的加载而执行
初始化信息类

非静态代码块
可以写输出语句
随着对象的创建而执行
创建一个对象就执行一次
可以在创建对象时对属性进行初始化

代码块可以使用在数据连接池工具

final 修饰属性 只能在显式初始化,构造器和代码块中赋值

接口

接口与接口之间可以多继承
接口的具体使用,体现了多态性
java类可以实现多个接口
接口是一种规范

day05 接口

image.png
image.png

  1. 接口中定义的静态方法,只能通过接口来调用
  2. 通过实现类的对象,可以调用接口中的默认方法
  3. 如果实现类重写了接口中的默认方法,调用时,仍然调用重写以后的方法
  4. 如果实现类继承的父类和实现的接口中声明的同名同参数的方法子类在没有重写的情况下默认调用父类的方法 类优先原则
  5. 如果实现类实现了多个接口,而多个接口定义了同名同参数默认方法在实现类没有重写此方法的情况下报错
    那就需要我们在实现类中重写此方法
  6. 如何在实现类中调用被重写的方法
    super.method();

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

接口的多重继承

在Java中,类的多重继承是不合法,但接口允许多重继承,。

在接口的多重继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多重继承,而 Sports及 Event 可能定义或是继承相同的方法

标记接口

最常用的继承接口是没有包含任何方法的接口。

标识接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。

标识接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。

例如:java.awt.event包中的MouseListener接口继承的java.util.EventListener接口定义如下:

1
2
3
package java.util;
public interface EventListener
{}

没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:

  • 建立一个公共的父接口:
    正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

  • 向一个类添加数据类型:
    这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

内部类

如何实例化内部类?
image.png
成员内部类怎么调用外部类的方法?
image.png
局部内部类的使用
image.png
image.png

复习

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

day07 异常处理

image.png
image.png
image.png
image.png
image.png

day08 多线程

一个Java程序 至少有三个线程 main主线程 gc垃圾回收线程 , 异常处理线程。当然如果发生异常,会影响主线程

并行和并发

并行:多个CPU同时执行多个任务,比如多个人同时做不同的事
并发:一个CPU同时执行多个任务。比如秒杀、多个人做同一件事

使用多线程的优点

  1. 提高应用程序的响应速度。对图形化界面更有意义。增强用户体验
  2. 提高CPU的利用率
  3. 改善程序结构,把负责的进程分为多个线程独立运行利于理解和修改
    ##创建一个多线程
    image.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(i);
}
}
public MyThread(String name) {//给线程命名
super(name);
}
}
public class ThreadTest{

public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}

image.png

创建匿名子类行程

1
2
3
4
5
6
7
8
new Thread(){
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(i);
}
}
}.start();

Thread常用方法

image.png

线程的调度

image.png
image.png

三个窗口卖100张票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Windows1 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while(true){
if (ticket>0){
ticket--;
System.out.println(getName()+":买票,票号为"+ticket);
}else {
break;
}
}
}

public Windows1(String name) {
super(name);
}
}
public class Windows{
public static void main(String[] args) {
Windows1 t1 = new Windows1("窗口1");//命名方法2
Windows1 t2 = new Windows1("窗口2");
Windows1 t3 = new Windows1("窗口3");
t1.setName("窗口1");//命名方法1
t1.start();
t2.start();
t3.start();
}
}

用Runnable实现

1
2
3
4
5
6
7
8
9
10
Windows1 ws = new Windows1("test");
Thread t1 = new Thread(ws);
Thread t2 = new Thread(ws);
Thread t3 = new Thread(ws);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();

实现Runnable接口

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadTest1 {
public static void main(String[] args) {
Runat runat = new Runat();
Thread thread = new Thread(runat);
thread.start();
}
}

//创建一个实现Runnable的类
class Runat implements Runnable{
//实现抽象方法
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(i);
}
}
}

为什么调用当前线程的run会去实现测试类的run

1
2
3
4
5
6
7
8
@Override
public void run() {
if (target != null) {
target.run();
}
}
/* What will be run. */
private Runnable target;

再开一个线程只需要
Thread t2 = new Thread(runat);
thread.start();

两种创建方式的对比

image.png

day09 线程的生命周期

image.png
image.png
image.png
image.png

线程安全问题

image.png

同步代码块方式 对于继承类需要给锁加一个static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ThreadTest1 {
public static void main(String[] args) {
Runat runat = new Runat();
Thread t1 = new Thread(runat);
Thread t2 = new Thread(runat);
Thread t3 = new Thread(runat);
t1.setName("1");
t2.setName("2");
t3.setName("3");
t1.start();
t2.start();
t3.start();
}
}

//创建一个实现Runnable的类
class Runat implements Runnable{
//实现抽象方法
private static int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
//synchronized(this){//this代表当前的类是唯一的但是如果是类的继承方式不能用此方式因为使用了三个类
//synchronized(Runat.class){ //这个也是可以的唯一的用在继承类实现
synchronized(obj){
if (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+":买票,票号为"+ticket);
}else {
break;
}
}

}
}
}

同步代码方法实现Runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class WindowsTest {
public static void main(String[] args) {

Windows3 runat = new Windows3();
Thread t1 = new Thread(runat);
Thread t2 = new Thread(runat);
Thread t3 = new Thread(runat);
t1.setName("1");
t2.setName("2");
t3.setName("3");
t1.start();
t2.start();
t3.start();
}
}
class Windows3 implements Runnable{

private static int ticket = 100;
@Override
public void run() {
while(true){
sold();
}
}
private synchronized void sold(){ //同步方法
if (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+":买票,票号为"+ticket);
}
}
}

线程死锁问题

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

Lock锁(同步锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
class A{
private final ReentrantLock lock = new ReentrantLock();

void m(){
try{
lock.lock();
//保证线程安全的代码;
}finally {
lock.unlock();
}
}
}
//注意:如果同步代码有异常,要将unlock()写入finally语句块

Lock和synchronized的区别

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)

优先使用顺序: Lock 》 同步代码块(已经进入了方法体,分配了相应资源) 》 同步方法 (在方法体之外)

练习题

银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打 印账户余额。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class AccountTest {


public static void main(String[] args) {
Account act = new Account(0);
Customer c1 = new Customer(act);
Customer c2 = new Customer(act);
c1.setName("用户1");
c2.setName("用户2");
c1.start();
c2.start();
}
}

class Account{
private double balance;

public Account(double balance) {
this.balance = balance;
}
public synchronized void deposite(int amt) {
balance +=amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("存钱成功"+balance);
}
}

class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct=acct;
}

@Override
public void run() {
for (int i = 0; i <3 ; i++) {
acct.deposite(1000);
}
}
}

线程的通信

wait() 与 notify() 和 notifyAll()的区别
wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待 notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。

交替打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void run() {
while (true){
synchronized (this){
notify(); //唤醒
if (ticket > 0) {
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为" + ticket);
try {
wait(); //阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}

}
}

面试题
sleep和wait的异同
相同点 一旦执行方法,都可以使得当前的线程进入阻塞状态
不同点 1. 两个方法的声明位置不同,Thread类中声明sleep,Object类中声明wait
2. 调用的要求不同 sleep可以在任何需要的场景下调用,wait必须在同步代码块中调用
3. 关于是否释放同步监视器如果两个方法都使用在同步代码块或同步方法中sleep不会释放同步监视器,而wait会,也就是释放锁

生产者/消费者问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
producer.setName("生产者1");

Consumer consumer = new Consumer(clerk);
consumer.setName("消费者1");

producer.start();
consumer.start();

}
}

class Clerk{
private int num=0;

public synchronized void produceProduct() {
if (num<20){
notify();
num++;
System.out.println(Thread.currentThread().getName()+"开始生产产品"+num);
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public synchronized void consumeProduct() {
if (num>0){
notify();
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"开始消费产品"+num);
num--;
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
System.out.println(getName()+"开始生产");
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();

}
}

private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
}
class Consumer extends Thread{

private Clerk clerk;

public Consumer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println(getName() + "开始生产");
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}

JDK5.0新增线程创建方式 实现Callable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadNew {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
new Thread(futureTask).start();

try {
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

}
}
class NumThread implements Callable{

@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <100 ; i++) {
if (i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}

与使用Runnable相比, Callable功能更强大些

  1. 相比run()方法,可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果

使用线程池

 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
 好处:  提高响应速度(减少了创建新线程的时间)
 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
 便于线程管理  corePoolSize:核心池的大小
 maximumPoolSize:最大线程数
 keepAliveTime:线程没有任务时最多保持多长时间后会终止

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
 ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
 void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
 void shutdown() :关闭连接池
 Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。

1
2
3
4
5
6
7
8
9
10
11
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new Runat());//适合Runnable
service.submit(new NumThread());//适合Callable
service.shutdown();
}

day10 JAVA常用类 常量池,字面量

image.png

String str = “abc”和String str = new String(”abc“)的却别

image.png
image.png
image.png
结论:

  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  • 只要其中有一个是变量,结果就在堆中
  • 如果拼接的结果调用intern()方法,返回值就在常量池中

    面试题理解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class StringTest {
    String str = new String("good");
    char[] ch = { 't', 'e', 's', 't' };
    public void change(String str, char ch[]) {
    str = "test ok";
    ch[0] = 'b';
    }
    public static void main(String[] args) {
    StringTest ex = new StringTest();
    ex.change(ex.str, ex.ch);
    System.out.print(ex.str + " and ");
    System.out.println(ex.ch);
    }
    }

String常用方法

 int length():返回字符串的长度: return value.length
 char charAt(int index): 返回某索引处的字符return value[index]
 boolean isEmpty():判断是否是空字符串:return value.length == 0
 String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
 String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
 String trim():返回字符串的副本,忽略前导空白和尾部空白  boolean equals(Object obj):比较字符串的内容是否相同
 boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大 小写
 String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
 int compareTo(String anotherString):比较两个字符串的大小
 String substring(int beginIndex):返回一个新的字符串,它是此字符串的从 beginIndex开始截取到最后的一个子字符串。  String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
 boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
 boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
 boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的 子字符串是否以指定前缀开始
 boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true
 int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
 int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始
 int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
 int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索 注:indexOf和lastIndexOf方法如果未找到都是返回-1

 String replace(char oldChar, char newChar):返回一个新的字符串,它是 通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
 String replace(CharSequence target, CharSequence replacement):使 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
 String replaceAll(String regex, String replacement) : 使用给 定 的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
 String replaceFirst(String regex, String replacement) : 使用给 定 的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
 boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
 String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
 String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中

1
2
3
4
5
6
String str = "中"; 
System.out.println(str.getBytes("ISO8859-1").length);// -128~127 System.out.println(str.getBytes("GBK").length);
System.out.println(str.getBytes("UTF-8").length);

System.out.println(new String(str.getBytes("ISO8859-1"), "ISO8859-1"));// 乱码,表示不了中文 System.out.println(new String(str.getBytes("GBK"), "GBK"));
System.out.println(new String(str.getBytes("UTF-8"), "UTF-8"));

关于StringBuffer和StringBuilder的异同

StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且 提供相关功能的方法也一样
面试题:对比String、StringBuffer、StringBuilder
String(JDK1.0):不可变字符序列
StringBuffer(JDK1.0):可变字符序列、效率低、线程安全
StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全
注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder 会改变其值。
底层都使用char【】进行存储

StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:
StringBuffer():初始容量为16的字符串缓冲区 StringBuffer(int size):构造指定容量的字符串缓冲区 StringBuffer(String str):将内容初始化为指定字符串内容

StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转

常用方法
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)

三者效率对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime)); startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime)); startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));

JAVA日期和时间的API

  1. java.lang.System类
    System类提供的public static long currentTimeMillis()用来返回当前时 间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
     此方法适于计算时间差。时间戳

  2. java.util.Date类 表示特定的瞬间,精确到毫秒
     构造器:
     Date():使用无参构造器创建的对象可以获取本地当前时间。
     Date(long date)
     常用方法
     getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象 表示的毫秒数。时间戳
     toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。
     其它很多方法都过时了。

  3. java.text.SimpleDateFormat类
    格式化:
     SimpleDateFormat() :默认的模式和语言环境创建对象
     public SimpleDateFormat(String pattern):该构造方法可以用参数pattern 指定的格式创建一个对象,该对象调用:
     public String format(Date date):方法格式化时间对象date
     解析:
     public Date parse(String source):从给定字符串的开始解析文本,以生成 一个日期。
    image.png

把字符串转为sql类型

1
2
3
4
5
6
7
8
public static void main(String[] args) throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(birth);
java.sql.Date date1 = new java.sql.Date(date.getTime());
System.out.println(date1);

}
  1. java.util.Calendar(日历)类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void TestCalendar(){
//1.实例化
//方式一创建其子类(GregorianCalendar)的对象
//方式二调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass());
//2.常用方法
//get
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_WEEK));
//set
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime
Date date = calendar.getTime();
System.out.println(date);
//setTime
Date date1 = new Date();
calendar.setTime(date);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
}

新时间日期API

LocalDate和LocalTime和LocalDateTime的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
public void LocalTest(){
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);

//of()设置指定年月日时分秒
localDateTime = LocalDateTime.of(2020,10,10,13,25,34);
System.out.println(localDateTime);

//getXxx
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getMinute());

//withxxx
LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(22);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime1);
System.out.println(localDateTime2);

//plusxxx
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
LocalDateTime localDateTime4 = localDateTime.plusHours(3);
System.out.println(localDateTime3);
System.out.println(localDateTime4);

}

Instant 的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test2(){

//now获取本初子午线的标准时间
Instant instant = Instant.now();
System.out.println(instant);

//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);

//获取对应的毫秒数
long l = instant.toEpochMilli();
System.out.println(l);
}

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
 预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
public void test3(){
//预定义方式
DateTimeFormatter localDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime now = LocalDateTime.now();
String str1 = localDateTime.format(now);
System.out.println(str1);
TemporalAccessor parse = localDateTime.parse("2019-11-03T20:03:04.473");
System.out.println(parse);

//本地化相关的格式
//FormatStyle.SHORT,FormatStyle.LONG,FormatStyle.MEDIUM
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
//格式化
String str2 = formatter.format(now);
System.out.println(str2);

//方式三重点自定义格式
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str3 = formatter1.format(LocalDateTime.now());
System.out.println(str3);
//解析
TemporalAccessor accessor = formatter1.parse("2019-11-03");
System.out.println(accessor);
}

其它API
 ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris
 ZonedDateTime:一个在ISO-8601日历系统时区的日期时间,如 2007-1203T10:15:30+01:00 Europe/Paris。
 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如: Asia/Shanghai等
 Clock:使用时区提供对当前即时、日期和时间的访问的时钟。
 持续时间:Duration,用于计算两个“时间”间隔
 日期间隔:Period,用于计算两个“日期”间隔
 TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整 到“下一个工作日”等操作。
 TemporalAdjusters : 该类通过静态方法 (firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用 TemporalAdjuster 的实现。

JAVA比较器

 自然排序:java.lang.Comparable
 定制排序:java.util.Comparator
自然排序
Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称 为类的自然排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
//先按照价格从低到高排序,再按照产品名称从高到低排序
public int compareTo(Object o) {
if (o instanceof Good){
Good good = (Good) o;
//方式一
if (this.price>good.price){
return 1;
}else if (this.price<good.price){
return -1;
}else{
return -this.name.compareTo(good.name);
}
//方式二
// return Double.compare(this.price,good.price);
}
//return 0;
throw new RuntimeException("传入shujuleixingbuyizhi");
}

定制排序
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那 么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排 序的比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void test2(){
Good[] arr = new Good[4];
arr[0]=new Good("aaaa",12);
arr[1]=new Good("bbbb",56);
arr[2]=new Good("cccc",34);
arr[3]=new Good("dddd",6);
Arrays.sort(arr, new Comparator<Good>() {
//按照产品名称从低到高,然后按照价格从高到低
@Override
public int compare(Good o1, Good o2) {
if (o1.getName().equals(o2.getName())){
return -Double.compare(o1.getPrice(),o2.getPrice());
}else {
return o1.getName().compareTo(o2.getName());
}

}
});
System.out.println(Arrays.toString(arr));
}

SYSTEM类

System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。
 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。
 成员变量
 System类内部包含in、out和err三个成员变量,分别代表标准输入流 (键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
 成员方法
 native long currentTimeMillis(): 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时 间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
 void exit(int status): 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表 异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
 void gc(): 该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则 取决于系统中垃圾回收算法的实现以及系统执行时的情况。
 String getProperty(String key): 该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见 的属性名以及属性的作用如下表所示: image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);

Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回 值类型一般为double型。
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b) min(double a,double b) random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度

BigInteger与BigDecimal

java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供 所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。 另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。
构造器  BigInteger(String val):根据字符串构建BigInteger对象
 常用方法
 public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
 BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
 BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
 BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
 BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数 相除只保留整数部分。
 BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
 BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟 (this % val) 的两个 BigInteger 的数组。
 BigInteger pow(int exponent) :返回其值为 (thisexponent) 的 BigInteger。

BigDecimal
一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中, 要求数字精度比较高,故用到java.math.BigDecimal类。
 BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
构造器
 public BigDecimal(double val)
 public BigDecimal(String val)
 常用方法
 public BigDecimal add(BigDecimal augend)
 public BigDecimal subtract(BigDecimal subtrahend)
 public BigDecimal multiply(BigDecimal multiplicand)
 public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
用途示范

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
}

day11枚举类&注解

jad5.0之前第一种定义枚举类的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
class Season{
//1.声明Season对象的属性:private final修饰
private final String SeasonName;
private final String SeasonDesc;
//2.私有化类的构造器,并给对象属性赋值
public Season(String seasonName, String seasonDesc) {
SeasonName = seasonName;
SeasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMER = new Season("夏天","夏天");
public static final Season QT = new Season("秋天","秋天");
public static final Season DT = new Season("冬天","冬天");

//4.获取枚举类对象的属性

@Override
public String toString() {
return "Season{" +
"SeasonName='" + SeasonName + '\'' +
", SeasonDesc='" + SeasonDesc + '\'' +
'}';
}
public String getSeasonName() {
return SeasonName;
}
public String getSeasonDesc() {
return SeasonDesc;
}
}

//使用 ENUM定义枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SeasonTest {
public static void main(String[] args) {
Season1 spring = Season1.SPRING;
System.out.println(spring);
System.out.println(Season1.class.getSuperclass());
//定义的枚举类默认继承Enum
}
}
enum Season1{
SPRING("春天","春暖花开"),
SUMER("夏天","夏天"),
QT("秋天","秋天"),
DT("冬天","冬天");
private final String SeasonName;

public String getSeasonName() {
return SeasonName;
}

public String getSeasonDesc() {
return SeasonDesc;
}

private final String SeasonDesc;

Season1(String seasonName, String seasonDesc) {
SeasonName = seasonName;
SeasonDesc = seasonDesc;
}
}

values的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
Season1 spring = Season1.SPRING;
System.out.println(spring);
System.out.println(Season1.class.getSuperclass());
//定义的枚举类默认继承Enum
//获取数组值
Season1[] values = Season1.values();
for (int i = 0; i <values.length ; i++) {
System.out.println(values[i]);
}
//获取线程状态
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}
//valueof
Season1 winter = Season1.valueOf("SPRING");
System.out.println(winter.toString());
}

注解 Annotation

框架=注解+反射+设计模式

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成 一个修饰符使用。用于修饰它支持的程序元素
示例一:生成文档相关的注解
@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个

自定义Annotation

定义新的 Annotation 类型使用 @interface 关键字
 自定义注解自动继承了java.lang.annotation.Annotation接口
 Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其 方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。
 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字
 如果只有一个参数成员,建议使用参数名为value
 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value, 可以省略“value=”
 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数 据 Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义。

JDK中的元注解

@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命 周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的 注释
RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会 保留注释。程序可以通过反射获取该注释。

image.png

@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
定义为Documented的注解必须设置Retention值为RUNTIME。
@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以 继承父类类级别的注解
实际应用中,使用较少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

package 枚举类;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
String value();
}


@Test
public void testGetAnnotation(){
Class clazz = Season.class;
Annotation[] annotations = clazz.getAnnotations();
for (int i = 0; i < annotations.length; i++) {
System.out.println(annotations[i]
);
}
}

image.png
image.png
image.png

day12 JAVA集合

image.png
image.png
image.png

Collection 接口方法

1、添加
 add(Object obj)
 addAll(Collection coll)
2、获取有效元素的个数
 int size()
3、清空集合
 void clear()
4、是否是空集合
 boolean isEmpty()
5、是否包含某个元素
 boolean contains(Object obj):是通过元素的equals方法来判断是否 是同一个对象
 boolean containsAll(Collection c):也是调用元素的equals方法来比 较的。拿两个集合的元素挨个比较。
6、删除
 boolean remove(Object obj) :通过元素的equals方法判断是否是 要删除的那个元素。只会删除找到的第一个元素
 boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
 boolean retainAll(Collection c):把交集的结果存在当前集合中,不 影响c
8、集合是否相等
 boolean equals(Object obj)
9、转成对象数组
 Object[] toArray()
10、获取集合对象的哈希值
 hashCode()
11、遍历
 iterator():返回迭代器对象,用于集合遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package 集合;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class CollectionTest {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("tom"));
coll.add(false);
coll.add(new Person("tom"));

//contains 判断是否包含
boolean f = coll.contains(123);
System.out.println(coll.contains(new String("tom")));

//需要重写类中的equals否则伟false
System.out.println(coll.contains(new Person("tom")));

//remove
// coll.remove(123);
// System.out.println(coll);

//retain
Collection col1 = Arrays.asList(123,456);
col1.retainAll(coll);
System.out.println(col1);

//equals 因为ArrayList是有序集合,如果456和123调换位置则会现实false
Collection col2= new ArrayList();
col2.add(456);
col2.add(123);
col2.add(new String("tom"));
col2.add(false);
col2.add(new Person("tom"));
System.out.println(col2.equals(coll));

//集合和数组的转换
Object[] arr= coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
List<String> list = Arrays.asList(new String[]{"AA","BB"});
System.out.println(list);
//注意下面这么写会当成一个对象 ,arr2才是正确
List<int[]> arr1 = Arrays.asList(new int[]{123,456});
System.out.println(arr1);
List arr2 = Arrays.asList(123,456);
System.out.println(arr2);

//iteratoc()返回接口实例用于遍历结合元素
Iterator iterator = coll.iterator();
for (int i = 0; i <col1.size(); i++) {
System.out.println(iterator.next());
}

while (iterator.hasNext()){
System.out.println(iterator.next());
}

}
}

class Person{
public Person(String name) {
this.name = name;
}
String name;

@Override
public boolean equals(Object o) {
System.out.println("执行equals方法");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Person person = (Person) o;

return name.equals(person.name);
}

@Override
public int hashCode() {
return name.hashCode();
}
}

List接口 动态数组

List实现类之一:ArrayList
JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector
List实现类之一:ArrayList
ArrayList 是 List 接口的典型实现类、主要实现类
本质上,ArrayList是对象引用的一个”变长”数组
ArrayList的JDK1.8之前与之后的实现区别?
 JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
 JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元 素时再创建一个始容量为10的数组
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合

List实现类之二:LinkedList
 LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基 本结构。Node除了保存数据,还定义了两个变量:
 prev变量记录前一个元素的位置
 next变量记录下一个元素的位置
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高

List 实现类之三:Vector
Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList 相同,区别之处在于Vector是线程安全的。
在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时, 使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。

List常用方法
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中 的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Good("123",533));
System.out.println(list);
//add
list.add(1,"BB");
System.out.println(list);
//addAll
List list1 = Arrays.asList(12,2,3,4);
list.addAll(list1);
System.out.println(list);
//getIndex
System.out.println(list.get(2));
//indexOf
int i = list.indexOf(12);
System.out.println(i);
//lastindexof
int i1 = list.lastIndexOf(123);
System.out.println(i1);
//remove
Object o = list.remove(2);
//面试题如果为2得话通常删除索引而不是对象,需要删除对象如下
list.remove(new Integer(123));
System.out.println(o);
//set
list.set(4,"cc");
//subList
List list2 = list.subList(2, 4);
System.out.println(list2);
//遍历
for (Object obj:list) {
System.out.println(obj);
}
}

Set接口

Set接口是Collection的子接口,set接口没有提供额外的方法
 Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。
 Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法

Set实现类之一:HashSet
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除 性能。
HashSet 具有以下特点:
不能保证元素的排列顺序
HashSet 不是线程安全的
集合元素可以是 null
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相 等,并且两个对象的 equals() 方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

向HashSet中添加元素的过程:
 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法 来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象 在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在 数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布, 该散列函数设计的越好)
 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果 为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会通过链表的方式继续链接。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相 等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

Set实现类之二:LinkedHashSet
LinkedHashSet 是 HashSet 的子类
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置, 但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入 顺序保存的。
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全 部元素时有很好的性能。
LinkedHashSet 不允许集合元素重复。

Set实现类之三:TreeSet
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。 TreeSet底层使用红黑树结构存储数据  新增的方法如下: (了解)
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 public static void main(String[] args) {
Set set = new HashSet();
set.add(123);
set.add(456);
set.add("AA");
set.add(new Good("123",654));
set.add(129);
System.out.println(set);

Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}

Set set1 = new LinkedHashSet();
set1.add(123);
set1.add(456);
set1.add("AA");
set1.add(new Good("123",654));
set1.add(129);
System.out.println(set1);

Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Good && o2 instanceof Good){
Good g1 = (Good)o1;
Good g2 = (Good)o2;
if (g1.getName().equals(g2.getName())){
return Double.compare(g1.getPrice(),g2.getPrice());
}else {
return g1.getName().compareTo(g2.getName());
}

}
return 0;
}
};
TreeSet set3 = new TreeSet(com);
//失败,不能添加不同类得对象
// set3.add(123);
// set3.add("AA");
// set3.add(new Good("123",654));

// set3.add("AA");
// set3.add("DD");
// set3.add("BB");

set3.add(new Good("aom",12));
set3.add(new Good("btooo",13));
System.out.println(set3);

}

面试题理解为什么又四个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HashSet set2 = new HashSet();
Good g1 = new Good("AA",123);
Good g2 = new Good("BB",456);

set2.add(g1);
set2.add(g2);
System.out.println(set2);

g1.setName("CC");
set.remove(g1);
System.out.println(set2);
set2.add(new Good("CC",123));
System.out.println(set2);
set2.add(new Good("AA",123));

Map接口

 Map与Collection并列存在。用于保存具有映射关系的数据:key-value
 Map 中的 key 和 value 都可以是任何引用类型的数据
 Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应 的类,须重写hashCode()和equals()方法
 常用String类作为Map的“键”
 key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value
 Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类

常用方法
 添加、删除、修改操作:
 Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
 void putAll(Map m):将m中的所有key-value对存放到当前map中
 Object remove(Object key):移除指定key的key-value对,并返回value
 void clear():清空当前map中的所有数据
 元素查询的操作:
 Object get(Object key):获取指定key对应的value
 boolean containsKey(Object key):是否包含指定的key
 boolean containsValue(Object value):是否包含指定的value
 int size():返回map中key-value对的个数
 boolean isEmpty():判断当前map是否为空
 boolean equals(Object obj):判断当前map和参数对象obj是否相等
 元视图操作的方法:
 Set keySet():返回所有key构成的Set集合
 Collection values():返回所有value构成的Collection集合
 Set entrySet():返回所有key-value对构成的Set集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package 集合;
import java.util.*;
public class MapTest {
public static void main(String[] args) {
Map map = new HashMap();
map.put(null,123);
map.put("AA",123);
map.put(45,123);
map.put("AA",456);
System.out.println(map);

//remove
Object value = map.remove(123);
System.out.println(value);
System.out.println(map);

//遍历key
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

Collection values = map.values();
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}

//遍历key-value
Set set1 = map.entrySet();
Iterator iterator2 = set1.iterator();
while(iterator2.hasNext()){
System.out.println(iterator.next());
}

//clear()
map.clear();
System.out.println(map.size());

//get
System.out.println(map.get(456));
//containskey
boolean aa = map.containsKey("AA");
System.out.println(aa);
System.out.println(map.containsValue(123));
System.out.println(map.isEmpty());
System.out.println();
}
}

HashMap常量

DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表 MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的 数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行 resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4 倍。)
table:存储元素的数组,总是2的n次幂
entrySet:存储具体元素的集
size:HashMap中存储的键值对的数量
modCount:HashMap扩容和结构改变的次数。
threshold:扩容的临界值,=容量*填充因子
loadFactor:填充因子

 HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时, 系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个 bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
 每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引 用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head。
 添加元素的过程:
向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据 key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数 组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上 已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次 比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果 hash值不同,继续比较二者是否equals。如果返回值为true,则使用entry1的value 去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都 为false,则entry1仍可添加成功。entry1指向原有的entry元素。

HashMap的扩容

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的 长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在 HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算 其在新数组中的位置,并放进去,这就是resize。

那么HashMap什么时候进行扩容呢?

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况 下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数 超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把 数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置, 而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数, 那么预设元素的个数能够有效的提高HashMap的性能。

JDK1.8存储结构

 HashMap的内部存储结构其实是数组+链表+树的结合。当实例化一个 HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系 时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表 中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查 找bucket中的元素。
 每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带 一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能 生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象 可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

那么HashMap什么时候进行扩容和树形化呢?

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)loadFactor 时 , 就会进行数组扩容, loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认 情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中 元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值) 的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元 素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有 达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成 树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后, 下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

关于映射关系的key是否可以修改?answer:不要修改

映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算 每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关 系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上

总结:JDK1.8相较于之前的变化:

1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
2.当首次调用map.put()时,再创建长度为16的数组
3.数组为Node类型,在jdk7中称为Entry类型
4.形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
5.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置 上的所有key-value对使用红黑树进行存储

面试题:负载因子值的大小,对HashMap有什么影响

 负载因子的大小决定了HashMap的数据密度。
 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长, 造成查询或插入时的比较次数增多,性能会下降。
 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的 几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性 能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建 议初始化预设大一点的空间。
 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此 时平均检索长度接近于常数

Map实现类之二:LinkedHashMap

LinkedHashMap 是 HashMap 的子类
在HashMap存储结构的基础上,使用了一对双向链表来记录添加
元素的顺序
与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致

Map实现类之三:TreeMap

TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeSet底层使用红黑树结构存储数据
TreeMap 的 Key 的排序:
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
 TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TreeMap map = new TreeMap();
Good g1 = new Good("AA",123);
Good g2 = new Good("FF",456);
Good g3 = new Good("DD",789);
Good g4 = new Good("CC",456);
Good g5 = new Good("BB",012);
map.put(g1,98);
map.put(g2,89);
map.put(g3,100);
map.put(g4,150);
map.put(g5,120);

Set set1 = map.entrySet();
Iterator iterator2 = set1.iterator();
while(iterator2.hasNext()){
Object obj = iterator2.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"------"+entry.getValue());
}

Map实现类之四:Hashtable

Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询 速度快,很多情况下可以互用。
与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。

Map实现类之五:Properties

Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型 存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法

1
2
3
4
Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);

Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作:(均为static方法)
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序 sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add(123);
System.out.println(list);
//reverse
Collections.reverse(list);
System.out.println(list);
Collections.shuffle(list);
System.out.println(list);
Collections.sort(list);
System.out.println(list);
Collections.swap(list,1,2);
int frequency = Collections.frequency(list, 123);
System.out.println(frequency);
//错误示范 List dest = new ArrayList(list.size());
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);
System.out.println(dest);
//转成线程安全
List list3 = Collections.synchronizedList(list);

day13 泛型

那么为什么要有泛型呢,直接Object不是也可以存储数据吗?

  1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
  2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。

在集合中使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ArrayList<Integer> list = new ArrayList<>();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);
//遍历方式一: //for(Integer i : list){ //不需要强转 //System.out.println(i); //}
//遍历方式二: Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); }


Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom1",34); map.put("Tom2",44); map.put("Tom3",33); map.put("Tom4",32); //添加失败 //map.put(33, "Tom");
Set<Entry<String,Integer>> entrySet = map.entrySet();
Iterator<Entry<String,Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){ Entry<String,Integer> entry = iterator.next(); System.out.println(entry.getKey() + "--->" + entry.getValue()); }

自定义泛型结构

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: <E1,E2,E3>
    1. 泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass(){}
    1. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
    1. 泛型不同的引用不能相互赋值。 >尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有 一个ArrayList被加载到JVM中。
    1. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
    1. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
    1. jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
    1. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
  2. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。
    1. 异常类不能是泛型的
    1. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
  3. 12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:  子类不保留父类的泛型:按需实现  没有类型 擦除  具体类型  子类保留父类的泛型:泛型子类  全部保留  部分保留 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Father<T1, T2> { } 
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{ }
// 2)具体类型
class Son2 extends Father<Integer, String> { }
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> { }
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> { }


class Person<T> {
// 使用T类型定义变量
private T info;
// 使用T类型定义一般方法
public T getInfo() { return info; }
public void setInfo(T info) { this.info = info; }
// 使用T类型定义构造器
public Person() { }
public Person(T info) { this.info = info; }

自定义泛型结构:泛型方法

image.png
image.png

泛型在继承上的体现

image.png

通配符的使用

1.使用类型通配符:? 比如:List ,Map List是List、List<最高父类>等各种泛型List的父类。 2.读取List的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中 添加对象。  唯一的例外是null,它是所有类型的成员。
 将任意元素加入到其中不是类型安全的: Collection<?> c = new ArrayList(); c.add(new Object()); // 编译时错误 因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集 合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知 道那是什么类型,所以我们无法传任何东西进去。
 唯一的例外的是null,它是所有类型的成员。
 另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的 类型,但是我们知道,它总是一个Object。
image.png
image.png

有限制的通配符

 <?> 允许所有泛型的引用调用
 通配符指定上限 上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
 通配符指定下限 下限super:使用时指定的类型不能小于操作的类,即>=
 举例:
 <? extends Number> (无穷小 , Number] 只允许泛型为Number及Number子类的引用调用
 <? super Number> [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用
 <? extends Comparable> 只允许泛型为实现Comparable接口的实现类的引用调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//有限制的通配符
List<? extends Person> list4 = null;
List<? super Person> list5 = null;

List<Student> list6=new ArrayList<Student>();
List<Person> list7=new ArrayList<Person>();
List<Object> list8=new ArrayList<Object>();

list4 =list6;
list4=list7;
//编译失败
//list4=list8;

//list5=list6;
list5=list7;
list5=list8;

Object obj=list5.get(0);
//Person obj1 = list5.get(0);

//list4.add(new Student("aa"));
list5.add(new Person("aa"));
list5.add(new Student("22"));

image.png

应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//package com.atguigu.java2;

interface Info{ // 只有此接口的子类才是表示人的信息
}
class Contact implements Info{ // 表示联系方式
private String address ; // 联系地址
private String telephone ; // 联系方式
private String zipcode ; // 邮政编码
public Contact(String address,String telephone,String zipcode){
this.address = address;
this.telephone = telephone;
this.zipcode = zipcode;
}
public void setAddress(String address){
this.address = address ;
}
public void setTelephone(String telephone){
this.telephone = telephone ;
}
public void setZipcode(String zipcode){
this.zipcode = zipcode;
}
public String getAddress(){
return this.address ;
}
public String getTelephone(){
return this.telephone ;
}
public String getZipcode(){
return this.zipcode;
}
@Override
public String toString() {
return "Contact [address=" + address + ", telephone=" + telephone
+ ", zipcode=" + zipcode + "]";
}
}
class Introduction implements Info{
private String name ; // 姓名
private String sex ; // 性别
private int age ; // 年龄
public Introduction(String name,String sex,int age){
this.name = name;
this.sex = sex;
this.age = age;
}
public void setName(String name){
this.name = name ;
}
public void setSex(String sex){
this.sex = sex ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return this.name ;
}
public String getSex(){
return this.sex ;
}
public int getAge(){
return this.age ;
}
@Override
public String toString() {
return "Introduction [name=" + name + ", sex=" + sex + ", age=" + age
+ "]";
}
}
class Person<T extends Info>{
private T info ;
public Person(T info){ // 通过构造器设置信息属性内容
this.info = info;
}
public void setInfo(T info){
this.info = info ;
}
public T getInfo(){
return info ;
}
@Override
public String toString() {
return "Person [info=" + info + "]";
}

}
public class GenericPerson{
public static void main(String args[]){
Person<Contact> per = null ; // 声明Person对象
per = new Person<Contact>(new Contact("北京市","01088888888","102206")) ;
System.out.println(per);

Person<Introduction> per2 = null ; // 声明Person对象
per2 = new Person<Introduction>(new Introduction("李雷","男",24));
System.out.println(per2) ;
}
}

day14 IO流

File的使用

 java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
 File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流。  想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
 File对象可以作为参数传递给流的构造器

常用方法

 File类的获取功能
 public String getAbsolutePath():获取绝对路径
 public String getPath() :获取路径
 public String getName() :获取名称
 public String getParent():获取上层文件目录路径。若无,返回null
 public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
 public long lastModified() :获取最后一次的修改时间,毫秒值
 public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
 public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
 File类的重命名功能
 public boolean renameTo(File dest):把文件重命名为指定的文件路径
 File类的判断功能
 public boolean isDirectory():判断是否是文件目录
 public boolean isFile() :判断是否是文件
 public boolean exists() :判断是否存在
 public boolean canRead() :判断是否可读
 public boolean canWrite() :判断是否可写
 public boolean isHidden() :判断是否隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public static void main(String[] args) throws IOException {
//构造器
File file1 = new File("hello.md");
File file2 = new File("D:\\android-example","Daohang-master");
System.out.println(file2);
File file3 = new File(file2,"README.md");

System.out.println(file1.getAbsolutePath());
System.out.println(file1.getPath());
System.out.println(file1.getName());
System.out.println(file1.getParent());
System.out.println(file1.length());
System.out.println(file1.lastModified());

System.out.println();

System.out.println(file3.getAbsolutePath());
System.out.println(file3.getPath());
System.out.println(file3.getName());
System.out.println(file3.getParent());
System.out.println(file3.length());
System.out.println(file3.lastModified());

String[] list = file2.list();
File[] files = file2.listFiles();
for (String s:list){
System.out.println(s);
}
for (File f:files){
System.out.println(f);
}

//改名并且移动
boolean b = file3.renameTo(file1);
System.out.println(b);

//判断功能
System.out.println(file1.isDirectory());
System.out.println(file1.isFile());
System.out.println(file1.exists());
System.out.println(file1.canRead());
System.out.println(file1.canWrite());
System.out.println(file1.isHidden());

File file4 = new File("hi.txt");
if (!file4.exists()){
file4.createNewFile();
System.out.println("创建成功");
}else {
file4.delete();
//JAVA中的删除不走回收站
System.out.println("删除成功");
}
//文件目录创建
File file5 = new File("d:\\io\\io1");
boolean mkdir = file5.mkdir();
//如果上级目录也不存在使用如下方法
//boolean mkdirs = file5.mkdirs();
if (mkdir){
System.out.println("创建成功");
}else {
file5.delete();
System.out.println("删除成功");
}
}

遍历指定目录所有文件名称,包括子文件目录中的文件。
拓展1:并计算指定目录占用空间的大小
拓展2:删除指定文件目录及其下的所有文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public static void main(String[] args) {
// 递归:文件目录
/** 打印出指定目录所有文件名称,包括子文件目录中的文件 */

// 1.创建目录对象
File dir = new File("E:\\teach\\01_javaSE\\_尚硅谷Java编程语言\\3_软件");

// 2.打印目录的子文件
printSubFile(dir);
}

public static void printSubFile(File dir) {
// 打印目录的子文件
File[] subfiles = dir.listFiles();

for (File f : subfiles) {
if (f.isDirectory()) {// 文件目录
printSubFile(f);
} else {// 文件
System.out.println(f.getAbsolutePath());
}

}
}

// 方式二:循环实现
// 列出file目录的下级内容,仅列出一级的话
// 使用File类的String[] list()比较简单
public void listSubFiles(File file) {
if (file.isDirectory()) {
String[] all = file.list();
for (String s : all) {
System.out.println(s);
}
} else {
System.out.println(file + "是文件!");
}
}

// 列出file目录的下级,如果它的下级还是目录,接着列出下级的下级,依次类推
// 建议使用File类的File[] listFiles()
public void listAllSubFiles(File file) {
if (file.isFile()) {
System.out.println(file);
} else {
File[] all = file.listFiles();
// 如果all[i]是文件,直接打印
// 如果all[i]是目录,接着再获取它的下一级
for (File f : all) {
listAllSubFiles(f);// 递归调用:自己调用自己就叫递归
}
}
}

// 拓展1:求指定目录所在空间的大小
// 求任意一个目录的总大小
public long getDirectorySize(File file) {
// file是文件,那么直接返回file.length()
// file是目录,把它的下一级的所有大小加起来就是它的总大小
long size = 0;
if (file.isFile()) {
size += file.length();
} else {
File[] all = file.listFiles();// 获取file的下一级
// 累加all[i]的大小
for (File f : all) {
size += getDirectorySize(f);// f的大小;
}
}
return size;
}

// 拓展2:删除指定的目录
public void deleteDirectory(File file) {
// 如果file是文件,直接delete
// 如果file是目录,先把它的下一级干掉,然后删除自己
if (file.isDirectory()) {
File[] all = file.listFiles();
// 循环删除的是file的下一级
for (File f : all) {// f代表file的每一个下级
deleteDirectory(f);
}
}
// 删除自己
file.delete();
}

IO流原理及流的分类

流的分类
按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流
(抽象基类) 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer

  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个 抽象基类派生的。
    1. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
  2. 文本尽量用字符流,非文本如图片视频,尽量用字节流

InputStream & Reader
InputStream 和 Reader 是所有输入流的基类。
InputStream(典型实现:FileInputStream)
int read()
int read(byte[] b)
int read(byte[] b, int off, int len)
Reader(典型实现:FileReader) int read()
int read(char [] c)
int read(char [] c, int off, int len)
程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资 源,所以应该显式关闭文件 IO 资源。
FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader

InputStream
 int read()
从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因 为已经到达流末尾而没有可用的字节,则返回值 -1。
 int read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已 经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取 的字节数。
 int read(byte[] b, int off,int len) 将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取 的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于 文件末尾而没有可用的字节,则返回值 -1。
 public void close() throws IOException 关闭此输入流并释放与该流关联的所有系统资源。

Reader
 int read() 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
 int read(char[] cbuf) 将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
 int read(char[] cbuf,int off,int len) 将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
 public void close() throws IOException 关闭此输入流并释放与该流关联的所有系统资源

OutputStream & Writer
 OutputStream 和 Writer 也非常相似:
void write(int b/int c);
void write(byte[] b/char[] cbuf);
void write(byte[] b/char[] buff, int off, int len);
void flush();
void close(); 需要先刷新,再关闭此流
 因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数 void write(String str);
void write(String str, int off, int len);
 FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

OutputStream
 void write(int b) 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写 入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。  void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。
 void write(byte[] b,int off,int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
 public void flush()throws IOException 刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立 即写入它们预期的目标。
 public void close() throws IOException 关闭此输出流并释放与该流关联的所有系统资源。

Writer
 void write(int c) 写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。
 void write(char[] cbuf) 写入字符数组。
 void write(char[] cbuf,int off,int len) 写入字符数组的某一部分。从off开始,写入len个字符
 void write(String str) 写入字符串。
 void write(String str,int off,int len) 写入字符串的某一部分。
 void flush() 刷新该流的缓冲,则立即将它们写入预期目标。
 public void close() throws IOException 关闭此输出流并释放与该流关联的所有系统资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    public static void main(String[] args){
FileReader fr=null;
try{

File file = new File("hi.txt");
System.out.println(file.getAbsolutePath());

fr = new FileReader(file);
int read;
while ((read=fr.read())!=-1){
System.out.println((char) read);
}
}catch (IOException e){

}finally {
try {
if (fr!=null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void testFileReader(){
FileReader fr = null;
try {
File file = new File("hi.txt");
fr = new FileReader(file);
char[] cbuf = new char[5];
int len;
while((len=fr.read(cbuf))!=-1){
for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr!=null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}
@Test
public void FileWritertest(){
FileWriter fw = null;
try {
File file=new File("hi1.txt");
//如果不加参数append=true就会进行覆盖
fw = new FileWriter(file,true);
fw.write("test1\n");
fw.write("test2");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw!=null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@Test
public void FileReadandWrite(){
FileReader fr = null;
FileWriter fw = null;
try {
File srcFile = new File("hi.txt");
File deskFile = new File("hi2.txt");
fr = new FileReader(srcFile);
fw = new FileWriter(deskFile);

char[] cbuf = new char[5];
int len;
while ((len=fr.read(cbuf))!=-1){
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr!=null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fw!=null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void FileInputStreamtest(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File("1510646712825.jpg");
File deskFile = new File("hi2.jpg");

fis = new FileInputStream(srcFile);
fos = new FileOutputStream(deskFile);

byte[] buffer = new byte[1024];
int len;
while((len=fis.read(buffer))!=-1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos!=null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis!=null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void BufferStreamTest(){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
File srcFile = new File("1510646712825.jpg");
File deskFile = new File("hi3.jpg");
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(deskFile);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024];
int len;
while((len=fis.read(buffer))!=-1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//只需要关闭外层,内层自动关闭
try {
if (bis!=null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bos!=null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void BufferedReaderandWirter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(new File("hi.txt")));
bw = new BufferedWriter(new FileWriter(new File("hi3.txt")));

//方式一
// char[] cbuf = new char[1024];
// int len;
// while ((len=br.read(cbuf))!=-1){
// bw.write(cbuf,0,len);
// //刷新缓冲区
// //bw.flush();
// }

//方式二String
String str;
while ((str=br.readLine())!=null){
bw.write(str);//不包括换行符
bw.newLine();

}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br!=null)
br.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bw!=null)
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test2(){
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
File file1 = new File("h1.txt");
File file2 = new File("h3.txt");

FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
isr = new InputStreamReader(fis, "utf-8");
osw = new OutputStreamWriter(fos, "gbk");
char[] cbuf = new char[1024];
int len;
while ((len=isr.read(cbuf))!=-1){
osw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {

try {
if (isr!=null)
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (osw!=null)
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

标准输入、输出流(了解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package IO流;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class OtherStreamTest {
public static void main(String[] args) {
BufferedReader br=null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);

while (true){
System.out.println("请输入字符串:");
String data =br.readLine();
if (data.equalsIgnoreCase("e")||data.equalsIgnoreCase("exit")){
System.out.println("程序结束");
break;
}
String upperCase =data.toUpperCase();
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

DataOutputStream和DataInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
public void test3(){
try {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("hi.txt"));
dos.writeUTF("张佳缘");
dos.flush();
dos.writeInt(123);
dos.flush();
dos.writeBoolean(true);
dos.flush();
dos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {

}
}

@Test
public void test4(){
//读取的顺序要和写入的顺序一致
String s = null;
int age = 0;
boolean f = false;
try {
DataInputStream dis = new DataInputStream(new FileInputStream("hi.txt"));
s = dis.readUTF();
age = dis.readInt();
f = dis.readBoolean();
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println(s+age+f);
}
}

ObjectInputStream和OjbectOutputSteam

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Test
public void test5(){
try {
//类的所有成员必须是可序列化,并且不能序列化static和transient修饰的成员变量
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hi.txt"));
Good g = new Good("AA", 123);
oos.writeObject(g);
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
@Test
public void test6(){
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hi.txt"));
Good o = (Good) ois.readObject();
System.out.println(o);
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
}
}

对象的序列化

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从 而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传 输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原 来的Java对象
序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据, 使其在保存和传输时可被还原
序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返 回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可 序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。 否则,会抛出NotSerializableException异常
Serializable
Externalizable

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象 进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议, 显式声明。
 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)

谈谈你对java.io.Serializable接口的理解,我们知道它用于序列化, 是空方法接口,还有其它认识吗?
 实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后 完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机 制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创 建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里 准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必 关心字节的顺序或者其他任何细节。
 由于大部分作为参数的类如String、Integer等都实现了 java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更 灵活。

随机存取文件流

RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并 且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也 可以写。 RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意 地方来读、写文件
支持只访问文件的部分内容
可以向已存在的文件后追加内容
RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。 RandomAccessFile 类对象可以自由移动记录指针:
long getFilePointer():获取文件记录指针的当前位置
void seek(long pos):将文件记录指针定位到 pos 位置

构造器 public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指 定 RandomAccessFile 的访问模式:
r: 以只读方式打开 rw:打开以便读取和写入
rwd:打开以便读取和写入;同步文件内容的更新
rws:打开以便读取和写入;同步文件内容和元数据的更新
 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件, 如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不 存在则会去创建文件,如果存在则不会创建。如果文件存在,则会出现覆盖,从头覆盖,并非全部覆盖。

RandomAccessFile
我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能, 用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与 被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次 暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上 一次的地方下载,从而实现断点下载或上传的功能,有兴趣的朋友们可以 自己实现下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static void main(String[] args) {
RandomAccessFile file=null;
RandomAccessFile file1=null;
try {
file = new RandomAccessFile(new File("hi1.txt"),"rw");
file1 = new RandomAccessFile(new File("hi5.txt"),"rw");
byte[] bytes = new byte[1024];
int len;
while ((len=file1.read(bytes))!=-1){
file1.write(bytes,0,len);
}

//覆盖指定位置的数据,实现断点续传
file1.seek(5);
file1.write("xasd".getBytes());

//插入数据
file.seek(3);
StringBuilder builder = new StringBuilder((int) new File("hi1.txt").length());
byte[] buffer1 = new byte[20];
int len1;
while ((len1= file1.read(buffer1))!=-1){
builder.append(new String(buffer1,0,len1));
}
file.seek(3);
file.write("xyz".getBytes());
file.write(builder.toString().getBytes());

} catch (IOException e) {
e.printStackTrace();
} finally {
if (file!=null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (file1!=null) {
try {
file1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

day15 网络编程

1
2
3
4
5
6
InetAddress inet = InetAddress.getByName("192.168.1.111");
System.out.println(inet);
InetAddress inet1 = InetAddress.getByName("smxy.xyz");
System.out.println(inet1);
InetAddress inet2 = InetAddress.getLocalHost();
System.out.println(inet2);

网络协议

客户端发送信息和文件并接收反馈,服务端接收并反馈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    @Test
public void server1(){
ServerSocket serverSocket=null;
InputStream is=null;
ByteArrayOutputStream baos=null;
Socket socket=null;
try {
serverSocket = new ServerSocket(65500);
socket = serverSocket.accept();
is = socket.getInputStream();
boolean f=true;

//不建议这么写可能会乱码
// byte[] buffer = new byte[1024];
// int len;
// while ((len=is.read(buffer))!=-1){
// String str = new String(buffer,0,len);
// System.out.println(str);
// }
//接收字符串
// baos = new ByteArrayOutputStream();
// byte[] buffer = new byte[5];
// int len;
// while ((len = is.read(buffer))!=-1){
// baos.write(buffer,0,len);
// }
// System.out.println(socket.getInetAddress().getHostAddress()+baos.toString());
//接收文件
FileOutputStream fos = new FileOutputStream(new File("test1.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//服务器反馈
OutputStream os = socket.getOutputStream();
os.write("sadasdas".getBytes());



} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos!=null)
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is!=null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (serverSocket!=null)
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    @Test
public void client1() {
FileInputStream fis=null;
OutputStream os=null;
ByteArrayOutputStream baos=null;
Socket socket=null;
try {
InetAddress inet = InetAddress.getLocalHost();
socket = new Socket(inet,65500);
os = socket.getOutputStream();
// os.write("hellowd".getBytes());

//发送文件
fis = new FileInputStream(new File("hi2.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
//传输文件是阻塞式
socket.shutdownOutput();
//接收服务器反馈
InputStream is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buffer1 = new byte[1024];
int len1;
while ((len1 = is.read(buffer1))!=-1){
baos.write(buffer1,0,len1);
}
System.out.println(socket.getInetAddress().getHostAddress()+baos.toString());

} catch (IOException e) {
e.printStackTrace();
} finally {
if (os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket!=null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

UDP网络编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();

String str = "asasdasdasad";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
@Test
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),packet.getLength()));
socket.close();
}

URL编程

1
2
3
4
5
6
7
8
9
10
11
12
13
public void URLtest() throws IOException {
URL url = new URL("https://blog.smxy.xyz/upload/2019/10/image-ff215b21bb704a3589ee54afa8882079.png");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
InputStream is = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("ksk.png");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
is.close();
fos.close();

day15Java反射机制

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信