Git-基础命令

Git 一些常用命令。

阅读更多
Java-网络编程

有人说,20世纪最伟大的发明不是计算机,而是计算机网络。

还有人说,如果你买了计算机而没有联网,就等于买了电话机而没有接电话线一样。

阅读更多
Java-多线程

多线程概述

进程

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

多进程的意义

单进程计算机只能做一件事情。而现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。

对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。

因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。

多进程的作用不是提高执行速度,而是提高CPU的使用率。

线程

是进程中的单个顺序控制流,是一条执行路径。

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。

多线程的意义

多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。

而多线程却给了我们一个错觉:让我们认为多个线程是并发执行的。其实不是。

因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。

并行和并发

并行:前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。

并发:后者是物理上同时发生,指在某一个时间点同时运行多个程序。

那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过得知道如何调度和控制它们。

Q & A

Q:Java 程序的运行原理及 JVM 的启动是多线程的吗?

Java 命令去启动 JVM,JVM会启动一个进程,该进程会启动一个主线程。
JVM 的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

多线程实现

继承 Thread 类

  • 自定义类 MyThread 继承 Thread 类;
  • MyThread 类里面重写 run();
  • 创建对象;
  • 启动线程。

CODE

  • MyThread.java

    1
    2
    3
    4
    5
    6
    7
    8
    public class MyThread extends Thread {
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    System.out.println(getName() + "---" + i);
    }
    }
    }
  • MyThreadTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MyThreadTest {
    public static void main(String[] args) {
    MyThread my1 = new MyThread();
    MyThread my2 = new MyThread();

    my1.setName("ahoj");
    my2.setName("baozi");

    my1.start();
    my2.start();

    System.out.println(Thread.currentThread().getName());
    }
    }

Q & A

Q1:为什么是 run() 方法呢?

不是类中的所有代码都需要被线程执行的。这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

Q2:线程能不能多次启动(start)?

不可以
通过Thread实例的start(),一个Thread的实例只能产生一个线程。一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了。
一个线程对象只能调用一次start方法.从new到等待运行是单行道,所以如果你对一个已经启动的线程对象再调用一次start方法的话,会产生:IllegalThreadStateException异常. 可以被重复调用的是run()方法。

Q3:run()和start()方法的区别

run()方法: 在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法: 启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程

Thread 类的方法

1
2
3
4
public final String getName();
public final void setName(String name); // 设置线程名称也可以通过构造的方式

public static Thread currentThread(); // 返回当前正在执行的线程对象

实现 Runnable 接口

步骤:

  • 自定义类MyRunnable实现Runnable接口;
  • 重写run()方法;
  • 创建MyRunnable类的对象;
  • 创建Thread类的对象,并把C步骤的对象作为构造参数传递;
  • 启动线程。

