java网络方面的输出/入流。
-
http协义下:DataOutputStream ,BufferedReader,InputStreamReader,InputStream.
------InputStream,OutputStream这两个流是字节流,很不好用。一般是保存文件用它。而读取显示时用BufferedReader字符流。
-----输出流,注入数据到https请求报文中,以post请求方式访问远程服务器。再通过输入流获取数据到本地保存为一个文件中。
本示例:访问一个远程jsp页面内容保存到本地中,java项目为javanetModule1,文件Demo5.java,远程服务器项目qihangkttest---IndexController.java,WEB-INF-jsp-index1.jsp。
/**
* 该类功能:测试:post ,get请求服务器的区别(web服务器是:springmvc项目:qihangkttest,IndexController.java index1.jsp)
* @author x
*
*/
public class Demo5 {
public static void main(String args[]) throws Exception {
String urlPath = "http://localhost/qihangkt/postIndex.html";
//要提交给服务器的数据,汉字要编码(
String param = "name="+URLEncoder.encode("熊少文","UTF-8");
//建立连接
URL url = new URL(urlPath);
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
//设置参数
conn.setDoOutput(true); //post请求,它是把数据塞到https协议中报文中,所以我们要设置true允许修改输出。
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
//设置请求头信息
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive"); //长连接
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//获取到输出流,才能把param放到请求体(报文中)
conn.connect();
DataOutputStream dos=new DataOutputStream(conn.getOutputStream()); //数据输出流
dos.writeBytes(param);
dos.flush(); //真正塞进去
dos.close();
//获取服务器响应回来的信息
int resultcode = conn.getResponseCode();
if(resultcode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(),"UTF-8"));
String line = null;
while((line = reader.readLine())!=null) {
System.out.println(line);
}
}
}
}
IndexController.java---(spring mvc知识见简书相关资料)
@Controller
public class IndexController {
@GetMapping(value= {"/index.html","/","index"})
public String index() {
return "index";
}
@RequestMapping(value= {"/getIndex.html"},method=RequestMethod.GET)
public String getIndex(Model model,HttpServletRequest request) {
model.addAttribute("name","get请求过来的值--"+request.getParameter("name"));
return "index1";
}
@RequestMapping(value="/postIndex.html",method=RequestMethod.POST)
public String postIndex(Model model,HttpServletRequest request) throws UnsupportedEncodingException {
request.setCharacterEncoding("UTF-8");
model.addAttribute("name","post请求过来的值--"+request.getParameter("name"));
System.out.println(request.getParameter("name"));
return "index1";
}
}
index1.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>测试get请求与post请求区别</title>
</head>
<body>
用户传过来的值:<h2>${name}</h2>
</body>
</html>
测试:运行Demo5.java主函数
-
http协议下:RandomAccessFile可以创建文件保存任何类型的文件,这里我们保存图片
项目:javanetModuel1
文件:下载保存工具类 DownUtil.java 测试类:Demo4.java
DownUtil具有多线程功能,可以在创建该类对象时定义线程数。
public class DownUtil {
//首先需要的东西,要知道下载的目标文件的URL
private String urlPath;
//确定保存文件的位置
private String targetFile;
//确定下载用几条线程
private int threadNum;
//定义一个数组,放的是下载的线程类
private DownThread[] threads;
//把用于下载的线程类定义为工具里的内部类
private class DownThread extends Thread{
//线程类里边的具体实现
private int startPos; //开始位置(下载)
private int currentPartSize;//当前负责的块大小
private RandomAccessFile currentPart; //当前下载的文件块
//记录每条线程已经下载下来的文件的字节数
private int length;
public DownThread(int startPos,int currentPartSize,RandomAccessFile currentPart) {
this.startPos = startPos;
this.currentPartSize = currentPartSize;
this.currentPart = currentPart;
}
@Override
public void run() {
try { //类内部异常不可直抛出
URL url = new URL(urlPath);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//真正链接目标之前,要设置头部信息。
conn.setConnectTimeout(5000); //5秒后不链接
conn.setRequestMethod("GET"); //GET就够了,只是一个字符串(文件名)请求。
conn.setRequestProperty("Accept", "*/*"); //客户端可处理任何文件
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive"); //保持TCP连接暂时不断
//前面设置这么多东西,一个目的。取得目标文件的大小
//链接上目标
conn.connect(); //可不写
InputStream in=conn.getInputStream();
//把in的指针,跳到该线程负责下载的位置
in.skip(this.startPos);
byte[] buffer =new byte[1024];
int hasRead =0;
while(length<currentPartSize && (hasRead=in.read(buffer))!=-1) { //线程下载不能大于当前负责块的大小,也不下载完毕后再下载(最后一个线程考滤的)
currentPart.write(buffer,0,hasRead);
length+=hasRead;
}
currentPart.close();
in.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
//保存一下目标文件的大小
private int fileSize;
//构造器,让外界调用
public DownUtil(String urlPath,String targetFile,int threadNum) {
this.urlPath=urlPath;
this.targetFile=targetFile;
this.threadNum = threadNum;
this.threads = new DownThread[threadNum]; //初始化线程个数
}
//定义一个实现下载文件的方法
public void download() throws Exception { //工具类一般我们挽出异常,不用try catch.Exception是最大的异常类
//第一件事儿,获取目标文件的大小
URL url = new URL(urlPath);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//真正链接目标之前,要设置头部信息。
conn.setConnectTimeout(5000); //5秒后不链接
conn.setRequestMethod("GET"); //GET就够了,只是一个字符串(文件名)请求。
conn.setRequestProperty("Accept", "*/*"); //客户端可处理任何文件
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive"); //保持TCP连接暂时不断
//前面设置这么多东西,一个目的。取得目标文件的大小
//链接上目标
conn.connect();
fileSize = conn.getContentLength(); //获取目标大小
conn.disconnect(); //用了以后,及时关掉资源通道
//根据文件的大小和下载线程数量,来进行目标文件切块
//先在本地创建一个和目标文件大小的文件。
RandomAccessFile file = new RandomAccessFile(targetFile, "rw");//RandomAccessFile唯一父类是Object,与其他流父类不同。是用来访问那些保存数据记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了。
file.setLength(fileSize);
file.close();
//每个线程负责下载的文件块的大小
int currentPartSize = fileSize/threadNum+1; //例:500B分5块,起始址:0-101,102-202..。
for(int i=0;i<threadNum;i++) {
//设置每个线程下载文件块的开始值
int startPos = i*currentPartSize;
//让每个线程使用一个RandomAccessFile的对象来进行下载
RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw");
//每个线程负责下载的文件块
currentPart.seek(startPos); //移动文件指针,找到下载位置
threads[i] =new DownThread(startPos,currentPartSize,currentPart);
//让线程启动,开始下载
threads[i].start();
}
}
//获取下载完成的百分比
public double getCompleteRate() {
int sumSize = 0;
for(int i=0;i<threadNum;i++) {
sumSize+=threads[i].length;
}
return sumSize *1.0/fileSize;
}
}
public class Demo4 {
public static void main(String args[]) throws Exception {
DownUtil downUtil = new DownUtil("https://qcloud.dpfile.com/pc/47nSY2z-B4uD9sQCvj-.jpg","c:\\xiong/aaa.jpg", 5);
downUtil.download(); //下载
new Thread(new Runnable() {
@Override
public void run() {
while(downUtil.getCompleteRate()<=1) {
System.out.println("已下载: "+downUtil.getCompleteRate());
try {
Thread.sleep(10);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
- Socket实现服务器与客户端互通
----本实例应用BufferedReader字符输入流读取请求数据,显示到控制台中。
套接字
在网络上很多应用程序都是采用客户端/服务器(C/S)的模式,实现网络通信必须将两台计算机连接起来建立一个双向的通信链路,这个双向通信链路的每一端在Java的网络编程中,我们又称之为一个套接字(Socket)。套接字(Socket)是一个抽象出来的概念代表在端上的通信链路。
----上面的只是一端请求web,再响应客户,没有互通功能。
socket会先在服务器端阻塞,等客户端有请求,马上响应,可以实时给客户。
Server.java
public class Server {
public static void main(String[] args) throws IOException {
//根据流程第一步,创建ServerSocket
ServerSocket serverSocket = new ServerSocket(); //无参数,表示socket没有链接到任何设备上
serverSocket.bind(new InetSocketAddress("127.0.0.1",40000)); //链接到本机,端口号40000
//用一个循环来不断的接收客户端的链接
while(true) {
//接收客户端的链接请求,
Socket clientSocket=serverSocket.accept(); //此方法会阻塞,直到客户端有请求为止,他会返回一个与连进来的客户端一一对应的socket
//通过clientSocket对应的输入流来接收数据
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));//InputStream该流是字节码流,很不好用,所以别建字符流
//通过in获取客户端发来的信息
String str = in.readLine();
System.out.println("客户端发来的信息: "+str);
//发送信息
PrintStream ps = new PrintStream(clientSocket.getOutputStream(),true,"UTF-8"); //ture自动flush,'GBK'表示DOS用的中文码
ps.println("我是服务器!");
ps.close();
clientSocket.close();
}
}
}
Client.java
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(); //无参,默认连接本机
socket.connect(new InetSocketAddress("127.0.0.1",40000));
//发送数据
PrintStream ps=new PrintStream(socket.getOutputStream(),true,"UTF-8");
ps.println("我是客户端");
//接收数据
BufferedReader in =new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
String str=in.readLine();
System.out.println("服务器端发送来的数据是: "+str);
in.close();
ps.close();
socket.close();
}
}
测试:先运行Server.java 的main方法,serverSocket.accept();会阻塞,直到客户端有请求为止,再运行Client.java的main方法
文件I/O操作
----要把程序所处理的数据在不同的内存容器(内存或外存)进行传输,例如将内存数据写到外存上(某个文件中),就要用到I/O(输入/输出流)技术。JAVA中,所有的I/O机制都是基于数据‘流’方式进入输入/输出。
---这些流序列中的数据通常有两种方式:文本流与二进制流。
---Java语言中定义了许多专门负责各种方式的输入/输出,这些类都被放在java.io包中:有标准输入 /输出,文件的操作,网络上的数据流,字符串流和对象流等。
1-File文件类
----File类主要是JAVA为文件这块的操作(如删除、新建等)而设计的相关类File类的包名是java.io,其实现了Serializable, Comparable两大接口以便于其对象可序列化和比较。
----File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法重点:记住这第三个单词
file——文件
directory——文件夹/目录
path——路径
彻底搞明白绝对路径和相对路径
绝对路径————是一个完整的路径,如以盘符( 比如C:)开始的路径
c:\a.txt
c:\demo\b.txt
相对路径————是一个简化的路径
相对指的是相对于当前项目的根目录
如果使用当前项目的根目录,路径可以简化书写
'D:\java\Java语言高级\File类\File类\File类的概述.avi'–--------->简化为'File类的概述.avi'(可以省略项目的根目录)
java项目中,相对路径一般指英目根目录。
下面是几种获取路径的方法'/'是项目根目录,该项目在C:\Users\x\Desktop\STSWORKSPACE\中
/**
* 路径的测试:windows 中 d:\\a.txt.
* @author x
*
*/
public class FileTest1 {
public static void main(String args[]) {
FileTest1 fileTest1 = new FileTest1();
try {
fileTest1.getUrl();
} catch (IOException e) {
e.printStackTrace();
}
}
public void getUrl() throws IOException {
//1.使用类加载路径(反射)
String path="";
path=this.getClass().getResource("/").getPath();
System.out.println("1: "+path); //编译后的.class存放的根目录
path= this.getClass().getResource("").getPath();
System.out.println("当在类所在的路径,是加载类不是源码java:"+path);
//2.利用File类获取相关路径
File file = new File("");
path = file.getCanonicalPath();
// path= file.getAbsolutePath();
System.out.println(path); //项目根目录
//3.实际上还是第一种方法
URL path1 =this.getClass().getResource("");
System.out.println(path1); //file:/C:/Users/x/Desktop/STSWORKSPACE/javanetModule1/bin/cn/ybzy/demo/File/
//4.利用System类
path = System.getProperty("user.dir");
System.out.println("4:"+path); //C:\Users\x\Desktop\STSWORKSPACE\javanetModule1
//5.利用线程
path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
System.out.println("5:线程获取路径: "+path); ///C:/Users/x/Desktop/STSWORKSPACE/javanetModule1/bin/
//6.在web项目中,request
//request.getSession().getServletContext().getRealPath("/")); //根的绝对路径
}
}
注意
1.路径是不区分大小写
2.路径中的文件名称分隔符widows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通反斜杠。
public class FileProperties {
public static void main(String args[]) {
System.out.println("File属性读取path路径分隔符 "+File.pathSeparator); //windows中是分号,在LINUX中运行此程序为冒号 :(path变量中的分隔符)
System.out.println("File属性读取path路径分隔符 "+File.pathSeparatorChar);
System.out.println(File.separator); //winodws中显示为反斜杠 '\',Linux中显示为'/',所以我们写创建文件是为了跨平台,最好写成带属性的样子。
//File file = new File("C:"+File.separator+"a.txt");
// File file =new File("xiong/a.txt"); //项目根目录下的xiong目录下创建a.txt文件
File file = new File("D:\\xiong"); //或 File file = new File("D:/xiong");
try {
//file.createNewFile(); //发生IOException,该异常对创建目录不起作用。
file.mkdir(); //创建目录不发生异常
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(File.separatorChar);
// File file2 = new File("xiong\\shao\\wen"); //创建多级目录
File file2 = new File("d:/iotest/xiong/shao/wen");
file2.mkdirs();
//遍历(文件)目录
/* File file3 = new File("d:/iotest/xiong/shao/wen");
String[] fileName = file3.list();
File[] files = file3.listFiles();
for(String filename:fileName) {
System.out.println("文件名:"+filename);
}
for(File subFile:files) {
System.out.println(subFile); //不会拿到wen子目录下的文件
}*/
//遍历所有文件,过滤器过滤java扩展名的文件
File file4 = new File("D:/iotest/xiong/shao/wen");
getJavaFiles(file4); //调用过滤器,只显示所有扩展名为.java的文件。
}
//过滤器,过滤所有java类文件
public static void getJavaFiles(File file) {
if(!file.isDirectory()) {
return; //如果不是文件夹,不作任何操作
}
//是文件夹
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File subFile) {
//在这个方法体写上过滤规则 1,子目录。2,java文件
if(subFile.isDirectory()) {
return true;
}else {
return subFile.getName().toLowerCase().endsWith(".java");
}
}
});
//过滤后的结果文 件我要看看
for(File rsFile:files) {
if(rsFile.isDirectory()) {
getJavaFiles(rsFile); //使用递归来进入更深层次的文件夹
}else {
System.out.println(rsFile);
}
}
}
}
测试:我们把一个项目根目所所有文 件复制到wen目录下,再运行主方法在控制台可以看到效果,如下两图
File类常用的方法
public class Filefangfa {
public static void main(String args[]) {
File file = new File("D:/iotest/xiong/shao/wen/a.txt"); //并不在硬盘上创建文件,需要像 FileProperties.java那样搞,目录由 mkdir创建
System.out.println(file.getName());
System.out.println(file.getPath()); //获取相对路径,当路径写成绝对路径形式,下面两个方法返回一样的内容
System.out.println(file.getAbsolutePath()); //D:\iotest\xiong\shao\wen\a.txt
// File file = new File("xiong1");
// System.out.println(file.getName());
// System.out.println(file.getPath()); //xiong ,获取相对路径,当路径写成绝对路径形式,下面两个方法返回一样的内容
// System.out.println(file.getAbsolutePath()); //C:\Users\x\Desktop\STSWORKSPACE\javanetModule1\xiong
System.out.println("父目录:"+file.getParent()); //D:\iotest\xiong\shao\wen 当a.txt是目录的话,这里会显示D:\iotest\xiong\shao\wen\a.txt 当然了windows中不会这样显示但linux中会显示
//file.renameTo(new File("C:\\Users\\x\\Desktop\\STSWORKSPACE\\javanetModule1\\xiong2")); //为文件改名
//文件检查有关的方法
System.out.println("文件是否存在:"+file.exists());
System.out.println("日录吗?:"+file.isDirectory()); //是否为目录
System.out.println("目录没做一个文看待:"+file.isFile()); //目录没做一个文件看待
System.out.println("能写吗:"+file.canWrite()); //文件能写吗
System.out.println("能读吗: "+file.canRead());
System.out.println("是绝对路径吗"+file.isAbsolute()); //是绝对路径吗
System.out.println("文件的长度: "+file.length());
// file.delete(); //删除文件,不会放到回收站,小心使用,不会产生异常,不管有没有文件
}
}
////////////////////////////////////////////////////////结果//////////////////////////////////////////////
a.txt
D:\iotest\xiong\shao\wen\a.txt
D:\iotest\xiong\shao\wen\a.txt
父目录:D:\iotest\xiong\shao\wen
文件是否存在:true
日录吗?:false
目录没做一个文看待:true
能写吗:true
能读吗: true
是绝对路径吗true
文件的长度: 0
Java IO流
---大多数应用程序都需要实现与设备之间的数据传输,例如键盘可以输入数据,显示器可以显示程序的运行结果等。
---在Java中,将这种通过不同输入输出设备(键盘,内存,显示器,网络等)之间的数据传输抽象的表述为“流”。
---Java中的“流”都位于java.io包中,称之为IO(输入输出)流。
输入流和输出流是相对于内存设备而言的,将外设中的数据读取到内存中即输入,将内存的数据写入到外设中即输出。
IO流的分类:
---按照不同的分类方式,可以把流分为不同的类型。常用的分类有三种:
一,按照流的流向分,可以分为输入流和输出流。
输入流: 只能从中读取数据,而不能向其写入数据。
输出流:只能向其写入数据,而不能向其读取数据。
此处的输入,输出涉及一个方向的问题,数据从内存到硬盘,通常称为输出流
此处的输入,输出涉及一个方向的问题,数据从内存到硬盘,通常称为输出流
数据从服务器通过网络流向客户端,在这种情况下,Server端的内存负责将数据输出到网络里,
因此Server端的程序使用输出流;
Client端的内存负责从网络中读取数据,因此Client端的程序应该使用输入流。
注:java的输入流主要是InputStream和Reader作为基类,而输出流则是主要由outputStream和Writer作为基类。它们都是一些抽象基类,无法直接创建实例。
二,按照操作单元划分,可以划分为字节流和字符流。
---字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
---字节流主要是由InputStream和outPutStream作为基类,而字符流则主要有Reader和Writer作为基类。
三,按照流的角色划分为节点流和处理流。
---可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,称为节点流。 节点流也被称为低级流。
---处理流则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。 处理流也被称为高级流。
当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入和输出节点连接。使用处理流的一个明显的好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源
打个比方在进一步理解流的概念
java把所有设备里的有序数据抽象成流模型,简化了输入/输出处理。java IO流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。
《1》InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
《2》OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
对于InputStream和Reader而言,它们把输入设备抽象成为一个”水管“,这个水管的每个“水滴”依次排列,如图
输入流使用隐式的记录指针来表示当前正准备从哪个“水滴”开始读取,每当程序从InputStream或者Reader里面取出一个或者多个“水滴”后,记录指针自定向后移动;除此之外,InputStream和Reader里面都提供了一些方法来控制记录指针的移动。
对于OutputStream和Writer而言,它们同样把输出设备抽象成一个”水管“,只是这个水管里面没有任何水滴,如图
当执行输出时,程序相当于依次把“水滴”放入到输出流的水管中,输出流同样采用隐示指针来标识当前水滴即将放入的位置,每当程序向OutputStream或者Writer里面输出一个或者多个水滴后,记录指针自动向后移动。
处理流可以“嫁接”在任何已存在的流的基础之上,这就允许Java应用程序采用相同的代码,透明的方式来访问不同的输入和输出设备的数据流。
字节流和字符流的基类方法
IO体系的基类(InputStream/Reader,OutputStream/Writer)
字节流和字符流的操作方式基本一致,
只是操作的数据单元不同——字节流的操作单元是字节,字符流的操作单元是字符。
所以字节流和字符流就整理在一起了。
InputStream/Reader,OutputStream/Writer是所有输入/输出流的抽象基类,
本身并不能创建实例来执行输入/输出,
但它们将成为所有输入/输出流的模板,
所以它们的方法是所有输入/输出流都可使用的方法。
在InputStream里面包含读数据的方法。
一。int read(); 从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)。
二。int read(byte[] b)从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
三。int read(byte[] b,int off,int len); 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。
在Reader中包含也有如下3个方法。
一,int read(); 从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)。
二,int read(char[] b)从输入流中最多读取b.length个字符的数据,并将其存储在字节数组b中,返回实际读取的字符数。
三,int read(char[] b,int off,int len); 从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。
对比InputStream和Reader所提供的方法,就不难发现这两个基类的功能基本是一样的。