io流多线程总结

来源:互联网 发布:便利店文化知乎 编辑:程序博客网 时间:2024/04/30 11:01

IO流

1.概念

IO流用来处理设备之间的数据传输

Java对数据的操作是通过流的方式

Java用于操作流的类都在IO包中

流按流向分为两种:输入流,输出流。

流按操作类型分为两种:字节流与字符流。  字节流可以操作任何数据,字符流只能操作纯字符数据,比较方便。

2.IO流常用父类

字节流的抽象父类:

InputStream ,OutputStream

字符流的抽象父类:

Reader , Writer

3.IO程序书写

使用前,导入IO包中的类

使用时,进行IO异常处理

使用后,释放资源

字节流

1.读取文件

创建FileInputStream对象, 指定一个文件. 文件必须存在, 不存在则会抛出FileNotFoundException

使用read()方法可以从文件中读取一个字节. 如果读取到文件末尾会读到-1

读取结束后需要释放资源, 调用close()方法关闭输入流

2.写出文件

创建FileOutputStream对象, 指定一个文件. 文件不存在会创建新文件, 存在则清空原内容. 如果需要追加, 在构造函数中传入true.

使用write()方法可以向文件写出一个字节.

写出结束后同样需要调用close()

3.拷贝文件

可以从文件中逐个字节读取, 逐个字节写出, 但这样做效率非常低

我们可以定义一个数组作为缓冲区, 一次读取多个字节装入数组, 然后再一次性把数组中的字节写出1byte = 8bit

4.常用方法

InputStream:

read() 读取一个字节

read(byte[]) 读取若干(数组长度)字节

available() 获取可读的字节数

close() 关闭流, 释放资源

OutputStream:

write(int) 写出一个字节

write(byte[]) 写出数组中的所有字节

write(byte[],start,len);

close() 关闭流, 释放资源

5.BufferedInputStream

BufferedInputStream内置了一个缓冲区(数组)

从BufferedInputStream中读取一个字节时

BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 返回给程序一个

程序再次读取时, 就不用找文件了, 直接从缓冲区中获取

直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个

6.BufferedOutputStream

BufferedOutputStream也内置了一个缓冲区(数组)

程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中

直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里

字符流

1.字符流是什么

字符流是可以直接读写字符的IO流

字节流只能读写字节. 如果要读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.   

2.FileReader, FileWriter

FileReader类的read()方法可以按照字符大小读取

FileWriter类的write()方法可以自动把字符转为字节写出

3.什么情况下使用字符流

字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.

程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流

4.带缓冲的字符流

BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率

BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率

BufferedReader的readLine()方法可以读取一行字符(不包含换行符号)

BufferedWriter的newLine()可以输出一个跨平台的换行符号

5.LineNumberReader

LineNumberReader是BufferedReader的子类, 具有相同的功能, 并且可以统计行号

调用getLineNumber()方法可以获取当前行号

调用setLineNumber()方法可以设置当前行号

6.使用指定的码表读取字符

FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader

FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter

File

1.什么是File类

File类对象可以代表一个路径, 此路径可以是文件也可以是文件夹, 该类方法可以对这个路径进行各种操作

2.创建对象

给File类构造函数传一个String类型的路径就可以创建对象

路径分为两种: 绝对路径, 相对路径

绝对路径: 从盘符开始, 是一个固定的路径

相对路径: 不从盘符开始, 相对于某个位置. 在Eclipse中的Java工程如果使用相对路径, 那么就相对于工程根目录. cmd则相对应于当前目录.

3.常用方法

boolean exists() 判断是否存在

boolean isAbsolute(); 是否是绝对路径

boolean isDirectory(); 是否是文件夹

boolean isFile(); 是否是文件

boolean isHidden(); 是否是隐藏

getAbsolutePath(); 获取绝对路径

getFreeSpace(); 获取当前盘符剩余空间

getTotalSpace(); 获取当前盘符总空间

getUsableSpace(); 获取当前盘符可用空间

getParent()); 获取父级路径

getName()); 获取文件名

setReadable(false); 设置是否可读

setWritable(false); 设置是否可写

setExecutable(false); 设置是否可执行

canRead(); 是否可读

canWrite(); 是否可写

canExecute(); 是否可执行

setLastModified(); 设置文件的最后修改时间

lastModified(); 获取文件的最后修改时间

createNewFile() 创建文件

mkdir(); 创建文件夹(仅一级)

mkdirs(); 创建文件夹(父级不存在也创建)

renameTo(); 改名, 可以移动文件

delete() 删除, 文件可以直接删除, 文件夹只能删空的

length() 文件大小

String[] list()

File[] listFiles() 

异常

(1)就是程序运行过程中,遇到了问题,这就叫异常。

(2)异常的体系

Throwable 其实应该分三种

Error

通常出现重大问题如:服务器宕机数据库崩溃。

    不编写针对代码对其处理(这个我们是无能为力的)。

Exception

除了 RuntimeException 和其所有子类,其他所有的异常类都是在编译的时候必须要处理的

