IO流概述
l IO流用来处理设备之间的数据传输
• 上传文件和下载文件
l Java对数据的操作是通过流的方式
l Java用于操作流的对象都在IO包中
明确操作的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。
File类概述和构造方法
l File类的概述
• 文件和目录路径名的抽象表示形式
• Java中专门提供了一个表示目录与文件的类——java.io.File,通过其可以获取文件、目录的信息,对文件、目录进行管理。File类一共提供了四个构造器,表列出了其中常用的三个。
• 在File类中最常用的是第一个构造函数。使用构造函数public File(String pathname)创建一个文件对象。其中,如果pathname是实际存在的路径,则该File对象表示的是目录;如果pathname是存在的文件名,则该File对象表示的是文件。
l 构造方法
• public File(String pathname)
• public File(String parent,Stringchild)
• public File(File parent,Stringchild)
l public File(String pathname)通过指定的路径名字符串pathname创建一个File对象,如果给定字符串是空字符串,那么创建的File对象将不代表任何文件或目录
l public File(String parent,String child)根据指定的父路径名字符串parent以及子路径字符串child创建一个File对象。若parent为null,则与单字符串参数构造器效果一样,否则parent将用于表示目录,而child则表示该目录下的子目录或文件
l public File(File parent,String child)根据指定的父File对象parent及子路径字符串child创建一个File对象。若parent为null,则与单字符串参数构造器效果一样,否则parent将用于表示目录,而child则表示该目录下的子目录或文件
File类的成员方法
l 创建功能
• public boolean createNewFile()在指定目录下创建文件,如果该文件已存在,则不创
• 建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,
• 会覆盖。除非续写。
• public boolean mkdir()创建此抽象路径名指定的目录。
• public boolean mkdirs()创建多级目录。
l 删除功能
• public boolean delete()删除此抽象路径名表示的文件或目录。
• void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹
用 delete 删除。window 的删除动作,是从里往外删。注意:java 删除文件不走回收站。要慎用。
l 重命名功能
• public boolean renameTo(File dest)
String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。
如果调用 list方法的 File 对象中封装的是一个文件,那么 list方法返回数组为null。
如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个 list()
方法才有效。
File类的成员方法
l 判断功能
• public boolean isDirectory()测试此抽象路径名表示的文件是否是一个目录。
• public boolean isFile()测试此抽象路径名表示的文件是否是一个标准文件。
• public boolean exists()判断文件或者文件夹是否存在。
• public boolean canRead()
• public boolean canWrite()
• public boolean isHidden()测试此抽象路径名指定的文件是否是一个隐藏文件。
File类的成员方法
l 基本获取功能
• public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
• public String getPath():将此抽象路径名转换为一个路径名字符串。
• public String getName():返回由此抽象路径名表示的文件或目录的名称。
• public long length()获取文件大小。
• public long lastModified()
l 高级获取功能
• public String[] list()
• public File[] listFiles()
l close()关闭文件输入流,同时释放系统资源
read()从该输入流中读取一个数据字节,并以int类型返回
l 递归
• 方法定义中调用方法本身的现象
什么时候用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元
素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算
的未知内容不确定)。
l 递归注意实现
• 要有出口,否则就是死递归。(一定要定义递归的条件。)
• 次数不能太多,否则就内存溢出其实递归就是在栈内存中不断的加载同一个函数。
• 构造方法不能递归使用
l 递归解决问题的思想
• 找到出口
• 找到规律
IO流分类
l 按照数据流向
• 输入流 读入数据
• 输出流 写出数据
l 按照数据类型
• 字节流
• 字符流
• 什么情况下使用哪种流呢?
• 如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。
• 如果你什么都不知道,就用字节流
l 字节流以字节为基本单位来处理数据的输入/输出,一般都用于对二进制数据的读写,例如声音、图象等数据。
l 字符流以字符为基本单位来处理数据的输入和输出,一般都用于对文本类型数据的读写,例如文本文件、网络中发送的文本信息等。
IO流常用基类
l 字节流的抽象基类:
• InputStream ,OutputStream。
l FileInputStream类是InputStream的子类。FileInputStream类主要用于从文件系统中的某个文件中获取输入字节。FileOutputStream类是OutputStream的子类,FileOutputStream主要是用于将数据以字节流写入目标文件的输出流。表为FileInputStream中的方法声明及其使用描述。
l FileInputStream类从文件系统中的某个文件中获取输入字节。主要用于读取诸如图像数据之类的原始字节流。要读取字符流,可以考虑使用FileReader。
l 字符流的抽象基类:
• Reader , Writer。
l 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
• 如:InputStream的子类FileInputStream。
• 如:Reader的子类FileReader。
字节流写数据
l OutputStream
• FileOutputStream
• FileOutputStream类用于将数据写入File或FileDescriptor的输出流。主要用于写入诸如图像数据之类的原始字节的流。要写入字符流,可以考虑使用FileWriter。
l 往一个文本文件中写一句话:”helloworld”
• 分析发现其实更适合用字符流,但是由于字节流先出现,所以,我们考虑先使用字节流后面再讲会什么出现字符流。
l FileOutputStream的构造方法
• FileOutputStream(File file)
• FileOutputStream(String name)
字节流写数据的方式
public void write(int b)对于write方法,可以一次写入一个字节,但接收的是一个int类型数值。只写入该int类型的数值的最低一个字节(8位)。
l public void write(byte[] b)
l public void write(byte[] b,int off,int len)
字节流读取数据
l InputStream
• FileInputStream
l 把刚才写的数据读取出来显示在控制台
l FileInputStream的构造方法
• FileInputStream(File file)
• FileInputStream(String name)
l FileInputStream的成员方法
• public int read()
• public int read(byte[] b)
l public int read(byte[] b,int off,int len) throws IOException从输入流中读取len个数据字节,并存入字节数组b中
字节流读取数据两种方式
l 一次读取一个字节
l 一次读取一个字节数组
• 每次可以读取多个数据,提高了操作效率
字节缓冲流
l 字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,所以提供了字节缓冲区流
l 字节缓冲输出流
• BufferedOutputStream
l 字节缓冲输入流
• BufferedInputStream
编码表
• 由字符及其对应的数值组成的一张表:UTF-8
l 编码
• 把看得懂的变成看不懂的
l 解码
• 把看不懂的变成看得懂的
计算机只能识别二进制数据,早期由来是电信号。
为了方便应用计算机,让它可以识别各个国家的文字。
就将各个国家的文字用数字来表示,并一一对应,形成一张表。
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字节表示
Buffer(缓冲),Channer(通道)
转换流概述
l OutputStreamWriter 字符输出流
• publicOutputStreamWriter(OutputStream out)
• publicOutputStreamWriter(OutputStream out,String charsetName)
l InputStreamReader 字符输入流
• publicInputStreamReader(InputStream in)
• publicInputStreamReader(InputStream in,String charsetName)
OutputStreamWriter写数据
l OutputStreamWriter写数据方法
• public void write(int c)
• public void write(char[] cbuf)
• public void write(char[] cbuf,intoff,int len)
• public void write(String str)
• public void write(String str,intoff,int len)
• public void write(byte[] b) throwsIOException将字节数组b中的b.length个字节数据输入到输出流
l 字符流操作要注意的问题
• flush()的作用
• flush()和close()的区别
InputStreamReader读数据
l OutputStreamWriter读数据方法
• public int read()
• public int read(char[] cbuf)
FileWriter写数据
FileReader读取数据
字符缓冲流
l BufferedWriter基本用法
l BufferedReader基本用法
l 字符缓冲流复制文本文件
l 特殊功能
• BufferedWriter
• void newLine()
• BufferedReader
• String readLine()
l 字符缓冲流特殊功能复制文本文件
IO流小结
l 字节流
• 字节输入流
• 字节输出流
l 字符流
• 字符输入流
• 字符输出流
字节流:InputStreamOutputStream
字符流:ReaderWriter
在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都
是这个子类的功能名称。
public static voidmain(String[] args) throws IOException { //读、写都会发生 IO
异常概述
l 异常:异常就是Java程序在运行过程中出现的错误。
l 异常由来:问题也是现实生活中一个具体事务,也可以通过java 的类的形式进行描述,并封装成对象。其实就是Java对不正常情况进行描述后的对象体现。
l 我们见过的异常,角标越界异常,空指针异常
讲解IO流之前为什么先讲解异常和File类呢?
l 因为File表示的是IO流将来要操作的文件,所以我们需要学习File类。
l 而操作文件无非就是上传文件和下载文件,在这个操作的过程中可能出现问题,
l 出现问题后,我们需要对对应的代码进行处理。所以我们需要学习异常异常。
异常分三类:
l 骑车去旅行:
l Error:走到半路上,发生山路塌陷,或者出现了泥石流,这个问题很严重,不是班长能够立马解决的。
l Exception:出门前,班长要看看车轮子以及车链子等是否还在
l RuntimeException:在骑车的过程中,有好路不走,偏偏要走石子路
l 1,编译时异常
l 除了RuntimeException及其子类,Exception中所有的子类都是,这种异常必须要处理,要不编译通不过
l 2,运行是异常
l RuntimeException及其子类都是,这种异常不用处理,编译会通过,不过这样的程序会有安全隐患,遇到这种异常是需要改代码的
l 3,严重错误问题
l 用Error进行描述,这个问题发生后,一般不编写针对代码进行处理,而是要对程序进行修正.通常都是由虚拟机抛出的问题
基本格式:try是检测异常,catch是用来捕获异常的,finally是用来结束资源的
l 世界上最真情的相依,是你在try我在catch,无论你发神马脾气,我都默默接受,静静处理
Java中的异常被分为两大类:编译时异常和运行时异常。所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常
l 编译时异常
• Java程序必须显示处理,否则程序就会发生错误,无法通过编译
l 运行时异常
• 无需显示处理,也可以和编译时异常一样处理
Throwable中的方法
l getMessage()
• 获取异常信息,返回字符串。
l toString()
• 获取异常类名和异常信息,返回字符串。
l printStackTrace()
• 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
l printStackTrace(PrintStream s)
• 通常用该方法将异常内容保存在日志文件中,以便查阅。
throws和throw的区别
l throws
• 用在方法声明后面,跟的是异常类名
• 可以跟多个异常类名,用逗号隔开
• 表示抛出异常,由该方法的调用者来处理
• throws表示出现异常的一种可能性,并不一定会发生这些异常
l throw
• 用在方法体内,跟的是异常对象名
• 只能抛出一个异常对象名
• 表示抛出异常,由方法体内的语句处理
• throw则是抛出了异常,执行throw则一定抛出了某种异常
我们到底该如何处理异常呢
l 原则:如果该功能内部可以将问题处理,用try,如果处理不了,交由调用者处理,这是用throws
l 区别:
• 后续程序需要继续运行就try
• 后续程序不需要继续运行就throws
l 举例:
• 感冒了就自己吃点药就好了,try
• 吃了好几天药都没好结果得了H7N9,那就的得throws到医院
• 如果医院没有特效药就变成Error了
l finally的特点
• 被finally控制的语句体一定会执行
• 特殊情况:在执行到finally之前jvm退出了(比如System.exit(0))
l finally的作用
• 用于释放资源,在IO流操作和数据库操作中会见到
l finally相关的面试题
• final,finally和finalize的区别
• 如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后。
l 但是,catch,finally不能单独使用
自定义异常
• 继承自Exception
• 继承自RuntimeException
异常注意事项
l 子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类。(父亲坏了,儿子不能比父亲更坏)
l 如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是他的子集,子类不能抛出父类没有的异常
l 如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws
说明:System.out.println()语句的原理。
并测试如下两个代码:
用System.in实现键盘录入数据。用BufferedReader改进。
用System.out实现控制台输出数据。用BufferedWriter改进。
复制数据的最基本思想:
数据源:读数据
目的地:写数据
close()和 flush()的区别:
flush():将缓冲区的数据刷到目的地中后,流可以使用。
close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底
层资源。这个动作一定做。
l 字节流和字符流主要区别有哪些?
l 【提示】字节流是最基本的,所有的“InputStrem”和“OutputStream”的子类都是字节流,其主要用于处理二进制数据,并按字节来处理。
l 但是实际开发中很多的数据是文本,这就提出了字符流的概念,它按虚拟机的encode来处理,也就是要进行字符集的转化。这两者之间通过“InputStreamReader”和“OutputStreamWriter”来关联。实际上,通过“byte[]”和“String”来关联在实际开发中出现的汉字问题,这都是在字符流和字节流之间转化不统一而造成的。在从字节流转化为字符流时,实际上就是“byte[]”转化为“String”。
l public String(byte bytes[], String charsetName)
l 注意:有一个关键的参数字符集编码,通常可以省略,就是操作系统的lang。
l 字符流转化为字节流,实际上是String转化为byte[]。
l byte[] String.getBytes(String charsetName)
l 至于Java.io中还出现了许多其他的流,主要是为了提高性能和使用方便,如“BufferedInputStream”、“PipedInputStream”等。
注意:流的操作只有两种:读和写。
自定义缓冲区。
import java.io.*;
classFileReaderDemo2 {
public static voidmain(String[] args) throws IOException {
FileReader fr = newFileReader("demo.txt"); //创建读取流对象和指定文件
关联。
//因为要使用 read(char[])方法,将读取到字符存入数组。所以要创建一个字
符数组,一般数组的长度都是1024 的整数倍。
char[] buf = newchar[1024];//读取的字符数组长度是 1024
int len = 0;
while((len=fr.read(buf)) != -1) { //返回 -1 ,标志读到结尾。
System.out.println(newString(buf,0,len));//将 char 类型的数据从 0 到
len 转换成 String
}
fr.close();
}
}
2:JDK7要了解的新IO类
Path:与平台无关的路径。
Paths:包含了返回Path的静态方法。
publicstatic Path get(URI uri):根据给定的URI来确定文件路径。
Files:操作文件的工具类。提供了大量的方法,简单了解如下方法
publicstatic long copy(Path source,OutputStream out) :复制文件
publicstatic Path write(Path path,Iterable<? extendsCharSequence> lines,Charset cs,OpenOption... options):
把集合的数据写到文件。
//复制文件
Files.copy(Paths.get("Demo.java"),newFileOutputStream("Copy.Java"));
//把集合中的数据写到文件
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
Files.write(Paths.get("list.txt"),list, Charset.forName("gbk"));
多线程:★★★★
进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。
线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序
的空间的标示。线程负责的是应用程序的执行顺序。
一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是
多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
jvm 在启动的时,首先有一个主线程,负责程序的执行,调用的是 main 函数。主线程
执行的代码都在 main 方法中。
当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的
代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃
圾回收。
随机性的原理:因为 cpu 的快速切换造成,哪个线程获取到了 cpu 的执行权,哪个线
程就执行。
返回当前线程的名称:Thread.currentThread().getName()
线程的名称是由:Thread-编号定义的。编号从 0 开始。
线程要运行的代码都统一存放在了 run 方法中。
线程要运行必须要通过类中指定的方法开启。start 方法。(启动后,就多了一条执行路径)
start 方法:1)、启动了线程;2)、让 jvm 调用了 run 方法。
创建线程的第一种方式:继承 Thread ,由子类复写 run 方法。
步骤:
1,定义类继承 Thread 类;
2,目的是复写 run 方法,将要让线程运行的代码都存储到 run 方法中;
3,通过创建 Thread 类的子类对象,创建线程对象;
4,调用线程的 start 方法,开启线程,并执行 run 方法。
线程状态:
被创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备 cpu 的执行资格,没有 cpu 的执行权;
消亡:stop()
创建线程的第二种方式:实现一个接口 Runnable。
步骤:
1,定义类实现 Runnable 接口。
2,覆盖接口中的 run 方法(用于封装线程要运行的代码)。
3,通过 Thread 类创建线程对象;
4,将实现了 Runnable 接口的子类对象作为实际参数传递给 Thread 类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的 run 方法所属的对象。
5,调用 Thread 对象的 start 方法。开启线程,并运行 Runnable 接口子类中的 run方法。
Ticket t = new Ticket();
/*
直接创建 Ticket 对象,并不是创建线程对象。
因为创建对象只能通过 new Thread 类,或者 new Thread 类的子类才可以。
所以最终想要创建线程。既然没有了 Thread 类的子类,就只能用 Thread 类。
*/
Thread t1 = new Thread(t); //创建线程。
/*
只要将 t 作为 Thread 类的构造函数的实际参数传入即可完成线程对象和 t 之间的关联
为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码 run 方法。
*/
t1.start();
为什么要有 Runnable 接口的出现?
1:通过继承 Thread 类的方式,可以完成多线程的建立。但是这种方式有一个局限性,
如果一个类已经有了自己的父类,就不可以继承 Thread 类,因为 java 单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java 就提供了一个接口 Runnable。这个接口中
定义了 run 方法,其实 run 方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。
因为实现 Runnable 接口可以避免单继承的局限性。
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位
置单独定义到接口中。为其他类进行功能扩展提供了前提。
所以 Thread 类在描述线程时,内部定义的 run 方法,也来自于 Runnable 接口。
实现 Runnable 接口可以避免单继承的局限性。而且,继承 Thread,是可以对Thread 类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现 Runnable 接口更方便一些。所以 Runnable 接口将线程要执行的任务封装成了对象。
-------------------------------------------------------
//面试
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnablerun");
}
}
public void run(){
System.out.println("subthreadrun");
}
}.start(); //结果:subthreadrun
---------------------------------------------------------
Try {
Thread.sleep(10);
}catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟 cpu 切 换情况。
多线程安全问题的原因:
通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。
涉及到两个因素:
1,多个线程在操作共享数据。
2,有多条语句对共享数据进行运算。
原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。
解决安全问题的原理:
只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
如何进行多句操作共享数据代码的封装呢?
java 中提供了一个解决方式:就是同步代码块。
格式:
synchronized(对象) {// 任意对象都可以。这个对象就是锁。
需要被同步的代码;
}
------------------------------------------------------------------------------------------------------------
同步:★★★★★ //就是在操作共享数据代码时,访问时只能让一个线程进去访问,此线程执行完退出后,别的线程才能再对此共享数据代码进行访问。
好处:解决了线程安全问题。Synchronized
弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。
定义同步是有前提的:
1,必须要有两个或者两个以上的线程,才需要同步。
2,多个线程必须保证使用的是同一个锁。
同步的第二种表现形式: //对共享资源的方法定义同步
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
同步函数是用的哪个锁呢? //synchronized(this)用以定义需要进行同步的某一部分代码块
通过验证,函数都有自己所属的对象 this,所以同步函数所使用的锁就是 this 锁。This.
方法名
当同步函数被 static 修饰时,这时的同步用的是哪个锁呢?
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class
同步代码块和同步函数的区别?
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是 this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步的话,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
-------------------------------------------------------------------------------------------------------------------------------
★考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。
//懒汉式:延迟加载方式。
当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。所以容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。但是却带来了效率降低。
为了效率问题,通过双重判断的形式解决。
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ //锁是谁?字节码文件对象;
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
--------------------------------------------------------------------------------------------------------------
同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。
线程间通信:思路:多个线程在操作同一个资源,但是操作的动作却不一样。
1:将资源封装成对象。
2:将线程执行的任务(任务其实就是 run 方法。)也封装成对象。
等待唤醒机制:涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A 锁上的线程被 wait 了,那这个线程就相当于处于 A 锁的线程池中,只能
A 锁的 notify 唤醒。
3:这三个方法都定义在 Object 类中。为什么操作线程的方法定义在 Object 类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又
可以是任意对象,那么能被任意对象调用的方法一定定义在 Object 类中。
wait 和 sleep 区别:分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的 notify 或者
notifyAll 来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但不是不释放锁。
线程的停止:通过 stop 方法就可以停止线程。但是这个方式过时了。
停止线程:原理就是:让线程运行的代码结束,也就是结束 run 方法。
怎么结束 run 方法?一般 run 方法里肯定定义循环。所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread 类中的 interrupt 方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
---------< java.lang.Thread>----------
interrupt():中断线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用 join 方法。
当 A 线程执行到了 B 线程的 join 方式。A 线程处于冻结状态,释放了执行权,B
开始执行。A 什么时候执行呢?只有当B 线程运行结束后,A 才从冻结状态恢复运行状
态执行。
LOCK 的出现替代了同步:lock.lock();………lock.unlock();
Lock 接口:多线程在 JDK1.5版本升级时,推出一个接口 Lock 接口。
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同
步,就是释放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。
所以将这些动作定义在了锁当中,并把锁定义成对象。
所以同步是隐示的锁操作,而 Lock 对象是显示的锁操作,它的出现就替代了同步。
在之前的版本中使用 Object 类中wait、notify、notifyAll 的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在 Object 类中。
而现在锁是指定对象 Lock。所以查找等待唤醒机制方式需要通过 Lock 接口来完成。而
Lock 接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是 Condition,将 Object 中的三个方法进行单独的封装。并提供了
功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks >Condition 接口:await()、signal()、signalAll();
-------------------------------------------------------------------------------------------------------
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull =lock.newCondition();
final Condition notEmpty =lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throwsInterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}
finally {
lock.unlock();
}
}
public Object take() throwsInterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}
网络编程:
端口:
物理端口:
逻辑端口:用于标识进程的逻辑地址,不同进程的标识;有效端口:0~65535,其中0~1024 系统使用或保留端口。
java 中 ip 对象:InetAddress.
import java.net.*;
class IPDemo{
public staticvoid main(String[] args) throws UnknownHostException{
//通过名称(ip 字符串 or 主机名)来获取一个 ip 对象。
InetAddress ip =
InetAddress.getByName("www.baidu.com");//java.net.UnknownHostException
System.out.println("addr:"+ip.getHostAddress());
System.out.println("name:"+ip.getHostName());
}
}
Socket:★★★★,套接字,通信的端点。
就是为网络服务提供的一种机制,通信的两端都有 Socket,网络通信其实就是 Socket间的通信,数据在两个 Socket 间通过 IO 传输。
UDP 传输:
1,只要是网络传输,必须有socket 。
2,数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。
直接操作 udp 不可能,对于java 语言应该将 udp 封装成对象,易于我们的使用,这个
对象就是 DatagramSocket. 封装了 udp 传输协议的 socket 对象。
因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包对象就是:DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。
DatagramSocket 具备发送和接受功能,在进行 udp 传输时,需要明确一个是发送端,一个是接收端。
udp 的发送端:
1,建立 udp 的 socket 服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。
2,明确要发送的具体数据。
3,将数据封装成了数据包。
4,用 socket 服务的 send 方法将数据包发送出去。
5,关闭资源。
----------------------------------------------------------------------------------------------------------------------
import java.net.*;
class UdpSend{
public staticvoid main(String[] args)throws Exception {
// 1,建立 udp 的 socket 服务。
DatagramSocketds = new DatagramSocket(8888);//指定发送端口,不指定系统会随机分配。
// 2,明确要发送的具体数据。
String text ="udp 传输演示 哥们来了";
byte[] buf =text.getBytes();
// 3,将数据封装成了数据包。
DatagramPacketdp = new DatagramPacket(buf,
buf.length,InetAddress.getByName("10.1.31.127"),10000);
// 4,用 socket 服务的 send 方法将数据包发送出去。
ds.send(dp);
// 5,关闭资源。
ds.close();
}
}
-------------------------------------------------------------------------------------------------------------------
udp 的接收端:
1,创建 udp 的 socket 服务,必须要明确一个端口,作用在于,只有发送到这个端
口的数据才是这个接收端可以处理的数据。
2,定义数据包,用于存储接收到数据。
3,通过 socket 服务的接收方法将收到的数据存储到数据包中。
4,通过数据包的方法获取数据包中的具体数据内容,比如 ip、端口、数据等等。
5,关闭资源。
------------------------------------------------------------------------------------------------------------------------------
class UdpRece {
public static void main(String[] args)throws Exception{
// 1,创建 udp 的 socket 服务。
DatagramSocketds = new DatagramSocket(10000);
// 2,定义数据包,用于存储接收到数据。先定义字节数组,数据包会把数据存储到字节数组中。
byte[] buf = newbyte[1024];
DatagramPacketdp = new DatagramPacket(buf,buf.length);
// 3,通过 socket 服务的接收方法将收到的数据存储到数据包中。
ds.receive(dp);//该方法是阻塞式方法。
// 4,通过数据包的方法获取数据包中的具体数据内容,比如 ip,端口,数据等等。
String ip =dp.getAddress().getHostAddress();
int port =dp.getPort();
String text =new String(dp.getData(),0,dp.getLength());//将字节数组中的有效部分转成字符串。
System.out.println(ip+":"+port+"--"+text);
// 5,关闭资源。
ds.close();
}
}
------------------------------------------------------------------------------------------------------------------------------------
TCP 传输:两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为 socket 流。该流中既有读取,也有写入。
tcp 的两个端点:一个是客户端,一个是服务端。
客户端:对应的对象,Socket
服务端:对应的对象,ServerSocket
TCP 客户端:
1,建立 tcp 的 socket 服务,最好明确具体的地址和端口。这个对象在创建时,就已
经可以对指定 ip 和端口进行连接(三次握手)。
2,如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到 socket
流中的读取流和写入流即可,只要通过 getInputStream 和 getOutputStream 就可
以获取两个流对象。
3,关闭资源。
------------------------------------------------------------------------------------------------------------------------------
import java.net.*;
import java.io.*;
//需求:客户端给服务器端发送一个数据。
class TcpClient{
public staticvoid main(String[] args) throws Exception{
Socket s = newSocket("10.1.31.69",10002);
OutputStream out= s.getOutputStream();//获取了socket流中的输出流对象。
out.write("tcp演示,哥们又来了!".getBytes());
s.close();
}
}
----------------------------------------------------------------------------------------------------------------
TCP 服务端:
1,创建服务端 socket 服务,并监听一个端口。
2,服务端为了给客户端提供服务,获取客户端的内容,可以通过 accept 方法获取连
接过来的客户端对象。
3,可以通过获取到的 socket 对象中的 socket 流和具体的客户端进行通讯。
4,如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。
-------------------------------------------------------------------------------------------------------------------
class TcpServer{
public staticvoid main(String[] args) throws Exception{
ServerSocket ss= new ServerSocket(10002);//建立服务端的 socket 服务
Socket s =ss.accept();//获取客户端对象
String ip =s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");
// 可以通过获取到的 socket 对象中的 socket 流和具体的客户端进行通讯。
InputStream in =s.getInputStream();//读取客户端的数据,使用客户端对象的 socket 读取流
byte[] buf = newbyte[1024];
int len =in.read(buf);
String text =new String(buf,0,len);
System.out.println(text);
// 如果通讯结束,关闭资源。注意:要先关客户端,在关服务端。
s.close();
ss.close();
}
}
A:网络模型说完了,我们要进行通讯,需要哪些要素呢?
比如说:我要跟你说话.
第一个条件:我要先找到你 (IP)
第二个条件:你得有接收数据的地方 耳朵 (端口)
第三个条件:我跟你说话,你能接收到,咱按什么方式接收啊,我说英文你懂吗,说韩文你懂吗,不懂是吧,所以我还是说中文把.(协议)
A:所谓IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。例如一个采用二进制形式的IP地址是“00001010000000000000000000000001”,这么长的地址,人们处理起来也太费劲了。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号“.”分开不同的字节。于是,上面的IP地址可以表示为“10.0.0.1”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多。
B:IP地址的组成
IP地址 = 网络号码+主机地址
A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
特殊地址:
127.0.0.1 回环地址,可用于测试本机的网络是否有问题. ping 127.0.0.1
DOS命令 ipconfig:查看本机IP地址
xxx.xxx.xxx.0 网络地址
xxx.xxx.xxx.255 广播地址
A类 1.0.0.1---127.255.255.254 (1)10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址) (2)127.X.X.X是保留地址,用做循环测试用的。
B类 128.0.0.1---191.255.255.254 172.16.0.0---172.31.255.255是私有地址。169.254.X.X是保留地址。
C类 192.0.0.1---223.255.255.254 192.168.X.X是私有地址
D类 224.0.0.1---239.255.255.254
E类 240.0.0.1---247.255.255.254
A:有两个主方法,用几个控制台
B:先开启那个方法?
谁先开都行.因为udp的特点是面向无连接.
如果你先开启了发送,那么,它收不到数据而已.
为了演示的方便,先开启接收端.
看到了吗.为什么?等待接收是吧,那句话导致的:ds.receive(dp);该方法是个阻塞是方法.没数据就等着.
C:把代码用DOS窗口演示
A:这个时候,就需要和io结合起来使用了。还得注意一个问题,这个时候接收端,要一直开启,否则接收一句就关闭了。所以,用死循环,并且,服务不关闭。代码如下:
class SendDemo2{
publicstatic void main(String[] args) throws Exception {
DatagramSocketds = new DatagramSocket();
BufferedReaderbr = new BufferedReader(new InputStreamReader(System.in));
Stringline = null;
while((line=br.readLine())!=null) {
if("886".equals(line)) {
break;
}
byte[]by = line.getBytes();
DatagramPacketdp = newDatagramPacket(by,by.length,InetAddress.getByName(“192.168.1.255"),10000);
ds.send(dp);
}
ds.close();
}
}
class ReceiveDemo2{
publicstatic void main(String[] args) throws Exception {
DatagramSocketds = new DatagramSocket(10000); //如果10000端口已经被使用了,这个服务起不来
//我这边是循序接收的啊.无限循环
while(true) {
byte[]by = new byte[1024];
DatagramPacketdp = new DatagramPacket(by,by.length);
ds.receive(dp);
//通过数据包对象的方法获取其中的数据内容,包括地址,端口,数据主体
Stringip = dp.getAddress().getHostAddress();
intport = dp.getPort();
byte[]by2 = dp.getData();
Stringtext = new String(by2,0,dp.getLength());
System.out.println(ip+"..."+port+"..."+text);
}
//关闭资源
//ds.close();
}
}
B:用线程封装后的代码如下:
public class SendThread implements Runnable{
privateDatagramSocket ds;
publicSendThread(DatagramSocket ds) {
this.ds= ds;
}
@Override
publicvoid run() {
//创建UDP发送端的服务
try{
//把键盘录入数据用高效缓冲流封装
BufferedReaderbr = new BufferedReader(new InputStreamReader(
System.in));
Stringline = null;
while((line = br.readLine()) != null) {
if ("886".equals(line)){
break;
}
byte[]bys = line.getBytes();
//数据包
DatagramPacketdp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("192.168.1.255"),12345);
//发送数据
ds.send(dp);
}
//关闭资源
ds.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
public class ReceiveThread implementsRunnable {
privateDatagramSocket ds;
publicReceiveThread(DatagramSocket ds) {
this.ds= ds;
}
@Override
publicvoid run() {
try{
//为了循环多次接受
while(true) {
//创建字节数组作为数据包的缓冲区
byte[]bys = new byte[1024];
DatagramPacketdp = new DatagramPacket(bys, bys.length);
//读取数据包数据
ds.receive(dp);
//解析数据包
Stringip = dp.getAddress().getHostAddress();
intport = dp.getPort();
Stringtext = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip+ "***" + port + "***" + text);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
public class ChatDemo {
publicstatic void main(String[] args) throws IOException {
DatagramSocketsds = new DatagramSocket();
DatagramSocketrds = new DatagramSocket(12345);
SendThreadst = new SendThread(sds);
ReceiveThreadrt = new ReceiveThread(rds);
Threadt1 = new Thread(st);
Threadt2 = new Thread(rt);
t1.start();
t2.start();
}
}
A:代码如下
客户端追加代码:
//获取服务端的东西
InputStreamis = s.getInputStream();
byte[]by = new byte[1024];
intlen = is.read(by);
Stringtext = new String(by,0,len);
System.out.println("server:"+text);
服务器端追加代码:
//给客户端回馈数据
OutputStreamout = s.getOutputStream();
out.write("哥们已收到".getBytes());
B:代码如下
publicstatic void main(String[] args) throws Exception {
//建立客户端Socket服务,并去进行目的地址连接
Sockets = new Socket("192.168.1.100", 11111);
BufferedReaderbr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriterbw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
BufferedReaderbrServer = new BufferedReader(new InputStreamReader(
s.getInputStream()));
Stringline = null;
while((line = br.readLine()) != null) {
if("over".equals(line)) {
break;
}
bw.write(line);
bw.flush();
StringserverText = brServer.readLine();
System.out.println(serverText);
}
//out用关吗,它是有s建立的,s关闭了,它就关闭了,所以不用单独关
s.close();
}
publicstatic void main(String[] args) throws Exception {
//1:建立服务端对象,监听一个端口
ServerSocketss = new ServerSocket(11111);
//2:通过accept方法获取客户端对象
Sockets = ss.accept();
Stringip = s.getInetAddress().getHostAddress();
System.out.println(ip+ "...connected");
BufferedReaderbr = new BufferedReader(new InputStreamReader(
s.getInputStream()));
BufferedWriterbw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
Stringline = null;
while((line = br.readLine()) != null) {
//打印一下,看到底传递过来了什么数据
System.out.println(line);
bw.write(line.toUpperCase());
}
//5:关闭资源
s.close();
ss.close();
}
这个时候,出现问题了,我们需要考虑什么原因产生的。
1:首先要分析数据是否传递出去,以及数据是否接受到。
没发出去,因为out有缓冲区,必须刷新一下.两端都要刷。
2:还不行,其实是读取到了,但是问题在于readLine读取数据的时候,会根据换行来判断,
到此为止,它不知道你读取到结尾了.修改后的代码如下:
客户端
bw.write(line+ "\r\n");
bw.flush();
服务器:
bw.write(line.toUpperCase()+ "\r\n");
bw.flush();
服务器和客户端都会关闭,因为他是面向连接的。
到此为止,发现我们的数据需要刷新和换行,所以采用PrintWwriter改进。
定义结束标记:
因为while((line=br.readLine())!=null) readLine()是阻塞式的,它还在等着读,对吧.
但是,你客户端流一结束,就相当于给服务端写了一个结束标记.这样的话,服务端就结束了.
那么,大家,我们稍微总结下.
TCP传输容易出现,客户端和服务端都等待的情况,因为客户端和服务端有阻塞式方法存在,比如read,readLine方法.所以,必须要定义结束标记.
read要定义-1结束标记,readLine需要定义回车结束标记,和-1结束标记.
因为readLine底层调用了read.
C:代码如下
publicclass UploadClient {
publicstatic void main(String[] args) throws IOException {
Sockets = new Socket("192.168.1.100", 12321);
BufferedReaderbr = new BufferedReader(new FileReader("java.txt"));
PrintWriterpw = new PrintWriter(s.getOutputStream(), true);
Stringline = null;
while((line = br.readLine()) != null) {
pw.println(line);
}
//第一种解决方案 假如数据里面有一行是over就悲剧了
//pw.println("over");
//第二种解决方案
//s.shutdownOutput();
//接收服务器返回
BufferedReaderbrIn = new BufferedReader(new InputStreamReader(
s.getInputStream()));
Stringstr = brIn.readLine();
System.out.println("server:"+ str);
br.close();
s.close();
}
}
publicstatic void main(String[] args) throws IOException {
ServerSocketss = new ServerSocket(12321);
Sockets = ss.accept();
Stringip = s.getInetAddress().getHostAddress();
System.out.println(ip+ "...connected");
BufferedReaderbr = new BufferedReader(new InputStreamReader(
s.getInputStream()));
PrintWriterpw = new PrintWriter(new FileWriter("server.txt"), true);
Stringline = null;
while((line = br.readLine()) != null) {
//if ("over".equals(line)) {
//break;
//}
pw.println(line);
}
PrintWriterout = new PrintWriter(s.getOutputStream(), true);
out.println("上传成功");
pw.close();
s.close();
ss.close();
}
D:代码如下
客户端不改。
把服务器进行封装。
public class UserThread implements Runnable{
privateSocket s;
publicUserThread(Socket s) {
this.s= s;
}
@Override
publicvoid run() {
//同一个客户端可以发多个文件的,避免产生覆盖,对每个文件进行重写命名
//int count = 0;
Stringip = s.getInetAddress().getHostAddress();
try{
System.out.println(ip+ "...connected");
BufferedReaderbr = new BufferedReader(new InputStreamReader(
s.getInputStream()));
//为了文件不覆盖
Filedir = new File("c:\\txt");
if(!dir.exists()) {
dir.mkdir();
}
//File file = new File(dir, ip + "(" + (count++) + ")" +".txt");
但是同一台机子上可以继续传
//while (file.exists()) {
//file = new File(dir, ip + "(" + (count++) + ")" +".txt");
//}
Filefile = new File(dir, ip + ".txt");
PrintWriterpw = new PrintWriter(new FileWriter(file), true);
Stringline = null;
while((line = br.readLine()) != null) {
pw.println(line);
}
PrintWriterout = new PrintWriter(s.getOutputStream(), true);
out.println("上传成功");
pw.close();
s.close();
}catch (Exception e) {
thrownew RuntimeException(ip + "...");
}
}
}
测试代码:
publicstatic void main(String[] args) throws Exception {
ServerSocketss = new ServerSocket(10011);
while(true) {
Sockets = ss.accept();
//将客户端封装成线程
newThread(new UserThread(s)).start();
}
//ss不能关闭,等待着
}
其实,服务器最底层使用的就是三项基本技术:
Socket,ServerSocket
A:客户端和服务器都写好后,先开启谁呢?
开启服务器端。因为服务器如果不开启,客户端就不知道和谁进行连接啊。