1.JavaIO
1.1实现自定义输入流将文件中的小写字母和大写字母互相转换
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyInputStream extends FilterInputStream {
protected MyInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = 0;
if((c=super.read())!=-1) {
if(Character.isLowerCase((char)c)) {
return Character.toUpperCase((char)c);
}else if(Character.isUpperCase((char)c)) {
return Character.toLowerCase((char)c);
}else {
return c;
}
}else {
return -1;
}
}
public static void main(String[] args) throws IOException {
int c;
String path = "src//junhaox/cn/io/a.txt";
File file = new File(path);
MyInputStream in = new MyInputStream(new BufferedInputStream(new FileInputStream(file)));
while((c=in.read())>0) {
System.out.print((char)c);
}
}
}
1.2列出指定目录下所有目录和文件
import java.io.File;
public class Demo01 {
public static void main(String[] args) {
String path = "E:\\Typora文档\\JavaStudy";
File file = new File(path);
if(!file.exists()) {
System.out.println("file not found");
return;
}
File[] fileList = file.listFiles();
for (File f : fileList) {
if(f.isDirectory()) {
System.out.println("directory is " + f.getName());
}else {
System.out.println("file is " + f.getName());
}
}
}
}
1.3列出指定目录下所有的文件及个数
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
public static void getAllFileName( String path, List<String> fileList) {
File file = new File(path);
if(file.exists()) {
for(File f : file.listFiles()) {
if(f.isDirectory()) {
getAllFileName(f.getAbsolutePath(), fileList);
}else {
fileList.add(f.getName());
}
}
}else {
throw new MyFileNotFoundExcepiton("文件不存在");
}
}
public static void main(String[] args) {
List<String> fileList = new ArrayList<>(100);
String path = "E:\\Typora文档";
getAllFileName(path, fileList);
for(String fileName:fileList) {
System.out.println(fileName);
}
System.out.println(fileList.size());
}
}
2.Java Socket
2.1面向连接的通信TCP
Server
- 指定端口 使用ServerSocket创建服务器
- 阻塞式等待连接 accept
- 操作:输入输出流
- 释放资源
Client
- 指定端口 使用Socket创建客户端 + 服务器的地址和端口,此时已经连接
- 操作:输入输出流
- 释放资源
2.2面向无连接的通信UDP
3.Java NIO
3.1NIO(Noblocking IO)
- NIO采用了Reactor(反应器)设计模式
4.Java序列化
4.1序列化
- 所有序列化的对象都要实现Serializable接口
- 构造一个对象输出流ObjectOutputStream,使用writeObject方法就可以将对象写出
- 如果一个类可以被序列化则它的子类也可以被序列化
- static类型的变量,和用transient修饰的变量不能够被序列化
- 序列化会影响系统性能,非必须就不要进行序列化
- 序列化和反序列化可以实现对象的深复制
- 进行序列化和反序列化时应显式的声明一个static final类型的serialVersionUID,可以提高不同系统的兼容性,提高系统性能
4.2外部序列化
实现Externalizable接口,自己重写其中的方法,开发难度较大,但是较灵活
package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
4.3System.out.println()
向其中传入任何可以转为字符串类型的数据,或者传入一个自定义toString方法的对象,就可以实现在控制台打印的效果
如果传入的是一个表达式则从左往右一次计算
System.out.println(1+2+"");//3
System.out.println(""+1+2);//12
5.Java的平台与内存管理
5.1Java的平台独立性
- Java程序—->class字节码—->解释执行成机器码
- 不同平台装有不同的JVM,JVM负责将class字节码解释或编译为特定平台需要的机器码
- 所以Java程序具有平台独立性,依赖于JVM,但是JVM不具有平台独立性,每个平台需要有自己的JBM
- Java成员运行的顺序:Java程序—>JRE/JVM—>操作系统—>硬件
5.2Java平台
- Java平台是一个纯软件的平台,它可以运行在一些基于硬件的平台(例如Linux、Windows)
- JVM是一个虚拟出来的计算机,他可以将Java编译生成的中间代码转换为机器可以识别的编码并运行
- JVM具有完善的硬件架构,例如处理器、寄存器、堆栈、内存等
- JVM屏蔽了平台相关信息,使得Java代码只需要编译生成能在JVM上运行的class字节码就可以在不同操作系统不加修改的运行
- 每运行一个Java程序就会启动一个JVM实例,只有当程序结束后JVM实例才会退出,JVM是通过类的main方法来启动的
5.3JVM加载class文件的原理
- 类加载器本身也是一个类,其实质是将class文件从硬盘中加载到内存中
- 类的加载分为显式加载和隐式加载,显式加载是通过class.forName()方法来把所需要的类加载到JVM中;隐式加载是程序在使用new等方式创建对象时,会隐式的调用类的加载器,把对应的类加载到JVM中
- 当程序启动时只会把需要的类加载到JVM,其他类在使用时才会加载。
类加载的步骤:
- 装载:根据查找路径找到对应的class文件,然后导入
连接:
- 检查:检查待加载的class文件的正确性
- 准备:给类中的静态变量分配存储空间
- 解析:将符号引用转换成直接引用(可选)
- 初始化:对静态变量,和静态代码块执行初始化工作
5.4Java中的堆和栈
- 基本数据类型(int,short,long,byte等)和对象类型的引用变量都存储在栈中
- 引用类型所引用的对象都存储在 常量池或堆中
6.Java Collections框架
6.1基本结构
- 具体包括List,Queue,Set,Stack和Map等数据结构
- List,Queue,Set,Stack继承自Collection接口
Set:单值集合,元素不可重复,添加自定义对象时需要重写hashCode和equals方法
- HashSet:底层由HashMap完成,无序
- TreeSet:有序集合,如果需要自定义排序则要传入一个Comparator接口(可以用lamda表达式)
List:单值集合,元素可以重复,有序(按对象进入的顺序保存对象)
- LinkedList:底层链表,适合快速增删改
- ArrayList:底层数组,适合快速查询
- Vector:线程安全,效率较低,被舍弃
Map:多值集合(k-v对形式的集合),无序集合,键不可以重复,当对象作为键时需要重写hashCode和equals方法,值可以重复
- HashMap:数组+链表的数据结构
- TreeMap:有序集合,可以根据key进行排序
- LinkedHashMap
- WeakHashMap
- IdentityHashMap
6.2迭代器
- 调用容器的iterator()方法就可以的到一个迭代器
- 通过迭代器的hasNext()方法判断是否还有下一个元素,通过next()方法返回容器的第一个元素
- 还可以通过remove()方法删除迭代器返回的元素
- ListIterator只存在于List中可以双向迭代集合,是Iterator的子类
多线程下的并发容器:
- ConcurrentHashMap
- CopyOnWriteArrayList
7.多线程
7.1为什么使用多线程
- 线程被称为轻量级进程,是程序执行的最小单元,一个进程可以具有多个线程,他们之间共享程序的内存空间(代码段,数据段,堆空间)和一些进程级的资源(例如打开的文件),但是各个线程拥有自己的栈空间
- 操作系统级别上都是以进程为单位,每个进程有多个线程并发的执行
- 使用多线程可以减少程序的响应时间
- 与进程相比,线程的创建和切换消耗更小
7.2同步和异步
同步:
- 同步是指当多个线程要操作(读写)一个数据时,只能排队进行,只有拿到锁的线程才能对数据进行操作,其他线程必须等待
- 当拿到锁的线程释放锁时,其他线程才能获取所进而进行操作
异步:
- 每个线程都拥有自己的数据或方法,因此当系统调用其他耗时的线程时,并不希望程序等待,就可以使用异步方法
7.3实现多线程的方式
继承Thread类,重写run方法,创建实例,调用start方法
实现Runnable接口,重写run方法,创建Thread对象,并将该类的实例传给Thread构造方法 ```java class MyThread impliments Runnable{ public void run(){
} }
new Thread(new MyThread()).start();
-
实现Callable接口,重写call方法
- call方法可以抛异常,run方法不能抛异常
- call方法可以在任务结束后提供一个返回值
```java
package junhaox.cn.thread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Thread01 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Callable的启动依赖FutureTask
FutureTask<Integer> fuTask = new FutureTask<>(()->{
int sum = 1;
for(int i=0; i<100; i++) {
sum += i;
System.out.println(Thread.currentThread().getName()+i);
}
return sum;
});
new Thread(fuTask).start();
// 执行完毕后通过FutureTask获取返回值
Integer sum = fuTask.get();
System.out.println(sum);
}
}
7.4run方法和start方法
- 系统通过调用类的start方法来启动一个线程,此刻该线程就处于就绪状态,而非运行状态,表示该线程可以被JVM调度,通过run方法来进行具体操作,当run方法结束线程结束
- 如果直接调用线程类的run方法,则相当于调用一个普通方法,当执行到该方法时就会直接执行,而不会通过JVM的调度
7.5多线程同步的方法
1. 通过synchronized
在Java语言中每个对象都有一个对象锁,该锁表明对象在任何时候都只允许被一个线程所拥有,当一个线程调用对象的synchronize代码时需要先获得这个对象的锁,然后彩笔执行响应的代码
synchronize方法,在方法前加上synchronize关键字例如
public synchronized void fun()
,只要将需要同步的资源放到同步方法中就可以在同一时刻只能被一个线程访问synchronize块,synchronize块既可以把任意代码段声明为synchronize,也可以指定上锁的对象,有非常高的灵活性
synchronize(syncObject){
// do something...s
}
2. wait()和notify()
- 当使用synchronize修饰某个共享资源时,如果T1正在执行synchronize代码,另外一个线程T2也要同时执行同一对象的同一synchronize代码时,线程T2需要等待T1执行完毕后才能继续执行,如果不希望如此则可以使用wart()和notify()方法来切换线程
- 在synchronize代码被执行阶段如果调用wait()方法则会释放锁对象,进入等待状态,并且可以调用notify()或notifyAll()方法,通知其他正在等待的线程一起参与竞争(可能还是原来的线程竞争到锁对象)
- notify()是唤醒等待队列的第一个线程,notifyAll()是唤醒所有线程
3. Lock
JDK1.5中新增了Lock接口以及他的实现类ReentrantLock(重入锁),Lock也可以用来实现线程同步
Lock中的方法
- lock(),以阻塞的方式获取锁,如果获取到则立即返回,如果别的线程持有锁则当前线程等待直到获取所后返回
- tryLock(),以非阻塞的方式获取锁,只是尝试性的获取锁,如果获取锁则立即返回true,否则立即返回false
- tryLock(long timeout, TimeUnit unit)。如果获取锁则立即返回true,否则等待给定实际timeout,等待过程中获取锁则立即返回true,如果等待超时则返回false
- lockInterruptibly(),如果获取锁立即返回true,如果没有获取锁则当前线程处于休眠状态,直到获取锁,或者被其他线程中断(会收到InterruptedException异常,被要求处理该异常),它与lock方法的区别是lock获取不到锁会一直阻塞,而它可能别打断
使用Lock接口代替synchronize实现生产消费者模式
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProAdnCons {
public static void main(String[] args) {
Container container = new Container();
new Thread(new Consumer(container)).start();
new Thread(new Productor(container)).start();
}
}
// 产品
class Steamedbun{
private int id;
public int getId() {
return id;
}
public Steamedbun(int i) {
this.id = i;
}
}
// 生产者
class Productor implements Runnable{
private Container container;
public Productor(Container container) {
this.container = container;
}
@Override
public void run() {
for(int i=0; i<1000; i++) {
Steamedbun bun = new Steamedbun(i);
System.out.println("生产第"+i+"个产品");
container.push(bun);
}
}
}
// 消费者
class Consumer implements Runnable{
private Container container;
public Consumer(Container container) {
this.container = container;
}
@Override
public void run() {
for(int i=0; i<1000; i++) {
Steamedbun bun = container.pop();
System.out.println("消费第"+bun.getId()+"个产品");
}
}
}
// 容器
class Container{
private final Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
// 存放数据的缓冲区
private Steamedbun[] buns = new Steamedbun[10];
// 计数器
private int count = 0;
public void push(Steamedbun bun) {
lock.lock();
// 如果count==buns.length,容器满了,需等待消费
try {
if(count == buns.length) {
condition1.await();
}
// 容器不满,则继续生产
buns[count] = bun;
count ++;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public Steamedbun pop() {
lock.lock();
// 如果count==0,说明没有,等待生产
Steamedbun bun = null;
try {
if(count==0) {
condition2.await();
}
// count > 0, 则取出一个产品
count--;
bun = buns[count];
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return bun;
}
}
7.6sleep()和wait()方法的区别
sleep()是使线程暂停执行一段时间的方法。wait()也是让线程暂停执行的方法
- sleep()是Thread类的静态方法,它会使线程暂停执行一段时间,而把执行机会让给其他线程,时间一结束就会自动苏醒;wait()是Object中的方法,用于线程间的通信,该方法可以使拥有对象锁的线程进入等待状态,只有其他线程调用notify或notifyAll方法时才醒来
- sleep()方法不会释放锁;wait()方法会释放锁进入等待状态,等待其他线程将其唤醒然后重新竞争锁资源
- wait()必须放在同步控制方法或者同步块中使用,应为wait()、notify、notifyAll方法都涉及到锁对象的释放或者提醒可以重新获取锁对象,当没有锁的时候就没有任何意义;sleep()方法可以在任何地方使用
- sleep()方法必须捕获异常,应为sleep()没有释放锁对象,如果不捕获当发生InterruptedException时,将无法继续往下执行,只有捕获异常后才能继续执行catch块(finally块)中的代码;wait()方法本身释放锁资源,所以可以不捕获异常
sleep()和yield()方法区别
- sleep方法给其他线程运行机会时不会考虑优先级,而yield只会让给和它同一个优先级或者优先级比他高的线程
- sleep方法调用后会进入阻塞状态,在指定时间内一定不会执行;而执行yield方法的线程会重新进入可执行状态,所以执行yield方法后可能有马上执行
- sleep会抛出InterruptedException异常,yield没有任何异常
- sleep比yield具有更好的移植性
7.7终止线程的方法
不推荐使用stop()和suspend()方法的原因
stop方法会终止线程的执行,会释放该线程对象已经锁定的所有监视器,导致它所监视的对象处于不一致的状态,从而导致程序的不确定性
suspend被弃用的原因是因为它会造成死锁。suspend方法和stop方法不一样,它不会破换对象和强制释放锁,相反它会一直保持对锁的占有,一直到其他的线程调用resume方法,它才能继续向下执行。 假如有A,B两个线程,A线程在获得某个锁之后被suspend阻塞,这时A不能继续执行,线程B在或者相同的锁之后才能调用resume方法将A唤醒,但是此时的锁被A占有,B不能继续执行,也就不能及时的唤醒A,此时A,B两个线程都不能继续向下执行而形成了死锁。这就是suspend被弃用的原因。
合理立即终止线程的方法
- 使用标志位的方式来终止线程
public class MyThread implements Runnable{
private volatile Boolean flag;
public void stop{
flag = false;
}
public void run{
while(flag){
// do something...
}
}
}
- 当线程处于非运行状态时(当调用sleep方法,调用wait方法,或者处于IO阻塞),标志位的方式就并不适用。此时可以使用interrupt()方法来打破阻塞,当interrupt()方法被调用时,会抛出InterruptedExceptiony异常,可以在run()方法中捕获这个异常,来让线程安全终止
public static void main(String[] args){
Thread t = new Thread(()->{
System.out.println("Thread go to sleep..");
try{
Thread.sleep(5000);
}catch(InterruptedExceptiony e){
System.out.println("Thread is interrupted..");
}
});
t.start();
t.interrupt();
}
当线程并是应为IO阻塞而进入非运行状态时,需要等到IO结束后才能终止线程,此时无法使用interrupt()方法来终止线程,但是也可以通过触发一个异常来立即安全的终止一个线程。例如在等待读取网络资源时关闭输入流,触发IOException,通过捕获IOException来安全结束线程
Thread类中有两个方法用来判断线程是否被中断,静态方法interrupted()和非静态方法isInterrupted()方法。interrupted可以用来判断当前线程是否被中断,isInterrupted可以用来判断其他线程是否被中断
public class Thread3 extends Thread {
public void run() {
while (!this.isInterrupted()) {
// while(!Thread.interrupted)也行
//do something
}
}
public static void main(String[] args) throws Exception {
Thread thread = new Thread3();
thread.start();
thread.interrupt();
thread.join();
System.out.println("线程已经退出!");
}
}
7.8Lock和synchronize的区别
- Lock是Java提供的一个接口,而synchronize是Java的关键字
- synchronize是托管给JVM执行的,而Lock是通过代码实现的
- synchronize会在发生异常时自动释放锁,Lock异常时不会释放锁,所以需要在finally中手动释放锁
- Lock是可中断锁,synchronize是不可中断锁,必须等到线程执行完毕才释放锁
- Lock可以知道线程是否得到锁,因此可以提高线程操作读的效率
- synchronize是锁一个块,可以加在方法前,也可以加在特定代码块中,括号中表示锁的对象;Lock需要显示的指定起始位置和终止位置
7.9多个线程访问同一对象的不同方法
当一个线程进入对象synchronize方法后
- 其他线程不能访问synchronize修饰的普通方法
- 其他线程可以方法synchronize修饰的静态方法
- 其他线程可以方法非synchronize修饰的方法
7.10守护线程
所有非守护线程终止,只剩下守护线程时,JVM也就退出了,程序会终止并杀死守护线程
自定义守护线程,一个普通线程启动前调用setDaemon(true)方法,就可以将一个普通线程设置为守护线程
守护线程的典型案例就是垃圾回收器 ```java public class Thread4 {
public static void main(String[] args) throws Exception {System.out.println("main:begin");
Thread t = new Thread(new MyThread());
t.setDaemon(true);
t.start();
System.out.println("main:end");
}
} class MyThread implements Runnable{@Override public void run() {
System.out.println(Thread.currentThread().getName()+":begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":end");
}
} //输出 / main:begin main:end Thread-0:begin 没有Thread-0:end 这是因为当只有守护线程时,JVM是可以退出的 当main:end输出后main线程结束 此时t线程还在休眠,所以没有Thread-0:end /
<a name="8cca13a2"></a>
#### 7.11join方法
-
join()方法(插队方法),主要使用来使线程之间同步,等待该线程终止。**等待调用join方法的线程结束**,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。在很多情况下,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
-
在A线程中调用B线程的join方法表示A放弃CPU的调度,当B执行完毕后A才能继续执行
-
join(long time),表示A线程等待B线程先执行time毫秒,time毫秒过后A、B并行执行
-
join(0)和join()等价
```java
public class JoinTest {
public static void main(String [] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("小明");
ThreadJoinTest t2 = new ThreadJoinTest("小东");
t1.start();
/**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
*/
t1.join();
t2.start();
}
}
class ThreadJoinTest extends Thread{
public ThreadJoinTest(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}
8.Java数据库操作
8.1访问数据库的步骤
- 将对应数据库的jar包加载到classpath中
- 加载驱动器,使用反射
Class.forName(String driverName)
建立数据库连接,获取Connection对象
DriverManger.getConnection(url, username, pwd)
- url连接数据库的字符串,用来定位数据库MySQL的url一般是”jdbc:mysql://localhost:3306/DbName”
- username数据库用户名
- pwd对应用户名密码
- 建立Statement对象或PreparedStatement对象(推荐)
- 执行sql
- 访问ResultSet(如果有)
- 依次关闭ResultSet,PreparedStatement,Connection(底层采用网络IO来执行sql)
8.2jdbc处理事务
1.事务的特性
- 原子性:一组事务要么全部成功要么撤回
- 一致性:事务完成后数据库状态与其他业务规则一致。例如转账业务,无论事务是否成功,两个账户的余额之和是不变的
- 隔离性:事务独立运行,一个事务处理后的结果影响了其他事务,那么其他事务会撤回,事务的100%隔离需要牺牲速度
- 持久性:软、硬件崩溃后,InnoDB数据表驱动会根据日志文件进行重构,可靠性和高速度不可兼得
2.jdbc处理事务的条件
- 关闭自动提交,
setAutoCommit(false)
- 事务完成后调用commit()方法实现整体提交
- 如果其中一个表达式操作失败会抛出异常,而不会调用commit(),可以在异常捕获的代码块中调用rollback()
3.jdbc默认事务隔离级别
- Read UnCommited:一个事务可以读取另一个未提交事务的数据,会出现脏读,读取到了脏数据
- Read Commited:一个事务要等到另一个事务提交之后才能读取数据,但是A事务开启前和结束后B事务读取的数据不同,造成了不可重复读
- Repeatable Read:重复读,就是在开始读取数据(事务开启)时,不再允许修改操作,但是可以插入数据会引起幻读
- Serializable:可序列化,最高事务级别,它防止读脏数据,不可重复读,和虚读,但是效率较低,一般不采用
8.3Class.forName的作用
- 加载类到JVM,并且执行其中的静态代码段
- JDBC要求Driver类在使用前必须向DriverManager注册自己,所以
Class.forName
,就可以加载Driver类,从而注册自己
8.4Statement、PreparedStatement和CallableStatement
- PreparedStatement比Statement效率高,ps对象执行sql时命令被解析后会放到缓冲区。之后当执行同一个ps对象时,在缓冲区会发现预编译的命令,虽然会被在解析一次,但是不会编译。而s每次都要编译
- s执行带参数的sql时需要拼接字符串,容易造成sql注入,但ps不会
- CallableStatement由prepareCall方法创建,它为所有的RDBMS(关系数据库管理系统)提供了一种标准形式调用存储过程的方法。其对存储过程的调用存在两种形式:带结果参数和不带结果参数。输出参数时存储过程的返回值。两种形式都可以带有数量可变的输入(IN参数),输出(OUT参数)或输入和输出的参数
8.5getString()方法和getObject有什么区别
- getString()或getInt()等方法被调用时程序会一次性的把数据放到内存中,然后通过ResultSet的next()和getString()方法来获取数据
- 当数据量太大内存放不下时会抛出异常
- 使用getObject()则不会出现这种异常,它不会一次读取到内存,而是每次调用从数据库中取
8.6使用jdbc的注意事项
- 及时正确的关闭不在使用的链接
- 及时关闭资源ResultSet,PreparedStatement等
- 将Statement或者PreparedStatement的创建放在循环外