CODE,卖电影票案例

  • SellTickets.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class SellTickets implements Runnable{

    private int tickets = 100;

    @Override
    public void run() {
    while (true) {
    if (tickets > 0) {
    try {
    Thread.sleep(100); // 模拟现实中延迟的情况,延迟0.1秒
    System.out.println(Thread.currentThread().getName() + "正在出售第 " + (tickets--) + " 张票");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }
  • SellTicketsDemo.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class SellTicketsDemo {
    public static void main(String[] args) {
    SellTickets st = new SellTickets();

    Thread t1 = new Thread(st, "窗口1");
    Thread t2 = new Thread(st, "窗口2");
    Thread t3 = new Thread(st, "窗口3");

    t1.start();
    t2.start();
    t3.start();
    }
    }

继承接口的好处

可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

线程调度和优先级

计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程调度模型

  • 分时调度模型

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。

  • 抢占式调度模型

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

Java使用的是抢占式调度模型。

线程的优先级

默认优先级:5

优先级范围:1(低) ~ 10(高)

线程优先级仅代表几率,在多次运行的时候才能看到比较好的效果,1、2次说明不了什么问题。

1
2
public final int getPriority();	// 获取优先级
public final void setPriority(int newPriority); // 设置优先级

线程控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
线程休眠
public static void sleep(long millis)

线程加入
public final void join():等待该线程终止,为了让某些线程执行完毕别人才可以执行。

线程礼让
public static void yield():暂停正在执行的线程对象,并执行其他线程代码。在一定程度上礼让,但不能保证就是你执行一次我执行一次。

后台线程
public final void setDaemon(boolean on):将线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java虚拟机退出,该方法必须在启动线程前调用。
举例:坦克大战中的坦克就是守护老巢的守护线程,当老巢报废后,结束!

中断线程
public final void stop():过于暴力,直接停止线程,后面的代码也不会执行。
public void interrupt():把线程的状态终止,并抛出一个异常,后面的代码还会继续执行完。

线程的生命周期

新建、就绪、运行、阻塞、死亡。

线程生命周期

线程安全

线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

卖电影票案例出现的问题

为了更符合真实的场景,加入了休眠100毫秒。卖电影票案例

问题1:相同的票出现多次

CPU的一次操作必须是原子性的,例如 ticket– 就不是原子性的操作。

问题2:还出现了负数的票

随机性和延迟导致的

多线程安全问题的原因

  1. 是否有多线程环境
  2. 是否有共享数据
  3. 是否有多条语句操作共享数据

解决多线程安全问题

基本思想:让程序没有安全问题的环境。

把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

解决方案 1 同步代码块

同步代码块,格式如下,这里的 对象 可以是任意的对象,相当于一把钥匙,这个钥匙只能有一把。

1
2
3
synchronized(对象){
需要同步的代码;
}

举个🌰:​​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SellTickets implements Runnable {
private int tickets = 100;
private Object obj = new Object();

@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第 " + (tickets--) + " 张票");
}
}
}
}
}

解决方案 2 同步方法

把同步加在方法上,这里的锁对象是this。

举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SellTickets implements Runnable {

private int tickets = 100;

@Override
public void run() {
while (true) {
sellTickets();
}
}

private synchronized void sellTickets() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第 " + (tickets--) + " 张票");
}
}

}

解决方案 3 静态同步方法

把同步加在方法上,这里的锁对象是当前类的字节码文件对象。

举个🌰:此时锁对象是当前类的字节码文件对象 SellTickets.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SellTickets implements Runnable {

private static int tickets = 100;

@Override
public void run() {
while (true) {
sellTickets();
}
}

private static synchronized void sellTickets() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第 " + (tickets--) + " 张票");
}
}
}

JDK5 中 Lock 锁的使用

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。

1
2
3
4
// Lock 接口
void lock();
void unlock();
// ReentrantLock 是Lock的实现类

举个🌰:

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 SellTickets implements Runnable {

private int tickets = 100;

// 定义锁对象
private Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();

if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售 " + (tickets--) + " 张票");
}
} finally {

// 释放锁
lock.unlock();
}
}
}
}

线程同步的特点

同步的前提:多个线程、多个线程使用的是同一个锁对象

同步的好处:同步的出现解决了多线程的安全问题。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

这就是线程同步,效率低。不同步效率高的解释。

Q & A

Q1:在集合中有一些是线程不安全的集合,当需要使用多线程的时候怎么办?

用Collections工具类的方法把一个线程不安全的集合类变成一个线程安全的集合类。例如:List<String> list1 = Collections.synchronizedList(new ArrayList<String>());

Q2:那么,到底使用谁?

如果锁对象是this,就可以考虑使用同步方法。 否则能使用同步代码块的尽量使用同步代码块。

死锁问题

同步弊端

  • 效率低
  • 如果出现了同步嵌套,就容易产生死锁问题

死锁问题及其代码

  • 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

举个🌰:

1
2
3
4
5
// MyLock.java
public class MyLock {
public static final Object objA = new Object();
public static final Object objB = new 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
26
27
28
// DieLock.java
public class DieLock extends Thread{

private boolean flag;

public DieLock(boolean flag) {
this.flag = flag;
}

@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
// DieLockDemo.java
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);

dl1.start();
dl2.start();
}
}

死锁

线程间通信

针对同一个资源的操作有不同种类的线程。

举例:卖票有进的,也有出的。

