Java高级编程

第一章:多线程编程

- 进程与线程

  • 进程:

    • 是一个正在执行的程序
    • 第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
    • 第 二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
  • 进程的特征:

    • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。

    • 并发性:任何进程都可以同其他进程一起并发执行

    • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;

    • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进

      结构特征:进程由程序、数据和进程控制块三部分组成。

      多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

  • 进程和线程的区别:

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影 响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程 序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进 程。

- Thread类实现多线程

  • 线程的主体类:继承了Java.lang.Thread的程序类,覆写Thread的run()方法

  • 多线程的程序执行都应该在run()方法中定义,线程实例化对象通过调用start()方法启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyThread extends Thread{//线程的主体类
private String title;
public MyThread(String title){
this.title=title;
}
@Override
public void run() {//线程的主体方法
for (int x=0;x<10;x++){
System.out.println(this.title+"运行.x="+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread A=new MyThread("线程a");
MyThread B=new MyThread("线程b");
MyThread C=new MyThread("线程c");
A.start();
B.start();
C.start();
}
}

- Runnable实现多线程

相比于继承Thread的优势:由于只实现了Runable接口对象,规避了线程主体类单继承的局限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyThread implements Runnable{//线程的主体类
private String title;
public MyThread(String title){
this.title=title;
}
@Override
public void run() {//线程的主体方法
for (int x=0;x<10;x++){
System.out.println(this.title+"运行.x="+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread a=new Thread(new MyThread("线程A"));
Thread b=new Thread(new MyThread("线程b"));
Thread c=new Thread(new MyThread("线程c"));
a.start();
b.start();
c.start();
}
}

- 利用lambda实现

因为从JDK1.8开始,Runable接口使用了函数式接口定义,所以可以直接用Lanbda表达式实现多线程

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
package thread;

class MyThread extends Thread{//线程的主体类
private String title;
public MyThread(String title){
this.title=title;
}
@Override
public void run() {//线程的主体方法
for (int x=0;x<10;x++){
System.out.println(this.title+"运行.x="+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
for(int x=0;x<3;x++){
String title="线程对象-"+x;
Runnable run=()->{
for (int y=0;y<10;y++){
System.out.println(title+"运行,y="+y);
}
};
new Thread(run).start();
}
}
}

问:Thread与Runnable关系?

答:Runnable可以避免单继承的局限,同时可以更好的功能拓展,但是Runable执行过后,无法返回一个返回值

- Callable实现多线程

java1.5之后提出的新的线程接口实现:java.util.concurrent.Callable接口

可以发现Callable定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的好处是可以避免向下转型带来的安全隐患

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

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String> {//线程的主体类
@Override
public String call() throws Exception{//线程的主体方法
for (int x=0;x<10;x++){
System.out.println("线程执行.x="+x);
}
return "线程执行完毕。";
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
FutureTask<String> task=new FutureTask<>(new MyThread());
new Thread(task).start();
System.out.println("【线程返回数据】"+task.get());
}
}

Runnable与Callable的区别?

  • Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
  • java.lang.Runable接口之中提供有一个run方法,并且没有返回值;
  • java.util.concurrent.call()方法,可以有返回值;

- 线程运行状态

多线程开发,编写程序的过程:定义线程主体类,而后通过Thread类进行线程的启动,但是并不意味着调用了start()方法,线程就已经开始运行,因为整体的线程处理有自己的一套运行状态。

image-20210516170105484

  1. 任何一个线程的对象都应该使用Thread类封装,所以线程的启动使用的是start(),但是启动的时候实际上若干个线程都将进入到一种就绪状态,现在并没有执行;
  2. 进入到就绪状态之后就需要等待进行资源的调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间之后就需要让出资源;而后这个线程就进入到阻塞状态,随后重新回归到阻塞状态;
  3. 当run()方法执行完毕之后,实际上该线程的任务也就结束了,那么此时就可以直接进入到停止状态;

第二章:线程常用操作方法

- 线程的命名和取得

多线程的运行状态不确定,为了在实际开发中获取一些需要使用到的线程,就只能依靠线程的名字来操作。而Thread类中提供了线程名称操作的方法;

  • 构造方法:public Thread(Runable target,String name);
  • 设置名字:public final void setName(String name);
  • 取得名字:public final String getName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package thread;
class MyThread implements Runnable{

@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

public class ThreadDemo {
public static void main(String[] args) throws Exception{
MyThread mt=new MyThread();
new Thread(mt,"线程A").start();
new Thread(mt).start();
new Thread(mt,"线程C").start();
}
}

当开发者为线程设置名字的时候就使用设置的名字,而如果没有设置名字,则会自动生成一个不重复的名字。

一个主线程可以拥有多个子线程,主线程负责处理整体流程,而子线程负责处理耗时操作。

- 线程的休眠

Thread类之中定义的休眠方法:

  • 休眠:public static void sleep(long millis) throws InterruptedException;
  • 休眠:public static void sleep(long millis,int nanos) throws InterruptedException;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package thread;

public class ThreadDemo {
public static void main(String[] args) throws Exception{
new Thread(()->{
for(int x=0;x<10;x++){
System.out.println(Thread.currentThread().getName()+".x="+x);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程对象").start();
}
}

- 线程的中断

Thread类之中定义的中断执行的处理方法

  • 判断线程是否被中断:public boolean is Interrupted();
  • 中断线程执行:public void interrupt()

- 线程强制执行

当满足某个条件时,某一个线程可以一直独占资源,一直到该线程执行结束

  • join()方法
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 class ThreadDemo {
public static void main(String[] args) throws Exception{
Thread main_th=Thread.currentThread();//获得主线程
Thread thread=new Thread(()->{
for (int x=0;x<100;x++){
if(x==3){//霸道的线程
try{
main_th.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"执行,x="+x);
}
},"线程1");
thread.start();//线程启动
for(int x=0;x<100;x++){
Thread.sleep(100);
if(x>3){
main_th.start();
}
System.out.println("【霸道的main线程】number="+x);
}
}
}

- 线程礼让

先将资源让出去,让别的线程先执行

Thread类:

  • 礼让:public static void yield()

- 线程优先级

从理论上来讲,线程的优先级越高,越有可能先执行(越有可能先抢到资源),因此Thread类中提供了两个处理方法:

  • 设置优先级:public final void setPriority(int newPriority);
  • 获取优先级:public final int getPriority()

优先级数字常量:

  • 最高优先级:public static final int Max_PRIORITY —10
  • 中等优先级:public static final int NORM_PRIORITY—5
  • 最低优先级:public static final int MIN_PRIORITY—1

主线程的优先级:Thread.currentThread().getPriority()==5(中等优先级)

默认创建的线程为中等优先级

第三章:线程的同步与死锁

- 同步问题引出

在多线程处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,于是当多个线程对象,访问同一资源的时候如果处理不当就会产生数据错误的操作。

实例:编写简单的买票程序,将创建若干个线程对象实现买票的处理操作。

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
class Mythread implements Runnable{
private int ticket=10;//总票数为10张

@Override
public void run() {
while(true){
if (this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,票数"+this.ticket--);
}else{
System.out.println("票已卖光");
break;
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception{
Mythread mt=new Mythread();
new Thread(mt,"票贩子A").start();
new Thread(mt,"票贩子B").start();
new Thread(mt,"票贩子C").start();
}
}

- 线程同步处理

如果想要解决这个问题,就必须使用同步。所谓的同步就是指多个操作在一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行

问题解决:用synchronized关键字来实现,利用此关键字定义同步方法或同步代码块

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
//同步代码块处理
class Mythread implements Runnable{
private int ticket=10;//总票数为10张

@Override
public void run() {
while(true){
synchronized (this){// 每一次只允许一个线程访问
if (this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,票数"+this.ticket--);
}else{
System.out.println("票已卖光");
break;
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception{
Mythread mt=new Mythread();
new Thread(mt,"票贩子A").start();
new Thread(mt,"票贩子B").start();
new Thread(mt,"票贩子C").start();
}
}

同步处理会造成性能的降低

利用同步方法解决:只需要在方法上定义synchronized关键字

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
package thread;
//同步方法处理
class Mythread implements Runnable{
private int ticket=1000;//总票数为10张

public synchronized boolean sale(){
if (this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票,票数"+this.ticket--);
return true;
}else{
System.out.println("票已卖光");
return false;
}
}
@Override
public void run() {
while(true){
this.sale();
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception{
Mythread mt=new Mythread();
new Thread(mt,"票贩子A").start();
new Thread(mt,"票贩子B").start();
new Thread(mt,"票贩子C").start();
}
}
//创建了三个线程对象,卖5张票

- 线程死锁

  • 死锁是多线程同步处理之中可能出线的问题,所谓死锁是多个线程彼此互相等待的状态;
  • 若干个线程访问同一个资源时一定要进行同步处理,而过多的同步会造成死锁时

- 线程的等待与唤醒

同步问题解决:主要依靠的是Object类中提供的方法处理的

  • 等待机制:
    • 死等:Public final void wait() throws InerruptedException;
    • 设置等待时间:public final void wait(long timeout) throws Interrupted Exception;
    • 设置等待时间:public final void wait(long timeout,int nanos) throws Interrupted;
  • 唤醒第一个等待线程:public final native void notify();
  • 唤醒全部的等待线程:public final native void notifyAll();

若果同时有若干个等待线程的话,那么notify()表示的是唤醒第一个等待的,而其他的线程继续等待;而notifyall()表示唤醒所有的等待线程,那个线程的优先级高,就有可能先执行

- 守护线程

  • setDaemonThread() 设置守护线程

守护线程随着被守护的线程而存在,如果被守护线程结束,则守护线程也会随之结束,在JVM中最大的守护线程就是GC线程。程序执行中GC线程会一直存在,程序执行结束,GC就会消失

- volatile关键字

volite关键字主要是在属性定义上使用,直接

在正常进行变量处理的几个步骤:

  • 获取变量原有的数据内容副本;
  • 利用副本为变量进行数学计算;
  • 将计算后的变量,保存到原始空间之中;

而如果一个属性上追加了volatile关键字,表示不使用副本,而是直接操作原始变量,相当于节约了:拷贝副本,重新保存的步骤。

问:volatile 和 synchronized的区别?

  • volatile主要在属性上使用,而synchronized是在代码块与方法上使用
  • volatile无法描述同步的处理,他只是一种直接内存的处理,避免了副本的操作,而synchronized是实现同步的

- 线程池

线程池:描述的是一个可以容纳多个线程的容器,其中的线程可以反复调用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

- 创建线程池

  1. 用线程池的工厂类Executors里面的静态方法newFixedThreadPool生产一个指定的线程池;
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务;
  3. 调用ExecutorService中的submit,传递线程任务(实现类),开启线程,执行run方法;
  4. 调用ExecutorService中的shutdown销毁线程池(不建议执行)

第四章:Java基础类库

- StringBuffer类

String类的特点:

  • 每一个字符串的常量都属于一个String类的匿名对象,并且不可更改;
  • String有两个常量池:静态常量池,运行时常量池;
  • String初始化可以直接赋值或通过new关键字创建;
  • 一旦创建,不能修改,属于不可变类型;

StringBuffer类:所有的String类都可以通过StringBuffer的构造方法变为StringBuffer的类队对象

  • append() -可以通过append类进行字符串的修改
  • insert() 可以在已经存在的字符串的位置插入字符串
  • delete(start,num) 指定范围内删除字符串
  • reverse() 字符串反转

与StringBuffer类似的还有StringBuilder,区别在于StringBuffer类的方法属于线程安全的,全部使用了synchronized关键字进行标注,而StringBuild类属于非线程安全的。

- CharSequence接口

是一个描述字符串结构的接口,这个接口一般有三种常用子类:String类,StringBuffer类,StringBuilder类:

- AutoCloseable接口

在资源开发处理上实现资源的自动关闭(释放资源)例如:进行文件,网络,数据库的开发过长之中由于服务器的资源有限,所以在使用之后一定要关闭资源,这样才可以被更多的使用者所使用的;

- Runtime类

描述的是运行时的状态,以下有四个重要方法:

  • 获取最大可用内存空间:maxMemory();
  • 获取可用内存空间:totalMemory();
  • 获取空闲内存空间:freeMemory();
  • 手工进行GC处理:gc();

- System类

  • 数组拷贝:arrycopy()
  • 获取当前时间数值:currentTimeMillis()

- Cleaner类

提供·一个·对象清理操作,其主要的功能是进行finialize()方法的替代

- 大数字处理类

bigInterger类:

bigdecimal类:

- 日期处理类

日期格式化SimpleDateFormat

  • ```java
    //日期转换为字符串
    Date s=new Date();
    SimpleDateFormat sf=new SimpleDateFormat(“yyyy年MM月dd日 HH:mm:ss”);
    String date=sf.format(s);
    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

    #### - 正则表达式

    1. 字符匹配(数量,单个)

    - 任意字符:表示由任意字符组成;
    - \\\:匹配“\”;
    - \n:匹配换行
    - \t:匹配制表符

    2. 字符集(数量:单个)

    - [abc]:表示匹配a,b,c中的任意一个
    - [^abc]:表示不是由字母a,b,c中的任意一个
    - [a-zA-Z]:表示由任意字母所组成,不区分大小写
    - [0-9]:表示由0-9范围内的一个数字组成

    3. 简化字符集(数量:单个)
    - .:表示任意一个字符;
    - \d:等价于“[0-9]”
    - \s:匹配任意的一位空格,可能是空格,换行或制表符
    - \S:匹配任意一个非空格
    - \w:匹配字母,数字,下划线,等价于"[a-zA-Z_0-9]"
    - \W:匹配非字母,数字,下划线,等价于“[\^a-zA-Z_0-9]”;
    4. 边界匹配:
    - ^:匹配边界开始;
    - $:匹配边界结束;
    5. 数量表示:只有添加了数量单位·才可以匹配多位字符
    - 表达式?:0-1次
    - 表达式*:0,1,多次
    - 表达式+:出现一次或多次
    - 表达式{n}:表达式长度为n位
    - 表达式{n,}:表达式长度为n次以上
    - 表达式{n-m}:表达式长度为n-m次
    6. 逻辑表达式:可以连接多个正则:
    - 表达式X表达式Y:表达式X后跟表达式Y
    - x|y:x,y中的任意一个
    - (表达式):可以为整体描述设置数量单位;

    #### - String类对正则的支持

    - 将制定字符串与正则表达式进行判断 boolean matches(String regex)
    - 替换全部 String replaceAll(String regex,String replacement)
    - 替换首个 String replaceFirst(String regex,String replacement)
    - 正则拆分 String[] split(String regex)
    - 正则拆分 String[] split(String regex,int limit)

    ### 第五章:国际化程序的实现原理

    Locale类:

    - Locale loc=new Locale("zh","CN); 中文环境
    - Locale loc=Locale.getDefault(); 获取系统默认语言

    ### 第六章:网络编程

    #### - TCP通信的客户端实现

    TCP向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据

    表示客户端的类:java.net.Socket:此类实现客户端套接字(也可叫套接字),套接字是两个机器间的通信的端点。

    套接字:包含了IP地址和端口号的网络单位

    构造方法:Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号。

    参数:-String host:服务器的主机名字/服务器的IP地址 int port:服务器的端口号

    成员方法:

    - OutputStream getOutStream() 返回套接字的输出流。
    - InputStream getInputStream() 返回套接字的输入流。
    - void close() 关闭此套接字

    实现步骤:

    1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
    1. 使用Socket对象中的方法getOutoutStream()获取网络字节输出流OutoutStream对象
    1. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
    1. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
    1. 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
    1. 释放资源(Socket)

    注意:

    1. 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对像
    1. 当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路

    ```java
    package net;

    /**
    * @author jilfoyle
    * @date 2021/6/5-17:54
    */

    import java.io.*;
    import java.net.Socket;

    /**
    *文件上传案例客户端:读取本地文件,上传到服务器,读取服务器回写数据
    * 实现步骤:
    * 1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
    * 2.创建一个客户端Socket对象,构造方法绑定服务器的IP地址和端口号
    * 3.使用Socket中的getOutputStream,获取网络字节输出流OutputStream对象
    * 4.使用本地字节输入流FileInputSteam对象中的read,读取本地文件
    * 5.使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
    * 6.使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
    * 7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
    * 8.释放资源(FileInputStream,Socket)
    */
    public class TCPClient {
    public static void main(String[] args) {
    int x=0;
    while(x<10){
    try (FileInputStream fin = new FileInputStream("./Cat.jpg")) {
    Socket socket = new Socket("127.0.0.1",8888) ;
    OutputStream os=socket.getOutputStream();
    int len=0;
    byte[] bytes=new byte[1024];
    while ((len=fin.read(bytes))!=-1){
    os.write(bytes,0,len);
    }
    socket.shutdownOutput();//结束标记,解决read方法的阻塞问题
    InputStream is=socket.getInputStream();
    while ((len=is.read(bytes))!=-1){
    System.out.println(new String(bytes,0,len));
    }

    fin.close();
    socket.close();

    } catch (FileNotFoundException e) {
    System.out.println("文件不存在!");
    } catch (IOException e) {
    System.out.println("读写异常");
    }
    x++;
    }

    }
    }

- 服务器端

实现步骤:

  1. 创建一个服务器ServerSocket对象和系统端口号
  2. 使用ServerSocket对象中的方法accept()获取请求的客户端对象Socket
  3. 使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream
  4. 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
  5. 使用Socket对象中的方法getOutputStrame()获取网络字节输出流OutputStream
  6. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写回写数据
  7. 释放资源socket,serverSocket
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
package net;

/**
* @author jilfoyle
* @date 2021/6/5-18:27
*/

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Random;

/**
* 文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”
* 实现步骤:
* 1.创建一个服务器Serversocket对象,和系统要指定的端口号
* 2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
* 3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
* 4.判断的:目标文件所在文件夹是否存在,不存在创建
* 5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
* 6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
* 7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
* 8.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
* 9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
* 10.关闭资源(FileOutputStream,Socket,ServerSocket);
*/
public class TCPServer {
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(8888);
while(true){//使服务器一直处于运行状态
Socket socket=server.accept();
new Thread(new Runnable() {//使用多线程提高效率,有一个客户端就开启一个线程
@Override
public void run() {
try {
InputStream is=socket.getInputStream();
File file=new File("./img");
if(!file.exists()){
file.mkdirs();
}
String filename="img"+System.currentTimeMillis()+ new Random().nextInt(9999)+".jpg";
FileOutputStream fout=new FileOutputStream(file+"/"+filename);
int len=0;
byte[] bytes=new byte[1024];
while ((len=is.read(bytes))!=-1){
fout.write(bytes,0,len);
}
socket.getOutputStream().write("上传成功".getBytes());
fout.close();
socket.close();
}catch (IOException e){
System.out.println("读写异常");
}
}
}).start();
}

}
}