要么try,要么抛

RuntimeException

RuntimeException 和其所有的子类,都不会在编译的时候报异常,而是在运行时报异常,这时候我们

就需要回头看看我们的代码是否有问题,比如角标越界,空指针等

(3)Throwable

A:getMessage() :返回此 throwable 的详细消息字符串。

B:toString():获取异常类名和异常信息,返回字符串。

C:printStackTrace():获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。

(4)处理异常处理方式:

A:try...catch...finally

格式:

try {

需要检测的代码;

}

catch(异常类  变量) {

异常处理代码;

}

...

finally {

一定会执行的代码;

}

可以有下面三种搭配形式:

**try...catch(...)

**try...finally

**try...catch(...)...finally

B:抛出 throws throw

throws:用于标识函数暴露出的异常。thorws用在函数上,后面跟异常类名(可以由多个,隔开)。

throw:用于抛出异常对象。throw用在函数内,后面跟异常对象。new Exception();

C:到底用谁?

**你能处理,建议处理。try...catch...finally

**你处理不了,抛出。

**在实际开发中,是分层开发,底层代码是能抛出尽量抛出,到顶层的用日志记录住异常信息,并提供解决方案

D:自定义异常

自定义类继承Exception或者其子类(RuntimeException)

class MyException extends Exception{

MyException(){}

MyException(String message){

super(message); //将信息传递给父类,调用父类封装好的构造方法

}

}

class Student {

public void giveAge(int age) throws MyException {

if(age>40 || age<16) {

//throw new MyExcetpion("建议不学了");

MyExcepiont my = new MyExcetpion("建议不学了");

throw my;

}

else {

System.out.println("可以学习Java");

}

}

}

E:RuntimeException和Exception

区别:RuntimeException就是要你改代码的。你可以不处理。

序列流

1.什么是序列流

序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类推.

2.使用方式

整合两个: SequenceInputStream(InputStream, InputStream)

整合多个: SequenceInputStream(Enumeration)

内存输出流

1.什么是内存输出流

该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据

2.使用方式

创建对象: new ByteArrayOutputStream()

写出数据: write(int), write(byte[])

获取数据: toByteArray()

对象操作流

1.什么是对象操作流

该流可以将一个对象写出, 或者读取一个对象到程序中. 也就是执行了序列化和反序列化的操作.

2.使用方式

写出: new ObjectOutputStream(OutputStream), writeObject()

读取: new ObjectInputStream(InputStream), readObject()

3.注意

要写出的对象必须实现Serializable接口才能被序列化

打印流

1.什么是打印流(PrintStream)

该流可以很方便的将对象的toString()结果输出, 并且自动加上换行, 而且可以使用自动刷出的模式

System.out就是一个PrintStream, 其默认向控制台输出信息

2.使用方式

打印: print(), println()

自动刷出: PrintWriter(OutputStream out, boolean autoFlush)

标准输入输出流

1.什么是标准输入输出流

System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据

System.out是PrintStream, 标准输出流, 默认可以向Console中输出字符和字节数据

2.修改标准输入输出流

修改输入流: System.setIn(InputStream)

修改输出流: System.setOut(PrintStream)

数据输入输出流

1.什么是数据输入输出流

DataInputStream, DataOutputStream可以按照基本数据类型大小读写数据

例如按Long大小写出一个数字, 写出时该数据占8字节. 读取的时候也可以按照Long类型读取, 一次读取8个字节.

2.使用方式

DataInputStream(InputStream), readInt(), readLong()

DataOutputStream(OutputStream), writeInt(), writeLong()

Properties  集合

1.向内存中存入值,并通过键获取值setProperty(key,value) getProperty(key);

2.通过load方法,读取配置文件,propertyNames获取所有的key,返回Enumeration

3.根据键改值,并重新存入到配置文件setProperty(key,value),list(new PrintStream())

System.getProperties();获取系统属性,propertyNames将所有的键返回到枚举里,就可以迭代了

线程的概念

1.什么是线程

线程是程序执行的一条路径, 一个进程中可以包含多条线程

多线程并发执行可以提高程序的效率, 可以同时完成多项工作

2.多线程的应用场景

红蜘蛛同时共享屏幕给多个电脑

迅雷开启多条线程一起下载

QQ同时和多个人一起视频

服务器同时处理多个客户端请求

开启新线程

1.继承Thread

定义类继承Thread

重写run方法

把新线程要做的事写在run方法中

创建线程对象

开启新线程, 内部会自动执行run方法

2.实现Runnable

定义类实现Runnable接口

实现run方法

把新线程要做的事写在run方法中

创建自定义的Runnable对象

创建Thread对象, 传入Runnable

调用start()开启新线程, 内部会自动调用Runnable的run()方法

3.两种方式的区别

区别一:

a.由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法

b.构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable是否为空, 不为空则执行Runnable的run()

区别二:

a.继承Thread只能是单继承,如果自己定义的线程类已经有了父类,就不能再继承了