通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作。

生产者、消费者

等待唤醒机制

Object 类中提供了三个方法:

1
2
3
wait():等待唤醒,立即释放锁,将来醒过来的时候是从此处醒来。 
notify():唤醒,唤醒并不代表可以立马执行,还必须抢CPU执行权
notifyAll():唤醒所有线程

这些方法的调用,必须通过锁对象来调用。

进程间通信,等待唤醒机制

举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// GetThread.java
public class GetThread implements Runnable {

private Student s = new Student();

public GetThread(Student s) {
this.s = s;
}

@Override
public void run() {
while (true) {
synchronized (s) {
s.get();
}
}
}

}
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
// SetThread.java
public class SetThread implements Runnable {

private Student s = new Student();
private int x = 0;

public SetThread(Student s) {
this.s = s;
}

@Override
public void run() {
while (true) {
synchronized (s) {
if (x % 2 == 0) {
s.set("xiaohei", 21);
} else {
s.set("baozi", 20);
}
x++;
}
}
}

}
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
// Student.java
public class Student {
private String name;
private int age;
private boolean flag;

public synchronized void set(String name, int age) {
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

this.name = name;
this.age = age;

this.flag = true;
this.notify();
}

public synchronized void get() {
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println(this.name + "---" + this.age);
this.flag = false;
this.notify();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// StudentDemo.java
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();

GetThread gt = new GetThread(s);
SetThread st = new SetThread(s);

Thread t1 = new Thread(gt);
Thread t2 = new Thread(st);

t1.start();
t2.start();
}
}

Q & A

Q:wait()、notify()、notifyAll(),用来操作线程为什么定义在了Object类中?

这些方法存在与同步中。

使用这些方法时必须要标识所属的同步的锁。

锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

线程组

Java中使用ThreadGroup来表示线程组。

它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

默认情况下,所有的线程都属于主线程组。

1
public final ThreadGroup getThreadGroup();

也可以给线程设置分组

1
Thread(ThreadGroup group, Runnable target, String name);

线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:

1
2
3
4
5
6
7
public static ExecutorService newCachedThreadPool();
创建一个具有缓存功能的线程池
缓存:百度浏览过的信息再次访问
public static ExecutorService newFixedThreadPool(int nThreads);
创建一个可重用的,具有固定线程数的线程池
public static ExecutorService newSingleThreadExecutor();
创建一个只有单线程的线程池,相当于上个方法的参数是1

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:

1
2
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);

线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

1
2
3
4
5
6
7
8
9
10
11
// ExecutorsDemo.java
public class ExecutorsDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2); // 创建Runnable实例

pool.submit(new MyRunnable()); // 提交Runnable实例
pool.submit(new MyRunnable()); // 提交Runnable实例

pool.shutdown(); // 关闭线程池
}
}
1
2
3
4
5
6
7
8
9
// MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

匿名内部类方式使用多线程

匿名内部类方式使用多线程

1
2
3
4
5
6
7
new Thread() {
代码…
}.start();

new Thread(new Runnable() {
代码…
}).start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + "---" + x);
}
};
}.start();

new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "---" + x);
}
}
}).start();

定时器

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。

1
2
3
4
5
6
7
8
// Timer
public Timer();
public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task,long delay,long period);

// TimerTask
public abstract void run()
public boolean cancel()

开发中,Quartz是一个完全由java编写的开源调度框架。



Java-IO

File

概述和构造方法

1
2
3
4
5
6
7
8
File类的概述
文件和目录(路径名)的抽象表示形式
仅仅是一个路径的表示,不代码具体的事物一定是存在的。

构造方法
public File(String pathname):根据路径得到 File 对象
public File(String parent,String child):根据一个目录和一个子文件(或目录)得到一个 File 对象
public File(File parent,String child):根据一个父 File 对象和一个子文件(或目录)得到一个File对象

成员方法

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
创建功能
public boolean createNewFile():创建文件,存在则不创建
public boolean mkdir():创建文件夹,存在则不创建,要想在某个目录下创建内容,该目录必须存在。
public boolean mkdirs():创建多级文件夹,父文件夹不存在就创建,存在则不创建。

