IO流(补)
InputStreamReader和OutputStreamWriter(转换流)
转换流是通过在字节流和字符流之间转换来使得同时拥有字符流的操作广泛性的同时和操作字节流一样方便。
InputStreamReader把字节流转换成字符流进行操作,OutputStreamWriter把字符流转换成字节流
FileInputStream fileInputStream=new FileInputStream("D:\\text.txt");
InputStreamReader inputStreamReader=new InputStreamReader(fileInputStream);
BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
//简洁版
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(new FileInputStream("D:\\text.txt")));
FileOutputStream fileOutputStream=new FileOutputStream("D:\\text.txt");
OutputStreamWriter outputStreamWriter=new OutputStreamWriter(fileOutputStream);
BufferedWriter bufferedWriter=new BufferedWriter(outputStreamWriter);
//简洁版
BufferedWriter bufferedWriter=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\text.txt")));
ObjectInputStream(反序列化流)和ObjectOutputStream(序列化流)
ObjectOutputStream(序列化流)
序列化流可以把java中的对象写到本地文件中。
注意:每一个要被序列化写入文件的对象都要实现Serializable这个接口,实现这个接口不需要去重写任何方法,仅仅只是做一个标记而已,在做这一个标记的时候,系统会根据类中所有的内容来自动的分配一个唯一的long类型的记号,序列化和反序列化就是根据这与这唯一的记号进行的,当类发生变化的时候记号也会发生改变,这是因为文件中的记号与java中的不匹配就会报错。为了避免这种情况我们可以在定义类的时候手动的去给他分配一个记号,这样系统就不会去根据类的内容来更新记号了。
定义方法:
private static final long serialVersionUID = 记号L;
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private static final long serialVersionUID=213L;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
}
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
Student stu=new Student("jjm",19);
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("D:\\text.txt"));
objectOutputStream.writeObject(stu);
objectOutputStream.close();
}
}
ObjectOutputStream
反序列化流可以把文件中的对象读到程序里来。
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("D:\\text.txt"));
Student s =(Student)objectInputStream.readObject();
System.out.println("name:"+s.getName()+"\tage:"+s.getAge());
objectInputStream.close();
}
}
运行结果
transient(瞬态关键字):用这个关键字修饰的成员变量不会再序列化中被写入到文件中。
import java.io.*;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student stu=new Student("jjm",19);
Student student=new Student("gg",18);
ArrayList<Student> arrayList=new ArrayList<>();
arrayList.add(stu);
arrayList.add(student);
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("D:\\text.txt"));
objectOutputStream.writeObject(arrayList);
objectOutputStream.close();
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("D:\\text.txt"));
ArrayList<Student> s =(ArrayList<Student>)objectInputStream.readObject();
for (Student student1 : s) {
System.out.println(student1);
}
objectInputStream.close();
}
}
多线程
多线程的两个方式:并发、并行;
并发:一个cpu核心运行多个线程;
并行:多个cpu核心运行多个线程;
多线程的实现方式
多线程的实现一共有三种方法实现分别是继承Thread类或其子类(可自己编写,要重写run方法)、实现Runnable接口(要重写run方法)、实现Callable接口(要重写call方法)。
在完成了继承或实现接口之后只需要调用start方法就可以了,start方法会向cpu申请开一个线程然后调用用run方法。
第一种方法:继承Thread
public class ThreadTest extends Thread{
@Override
//线程体,启动线程时会运行run()方法中的代码
public void run() {
System.out.println(getName()+"run");
}
public static void main(String[] args) {
//创建一个Thread类的子类对象
ThreadTest t1 = new ThreadTest();
t1.setName("线程一");
t1.start();
//注意:已经启动过一次的线程无法再次启动
//再创建一个线程
ThreadTest t2 = new ThreadTest();
t2.setName("线程二");
t2.start();
//另一种调用方法,不用实例化对象
//因为没有设置名字所以系统会自动的分配一个
new ThreadTest().start();
System.out.println("主线程");
}
}
运行结果
在这里我们发现输出跟我们想的有点不一样,在写代码最后的,输出“主线程”的语句反而第一个运行,这跟java的线程的实现有关;
线程的生命周期:
建立:类被定义但是还没有调用start方法;
就绪:调用了start方法,程序向cpu请求占用并开始等待运行;
运行:一旦请求到了占用就立马开始运行run方法里的代码;
阻塞:但程序遇到了输入和输出和被挂起的时候,放开cpu的占用,运行完后再回到就绪状态;
死亡:当程序运行完毕或出现错误的时候就结束;
第二和第三种方法:实现Runnable和实现Callable
public class Runn implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100); //让程序手动暂停100毫秒可以更好的领会阻塞的作用
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":\t"+i);
}
}
}
public class Runntest {
public static void main(String[] args) {
Runn runn =new Runn();
Thread run=new Thread(runn);
Thread run1=new Thread(runn);
run.setName("线程一");
run1.setName("线程二");
run.start();
run1.start();
}
}
import java.util.concurrent.Callable;
public class Call implements Callable {
@Override
public Object call() {
int sum=0;
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":\t"+i);
sum+=i;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Callabletest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Call call =new Call();
FutureTask<Integer> f=new FutureTask<>(call);
FutureTask<Integer> f1=new FutureTask<>(call);
Thread call1=new Thread(f);
Thread call2=new Thread(f1);
call1.setName("线程一");
call2.setName("线程二");
call1.start();
call2.start();
System.out.println(f.get());
System.out.println(f.get());
}
}
运行结果
一般采用实现接口的方式来实现多线程:
1.因为一个类只能有一个父类所以当一个类有自己的父类的时候就无法继承Thread类,但是一个类可以实现多个接口所以实现接口比继承要方便一点。
2.实现Runnable接口的方式天然具有共享数据的特性(不用static变量)。因为继承Thread的实现方式,需要创建多个子类的对象来进行多线程,如果子类中有变量A,而不使用static约束变量的话,每个子类的对象都会有自己独立的变量A,只有static约束A后,子类的对象才共享变量A。而实现Runnable接口的方式,只需要创建一个实现类的对象,要将这个对象传入Thread类并创建多个Thread类的对象来完成多线程,而这多个Thread类对象实际上就是调用一个实现类对象而已。实现的方式更适合来处理多个线程有共享数据的情况。
线程的优先级
每个线程都有一个优先级(1~10,默认是5),线程的优先级高不意味着一定会优先运行,优先级高只是意味着抢到cpu的概率更高而已。
成员方法
守护线程
守护线程的作用是当在运行的线程中没有一个非守护线程那么他就会自动的去停止运行(不是马上停止是慢慢的依次停止),通过setDaemon方法来设置。
线程的安全问题
因为多线程是同时运行的,所以当他们要同时对一个变量进行修改的时候如果信息传达的不到位就会出现变量被异常修改的情况,比如:我们设置了三个窗口同时进行售卖门票,在这时同时来了三个人分别在三个窗口要买门票如果三个窗口信息交换不及时就会出现一张门票被卖给三个人的情况,程序也会出现这样的情况。
public class SafeTicketsWindow {
public static void main(String[] args) {
WindowThread ticketsThread02 = new WindowThread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class WindowThread implements Runnable {
private int tiketsNum = 100;
public void run() {
while (true) {
if (tiketsNum > 0) {
try {
//手动让线程进入阻塞,增大错票概率
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":\t票号:" + tiketsNum);
tiketsNum--;
} else {
break;
}
}
}
}
运行结果
解决方法
多线程会出现这种情况那么我们只要让程序运行这段代码的时候用一把锁锁住只有一个线程可以运行别的线程都需要等待这个线程运行完毕,锁开了才可以跟着一个一个去运行就可以了(类似与单线程),这样子的操作叫做同步代码块。
格式:
synchronized (锁){
要运行的代码
}
锁(对象)的创建是任意的,但是要成为锁必须具备唯一性且是公用的对象(可以用 类名.class)。同步的代码块越少运行效率就越好,但绝不能少同步。
public class SafeTicketsWindow {
public static void main(String[] args) {
WindowThread ticketsThread02 = new WindowThread();
Thread t1 = new Thread(ticketsThread02);
Thread t2 = new Thread(ticketsThread02);
Thread t3 = new Thread(ticketsThread02);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class WindowThread implements Runnable {
private int tiketsNum = 100;
static Object s=new Object();
public void run() {
while (true) {
synchronized (s) {
if (tiketsNum > 0) {
try {
//手动让线程进入阻塞,增大错票概率
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":\t票号:" + tiketsNum);
tiketsNum--;
} else {
break;
}
}
}
}
}
同步方法
同步的时候还有一种情况也会出现问题叫做:"死锁",这种问题不常见所以就不多说了,知道即可。
网络编程
网络编程的准备
一个软件如果要在多台设备上联机就要通过网络传输数据。那么我们要如何才能在网络中传输数据呢?答案是通过网络传输协议,只有当两台设备使用的是同一个网络传输协议他们才可以通信。目前世界上被广泛运用的网络协议是TCP/TP协议这个协议是通过IP来定位设备端口号来确定应用程序,这个协议有两种传输协议可供使用分别是TCP协议和UDP协议。
每一个设备都被分配有唯一的一个iP地址,这台设备上的应用程序在向外部传输数据的时候会占用一个端口号,传输的数据就是靠ip地址和端口号来确定到底要发送到哪里去的。端口号是可以由用户指定的但有范围,在1024以下的端口号都留给系统自带的服务,1024以上的端口号可以由用户自由使用。“127.0.0.1”是本机的回送地址,指定这个IP就能可以把信息发送到自己的电脑上。
UDP协议
import java.io.IOException;
import java.net.*;
public class UDPsend {
public static void main(String[] args) throws IOException {
//指定一个端口使用
DatagramSocket datagramSocket=new DatagramSocket(8080);
String s="欢迎来到Apex Leagues";
byte [] bytes=s.getBytes();
//把要发送的IP转换成指定格式
InetAddress address=InetAddress.getByName("127.0.0.1");
//要发送到的端口
int port=1080;
//打包数据
DatagramPacket datagramPacket=new DatagramPacket(bytes,bytes.length,address,port);
//发送打包好的数据
datagramSocket.send(datagramPacket);
datagramSocket.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPaccept {
public static void main(String[] args) throws IOException {
//指定一个端口使用
DatagramSocket datagramSocket=new DatagramSocket(1080);
byte [] bytes=new byte[1024];
//新建一个包用来接收数据
DatagramPacket datagramPacket=new DatagramPacket(bytes,bytes.length);
//接收数据,如果没接收到会一直停在这里
datagramSocket.receive(datagramPacket);
//从包里获取数据
byte [] bytes1=datagramPacket.getData();
int len=datagramPacket.getLength();
InetAddress inetAddress=datagramPacket.getAddress();
int port=datagramPacket.getPort();
//输出
System.out.println(new String(bytes1,0,len)+"\t"+inetAddress+"\t"+len+"\t"+port);
datagramSocket.close();
}
}
接收的运行结果
TCP协议
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class inter1 {
public static void main(String[] args) throws IOException {
//为客户端指定一个服务器,并告知IP地址和端口号
String ipAddress="127.0.0.1";
int portNumber=8887;
Socket socket=new Socket(ipAddress,portNumber);
//使OutputStream从把数据发送到指定的IP和端口
OutputStream outputStream=socket.getOutputStream();
//从键盘上获取数据
InputStreamReader inputStreamReader=new InputStreamReader(System.in);
char [] s= new char[100];
int x=inputStreamReader.read(s);
//发送数据
outputStream.write(new String(s,0,x).getBytes());
outputStream.close();
socket.close();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class inter {
public static void main(String[] args) throws IOException {
//为服务器指定一个端口使用
ServerSocket serverSocket =new ServerSocket(8887);
//等待客户端的链接
Socket socket=serverSocket.accept();
//使InputStreamReader从端口的IO流中获取数据
InputStreamReader inputStreamReader= new InputStreamReader(socket.getInputStream());
//开始获取数据
char []string =new char[1000];
//返回值为获取到的数据的个数
int x=inputStreamReader.read(string);
//输出到控制台
System.out.println(new String(string,0,x));
socket.close();
serverSocket.close();
}
}
客户端运行结果:从键盘输入并发送
服务端运行结果:接收并输出