b.实现Runnable接口可以多实现,即使自己定义线程类已经有父类可以实现Runnable接口

总结:继承Thread的好处是:可以直接使用Thread类中的方法,代码简单

弊端是:如果已经有了父类,就不能用这种方法

  实现Runnable接口的好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的

弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

Thread类常用方法

1.设置名字

通过构造函数可以传入String类型的名字

通过setName(String)方法可以设置线程对象的名字

2.获取名字

通过getName()方法获取线程对象的名字

3.获取当前线程对象

Thread.currentThread(), 主线程也可以获取

4.休眠

Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000

5.守护

setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

6.加入

join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续

join(int), 可以等待指定的毫秒之后继续

线程之间的同步

1.什么情况下需要同步

当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.

如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

2.同步代码块

使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

3.同步方法

使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

非静态同步方法默认使用当前对象this作为锁对象

静态方法同步默认的锁对象是,所在类的字节码对象

4.线程安全问题

多线程并发操作同一数据时, 就有可能出现线程安全问题

使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作

5.死锁问题

多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁

尽量不要嵌套使用

一.线程的方法

1.yield让出cpu

2.setPriority()设置线程的优先级

二.线程之间的通信

1.什么时候需要通信

多个线程并发执行时, 在默认情况下CPU是随机切换线程的

如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

2.怎么通信

如果希望线程等待, 就调用wait()

如果希望唤醒等待的线程, 就调用notify();

这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

3.多个线程通信的问题

notify()方法是随机唤醒一个线程

notifyAll()方法是唤醒所有线程

JDK5之前无法唤醒指定的一个线程

如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件

三.JDK5之后的线程控制

1.同步

使用ReentrantLock类的lock()和unlock()方法进行同步

2.通信

使用ReentrantLock类的newCondition()方法可以获取Condition对象

需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法

不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

四.GUI

1.事件处理

用户的一个操作就是一个事件, 事件处理就是在事件发生的时候程序做出什么反应

事件发生在哪个组件上, 哪个组件就是事件源

给事件源添加一个监听器对象

监听器对象中包含若干事件处理方法

如果时间发生了, 事件处理方法就会自动执行

3.适配器

a.什么是适配器

在使用监听器的时候, 需要定义一个类时间监听器接口.

通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.

适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.

b.适配器原理

适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.

目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.

IP地址

每个设备在网络中的唯一标识

每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。

ipconfig:查看本机IP

ping:测试连接

本地回路地址:127.0.0.1 255.255.255.255是广播地址

IPv4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。

IPv6:8组,每组4个16进制数。

1a2b:0000:aaaa:0000:0000:0000:aabb:1f2f

1a2b::aaaa:0000:0000:0000:aabb:1f2f

1a2b:0000:aaaa::aabb:1f2f

1a2b:0000:aaaa::0000:aabb:1f2f

1a2b:0000:aaaa:0000::aabb:1f2f

端口号

每个程序在设备上的唯一标识

每个网络程序都需要绑定一个端口号,传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序。

端口号范围从0-65535

编写网络应用就需要绑定一个端口号,尽量使用1024以上的,1024以下的基本上都被系统程序占用了。

常用端口

mysql: 3306

oracle: 1521

web: 80

tomcat: 8080

QQ: 4000

feiQ: 2425

网络协议

为计算机网络中进行数据交换而建立的规则、标准或约定的集合。

UDP

面向无连接,数据不安全,速度快。不区分客户端与服务端。

TCP

面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。

三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据

Socket

通信的两端都有Socket。

网络通信其实就是Socket间的通信。

数据在两个Socket间通过IO流传输。

Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。

UDP传输

1.发送

创建DatagramSocket, 随机端口号

创建DatagramPacket, 指定数据, 长度, 地址, 端口

使用DatagramSocket发送DatagramPacket(send)

关闭DatagramSocket

2.接收

创建DatagramSocket, 指定端口号

创建DatagramPacket, 指定数组, 长度

使用DatagramSocket接收DatagramPacket()

关闭DatagramSocket

从DatagramPacket中获取数据

3.接收方获取ip和端口号

String ip = packet.getAddress().getHostAddress();

int port = packet.getPort();

TCP传输

1.客户端

创建Socket连接服务端(指定ip地址,端口号)通过ip地址找对应的服务器

调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的IO流

输入流可以读取服务端输出流写出的数据

输出流可以写出数据到服务端的输入流

2.服务端

创建ServerSocket(需要指定端口号)

调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket

调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流

输入流可以读取客户端输出流写出的数据

输出流可以写出数据到客户端的输

  synchronized的问题

  StringBuffer和StringBuilder

  StringBuffer是线程安全的,效率低

  StringBuilder是线程不安全的,效率高

 

  Vector和ArrayList

  Vector是线程安全的,效率低

  ArrayList是线程不安全的,效率高

 

  Hashtable和HashMap

  Hashtable是线程安全的,效率低

  Hashtable不能存null键和null值

  HashMap是线程不安全的,效率高

  HashMap可以存储null键和null值

0 0
原创粉丝点击