删除功能
public boolean delete():Java 中的删除不走回收站,还要注意,文件夹里面有东西是删除不了的。

重命名功能
public boolean renameTo(File dest):注意这个和 mv 命令有些像,前后路径不一样,做移动并改名。

判断功能
public boolean isDirectory():判断是否位文件
public boolean isFile():判断是否为文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏

基本获取功能
public String getAbsolutePath():获取绝对路径
public String getPath():获取相对路径
public String getName():获取名称
public long length():获取长度,字节数
public long lastModified():获取最后一次修改时间,返回的是毫秒值。

高级获取功能
public String[] list()
public File[] listFiles()

文件过滤器的使用栗子🌰

需求:判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FileDemo2 {
public static void main(String[] args) {
// 封装 e:\\ 目录
File file = new File("e:\\");

// 获取该目录下所有文件或者文件夹的 String 数组
// public String[] list(FilenameFilter filter)
String[] strArray = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return new File(dir, name).isFile() && name.endsWith(".jpg");
}
});

// 遍历输出
for (String s : strArray) {
System.out.println(s);
}
}
}

IO流

概述和分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
概述
IO流用来处理设备之间的数据传输
上传文件和下载文件
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中

分类(IO流的分类如果没有明确指定,默认指按照数据类型分)
按照数据流向
输入流 读入数据
输出流 写出数据
按照数据类型
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
  • 什么情况下使用哪种流呢?

    如果数据所在的文件通过 windows 自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。

    如果你什么都不知道,就用字节流。

    计算机如何识别是中文还是英文呢?

    计算机是如何识别什么时候该把两个字节转换为一个中文呢?
    在计算机中中文的存储分两个字节:

    • 第一个字节肯定是负数。
    • 第二个字节常见的是负数,可能有正数。但是没影响。

    看下面的栗子🌰:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Test {
    public static void main(String[] args) {
    String en = "ANDYOU";
    byte[] en_b = en.getBytes();
    System.out.print("en_b : ");
    for (byte b : en_b) {
    System.out.print(b + " ");
    }

    System.out.println("\n------------");

    String zh = "爱你";
    byte[] zh_b = zh.getBytes();
    System.out.print("zh_b : ");
    for (byte b : zh_b) {
    System.out.print(b + " ");
    }
    }
    }

    输出:

    1
    2
    3
    en_b : 65 78 68 89 79 85 
    ------------
    zh_b : -25 -120 -79 -28 -67 -96

常用基类

1
2
3
4
5
6
7
字节流的抽象基类:
InputStream ,OutputStream。
字符流的抽象基类:
Reader , Writer。
由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader。

字节流

1
2
3
4
5
6
java.lang.Object
|---java.io.InputStream
|---java.io.OutputStream

public abstract class InputStream; //此抽象类是表示字节输入流的所有类的超类。
public abstract class OutputStream; //此抽象类是表示输出字节流的所有类的超类。

字节缓冲流

1
2
3
4
5
6
7
8
9
10
11
java.lang.Object
|---java.io.InputStream
|---java.io.FilterInputStream
|---java.io.BufferedInputStream
|---java.io.BufferedOutputStream

public class BufferedInputStream;
// 在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。

public class BufferedOutputStream;
// 该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

字符流

1
2
3
4
5
6
java.lang.Object
|---java.io.Reader
|---java.io.Writer

public abstract class Reader; // 用于读取字符流的抽象类。
public abstract class Writer; // 写入字符流的抽象类。

字符缓冲流

1
2
3
4
5
6
7
8
9
10
11
12
13
java.lang.Object
|---java.io.Reader
|---java.io.BufferedReader
|---java.io.BufferedWriter

public class BufferedReader; // 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
public class BufferedWriter; // 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

特殊功能,根据系统来读取或写入换行符
BufferedWriter
void newLine()
BufferedReader
String readLine()

注意事项

1
2
3
4
5
字符流操作要注意的问题
flush()的作用
flush()和close()的区别
clone()关闭流对象,但是要刷新一下缓冲区,关闭后流对象不可用。
flush()刷新流对象,刷新完流对象还可以用。

转换流

转换流出现的原因及思想

1
2
由于字节流操作中文不是特别方便,所以,java就提供了转换流。
字符流=字节流+编码表。

编码表概述和常见编码表

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
编码表
由字符及其对应的数值组成的一张表
常见编码表
ASCII:美国标准信息交换码。
用一个字节的7位可以表示。

ISO8859-1:拉丁码表。欧洲码表
用一个字节的8位表示。

GB2312:中国的中文编码表,简体中文。

GBK:中国的中文编码表升级,融合了更多的中文文字符号。

GB18030:GBK的取代版本

BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”。

Unicode:国际标准码,融合了多种文字。
所有文字都用两个字节来表示,Java语言使用的就是unicode

UTF-8:最多用三个字节来表示一个字符。

UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:
它将Unicode编码为00000000-0000007F的字符,用单个字节来表示
它将Unicode编码为00000080-000007FF的字符用两个字节表示
它将Unicode编码为00000800-0000FFFF的字符用3字节表示

转换流概述

1
2
3
4
5
6
7
OutputStreamWriter 字符输出流
public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out,String charsetName)

InputStreamReader 字符输入流
public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in,String charsetName)

转换流简化

1
2
3
转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化我们的书写,转换流提供了对应的子类。
FileWriter
FileReader

其他流

数据操作流

1
2
3
操作基本数据类型的类:
DataInputStream
DataOutputStream

内存操作流

1
2
3
4
5
6
7
8
9
10
11
内存操作流一般用于处理临时信息,因为临时信息不需要保存,使用后就可以删除。

操作字节数组
ByteArrayInputStream
ByteArrayOutputStream
操作字符数组
CharArrayReader
CharArrayWrite
操作字符串
StringReader
StringWriter

打印流

1
2
3
4
5
6
7
8
9
打印流概述
字节流打印流 PrintStream
字符打印流 PrintWriter

打印流特点
只能操作目的地,不能操作数据。
可以操作任意类型的数据。
如果启动了自动刷新,能够自动刷新。
可以操作文件的流

标准输入输出流

1
2
3
4
5
System类中的字段:in,out。
它们各代表了系统标准的输入和输出设备。
默认输入设备是键盘,输出设备是显示器。
System.in的类型是InputStream.
System.out的类型是PrintStream是OutputStream的子类FilterOutputStream 的子类.

随机访问流

1
2
3
4
5
6
7
8
RandomAccessFile概述
RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。支持对随机访问文件的读取和写入。

访问模式
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。

合并流

1
2
3
4
5
SequenceInputStream类可以将多个输入流串流在一起,合并为一个输入流,因此,该流也被称为合并流。

SequenceInputStream的构造方法
SequenceInputStream(InputStream s1, InputStream s2);
SequenceInputStream(Enumeration<? extends InputStream> e);

序列化流

1
2
3
4
5
6
7
8
9
将对象写入文件,传输。。。
对象序列化是将对象状态转换为可保持或传输的过程。一般的格式是与平台无关的二进制流,可以将这种二进制流持久保存在磁盘上,也可以通过网络将这种二进制流传输到另一个网络结点。
对象反序列化,是指把这种二进制流数据还原成对象。

序列化流
ObjectOutputStream

反序列化流
ObjectInputStream
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
序列化操作问题
为什么要实现序列化?
如何实现序列化?
序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?

使用transient关键字声明不需要序列化的成员变量
NotSerializableException:未序列化异常

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
该接口居然没有任何方法,类似于这种没有方法的接口被称为标记接口。

java.io.InvalidClassException:
cn.itcast_07.Person; local class incompatible:
stream classdesc serialVersionUID = -2071565876962058344,
local class serialVersionUID = -8345153069362641443

为什么会有问题呢?
Person类实现了序列化接口,那么它本身也应该有一个标记值。
这个标记值假设是100
开始的时候:
Person.class -- id=100
wirte数据: oos.txt -- id=100
read数据: oos.txt -- id=100

现在:
Person.class -- id=200
wirte数据: oos.txt -- id=100
read数据: oos.txt -- id=100
我们在实际开发中,可能还需要使用以前写过的数据,不能重新写入。怎么办呢?
回想一下原因是因为它们的id值不匹配。
每次修改java文件的内容的时候,class文件的id值都会发生改变。
而读取文件的时候,会和class文件中的id值进行匹配。所以,就会出问题。
但是呢,如果我有办法,让这个id值在java文件中是一个固定的值,这样,你修改文件的时候,这个id值还会发生改变吗?
不会。现在的关键是我如何能够知道这个id值如何表示的呢?
不用担心,你不用记住,也没关系,点击鼠标即可。
你难道没有看到黄色警告线吗?

我们要知道的是:
看到类实现了序列化接口的时候,要想解决黄色警告线问题,就可以自动产生一个序列化id值。
而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的。

注意:
我一个类中可能有很多的成员变量,有些我不想进行序列化。请问该怎么办呢?
使用transient关键字声明不需要序列化的成员变量

Properties 集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Properties:属性集合类。是一个可以和IO流相结合使用的集合类。
Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
是Hashtable的子类,说明是一个Map集合。

Properties作为Map集合的使用

Properties的特殊功能
public Object setProperty(String key,String value):添加元素
public String getProperty(String key):获取元素
public Set<String> stringPropertyNames():获取所有键的集合

Properties和IO流的结合使用
Properties集合!
public void load(Reader reader):把文件中的数据读取到集合中
public void store(Writer writer,String comments):把集合中的数据存储到文件中

NIO

NIO其实就是新IO的意思。

JDK4出现NIO。新IO和传统的IO有相同的目的,都是用于进行输入输出的,但新IO使用了不同的方式来处理输入输出,采用内存映射文件的方式,将文件或者文件的一段区域映射到内存中,就可以像访问内存一样的来访问文件了,这种方式效率比旧IO要高很多,但是目前好多地方我们看到的还是旧IO的引用,所以我们仍以旧IO为主,知道NIO即可。

JDK7的IO改进

  • Path:与平台无关的路径。

  • Paths:包含了返回Path的静态方法。

    public static Path get(URI uri):根据给定的URI来确定文件路径。

  • Files:操作文件的工具类。提供了大量的方法,简单了解如下方法

    public static long copy(Path source, OutputStream out) :复制文件

    public static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption… options):

    把集合的数据写到文件。


Java-异常

拉妹子的手,可能会出错,出错了就道歉,没出错就继续。(哈哈哈)

异常:异常就是Java程序在运行过程中出现的错误。

异常由来:问题也是现实生活中一个具体事务,也可以通过 Java 的类的形式进行描述,并封装成对象。其实就是Java 对不正常情况进行描述后的对象体现。

阅读更多
Java-集合框架

Collection集合

Collection集合总结

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
Collection
|---List 有序,可重复
|---ArrayList
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高
|---Vector
底层数据结构是数组,查询快,增删慢。
线程安全,效率低
|---LinkedList
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高
|---Set 无序,唯一
|---HashSet
底层数据结构是哈希表。
如何保证元素唯一性的呢?
依赖两个方法:hashCode()和equals()
开发中自动生成这两个方法即可
|---LinkedHashSet
底层数据结构是链表和哈希表
由链表保证元素有序
由哈希表保证元素唯一
|---TreeSet
底层数据结构是红黑树。
如何保证元素排序的呢?
自然排序
比较器排序
如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定

Collection使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
针对Collection集合我们到底使用谁呢?(掌握)
唯一吗?
是:Set
排序吗?
是:TreeSet
否:HashSet
如果你知道是Set,但是不知道是哪个Set,就用HashSet。

否:List
要安全吗?
是:Vector
否:ArrayList或者LinkedList
查询多:ArrayList
增删多:LinkedList
如果知道是List,但是不知道是哪个List,就用ArrayList。

如果知道是Collection集合,但是不知道使用谁,就用ArrayList。

如果知道用集合,就用ArrayList。

遍历方式

  1. 迭代器遍历
    • 注意:并发修改异常!
  2. 增强for
    • 注意:底层其实就是迭代器遍历!
  3. 普通for

集合中常见数据结构

1
2
3
4
5
在集合中常见的数据结构(掌握)
ArrayXxx : 底层数据结构是数组,查询快,增删慢
LinkedXxx : 底层数据结构是链表,查询慢,增删快
HashXxx : 底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx : 底层数据结构是二叉树。两种方式排序:自然排序和比较器排序

Map集合

将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。

Map集合总结

1
2
3
4
5
6
7
Map
|---HashMap
键是哈希表结构,可以保证键的唯一性
|---LinkedHashMap
Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。
由哈希表保证唯一性
由链表保证有序性(存储和取出的顺序一致)

Map和Collection的区别

Map 存储的是键值对形式的元素,键唯一,值可以重复。夫妻对
Collection 存储的是单独出现的元素,子接口Set元素唯一,子接口List元素可重复。光棍

遍历方式

  1. 键找值
    • 获取所有键的集合
    • 遍历键的集合,得到每一个键
    • 根据键到集合中去找值
  2. 键值对对象找键和值
    • 获取所有的键值对对象的集合
    • 遍历键值对对象的集合,获取每一个键值对对象
    • 根据键值对对象去获取键和值

栗子🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Map<String,String> hm = new HashMap<String,String>();

hm.put("it002","hello");
hm.put("it003","world");
hm.put("it001","java");

//方式1 键找值
Set<String> set = hm.keySet();
for(String key : set) {
String value = hm.get(key);
System.out.println(key+"---"+value);
}

//方式2 键值对对象找键和值
Set<Map.Entry<String,String>> set2 = hm.entrySet();
for(Map.Entry<String,String> me : set2) {
String key = me.getKey();
String value = me.getValue();
System.out.println(key+"---"+value);
}

Collections工具类

1
2
3
4
5
6
7
8
9
10
(1)是针对集合进行操作的工具类
(2)面试题:Collection和Collections的区别
A:Collection 是单列集合的顶层接口,有两个子接口List和Set
B:Collections 是针对集合进行操作的工具类,可以对集合进行排序和查找等
(3)常见的几个小方法:
A:public static <T> void sort(List<T> list)
B:public static <T> int binarySearch(List<?> list,T key)
C:public static <T> T max(Collection<?> coll)
D:public static void reverse(List<?> list)
E:public static void shuffle(List<?> list)

注意:在Collection中有一些线程不安全的集合类,当在需要多线程操作的时候,可以使用这个工具类来将线程不安全的集合转为一个线程安全的集合。



Java-初识正则表达式

正则表达式概述及基本使用

指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。其实就是一种规则。有自己特殊的应用。

常见规则

规则字符在java.util.regex包下的Pattern类中。

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
A:字符
x 字符 x。举例:'a'表示字符a
\\ 反斜线字符。
\n 新行(换行)符 ('\u000A')
\r 回车符 ('\u000D')

B:字符类
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a到 z 或 A到 Z,两头的字母包括在内(范围)
[0-9] 0到9的字符都包括

C:预定义字符类
. 任何字符。我的就是.字符本身,怎么表示呢? \.
\d 数字:[0-9]
\w 单词字符:[a-zA-Z_0-9]
在正则表达式里面组成单词的东西必须有这些东西组成

D:边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
就是不是单词字符的地方。
举例:hello world?haha;xixi

E:Greedy 数量词
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次

常用功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
A:判断功能
String类的public boolean matches(String regex):判断此字符串是否匹配给定的正则表达式

B:分割功能
String类的public String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

C:替换功能
String类的public String replaceAll(String regex,String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

D:获取功能
Pattern和Matcher
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");

find():查找存不存在
group():获取刚才查找过的数据

获取功能的典型调用如下:

1
2
3
4
5
6
7
8
9
// 典型的调用如下
// 模式和匹配器的典型调用顺序
// 把正则表达式编译成模式对象
Pattern p = Pattern.compile("a*b");
// 通过模式对象得到匹配器对象,这个时候需要的是被匹配的字符串
Matcher m = p.matcher("aaaaab");
// 调用匹配器对象的功能
boolean b = m.matches();
System.out.println(b);

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 获取功能:
* 获取下面这个字符串中由三个字符组成的单词
* da jia ting wo shuo,jin tian yao xia yu,bu shang wan zi xi,gao xing bu?
*/
public class RegexDemo2 {
public static void main(String[] args) {
// 定义字符串
String s = "da jia ting wo shuo,jin tian yao xia yu,bu shang wan zi xi,gao xing bu?";
// 规则
String regex = "\\b\\w{3}\\b";

// 把规则编译成模式对象
Pattern p = Pattern.compile(regex);
// 通过模式对象得到匹配器对象
Matcher m = p.matcher(s);

while (m.find()) {
System.out.println(m.group());
}
}
}


C-结构体

结构体内存对齐的知识总结。

阅读更多
Java-基础

名词释义

JVMJava Virtual Machine,Java虚拟机

因为有了 JVM,所以同一个 Java 程序在三个不同的操作系统中都可以执行。这样就实现了 Java 程序的跨平台性。也称 Java 具有良好的可移植性。

JREJava Runtime Environment,Java运行环境

包括Java虚拟机和 Java 程序所需的核心类库等,如果想要运行一个开发好的 Java 程序,计算机中只需要安装 JRE 即可。

JDKJava Developme Kit,Java开发工具包

JDK 是提供给开发人员使用的,其中包含了 Java 的开发工具,也包括了 JRE 。所以安装了JDK,就不用再单独安装 JRE 了。其中的开发工具:编译工具 Javac.exe ,打包工具 Jar.exe 等。

简单而言:使用 JDK 开发完成的 Java 程序,交给 JRE 去运行,由 JVM 保证跨平台。

命名规则

在开发中命名我们要尽量做到“见名知意”,这是一个良好的习惯。

1
2
3
全部小写。
单级包:ahojcn
多级包:cn.ahoj

类或接口

1
2
一个单词:单词的首字母必须大写(Student、Dog)
多个单词:每个单词的首字母必须大写(HelloWorld、StudentName)

方法或变量

1
2
一个单词:单词的首字母小写(main、age)
多个单词:从第二个单词开始,每个单词的首字母大写(studentAge、showAllClass())

常量

1
2
一个单词:全部大写
多个单词:每个字母都大写,用_隔开

数据的表示

2、8、10、16进制

二进制:0b开头

八进制:0开头

十进制:整数默认是十进制的

十六进制:0x开头

1
2
3
4
System.out.println(0b1001);	// 2
System.out.println(0100); // 8
System.out.println(1001); // 10
System.out.println(0xffff); // 16

数据类型

Java 是强类型语言,对于每一种数据都定义了明确的具体数据类型,在内存中分配了不同大小的内存空间。

基本数据类型:

  1. 数值型:

    • 整数类型:byte(1),short(2),int(4),long(8,超过了int范围需要加L或l,建议使用L)

    • 浮点类型:float(4,单精度浮点数用f或F标记,建议使用F,不加默认是double类型),double(8)

    • 字符型:char(2)

    • 布尔型:boolean(1)

  2. 引用数据类型:

    • 类:class

    • 接口:interface

    • 数组:[]

变量使用注意事项: 作用域,初始化值,一行建议只定义一个变量。

强制类型转换

注意:boolean类型不能转换为其他的数据类型

  1. 默认转换

    • byte,short,char => int => long => float => double

    • byte,short,char 相互之间不转换,他们参与运算首先转换为 int 类型

  2. 强制转换

    • 目标类型 变量名 = (目标类型)(被转换的数据)

    例如:byte c = (byte)(a+b);

一些特别的

  1. 跳出多重循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class test {
    public static void main(String[] args) {

    OUT: // 相当于给循环起了个名字
    for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
    for (int k = 0; k < 10; k++) {
    System.out.println("test");
    break OUT;
    }
    }
    }

    }
    }
  2. ….



跳表

此文是学习《数据结构与算法之美》时的笔记。

阅读更多