网络层:
IP地址:InetAddress
• 网络中设备的标识
传输协议
• 通讯的规则
• 常见协议: TCP, UDP
传输层:
端口号
• 用于标识进程的逻辑地址,不同进程的标识
• 有效端口:0-65535,其中0~1024系统使用或保留端口。
其实面向网络编程就是面向Socket编程,有关的Socket详细介绍链接:Socket的一些说明
DatagramSocket详解
Socket/ServiceSocket详解
文件传输演示
期初看官方文档挺晕的,构造函数挺多的,不知道那些事用于发送和接收的,但有个规律就是,凡是发送的构造方法都接收InetAddress对象。
发送包构造方法 | 说明 |
---|---|
DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。 |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) | 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 |
DatagramPacket(byte[] buf, int length, SocketAddress address) | 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。 |
接收包构造方法 | 说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造 DatagramPacket,用来接收长度为 length 的数据包。 |
DatagramPacket(byte[] buf, int offset, int length) | 构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。 |
在介绍DatagramSocket时还需要了解DatagramPacket类说明
发送
和接收
数据报包的Socket。class UdpSend {
public static void main(String[] args) throws Exception {
//1,创建udp服务。通过DatagramSocket对象。
DatagramSocket ds = new DatagramSocket(8888); //发送端的源端口号
//2,确定数据,并封装成数据包。
byte[] buf = "This is UDP-encapsulated data".getBytes();
//使用的构造函数是:
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket dp =new DatagramPacket(buf, buf.length,
InetAddress.getByName("192.168.0.106"), 10000);//10000为目的端口号
//3,通过socket服务,将已有的数据包发送出去。通过send方法。
ds.send(dp);
//4,关闭资源。
ds.close();
}
}
class UdpRece {
public static void main(String[] args) throws Exception {
//1,创建udp socket,建立端点。
DatagramSocket ds = new DatagramSocket(10000);//接收端口号
while (true) {
//2,定义数据包。用于存储数据。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//3,通过服务的receive方法将收到数据存入数据包中。
ds.receive(dp);//阻塞式方法。
//4,通过数据包的方法获取其中的数据。
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
int port = dp.getPort();
System.out.println(ip + "::" + data + "::" + port);
}
//5,关闭资源
ds.close();
}
}
import java.net.DatagramSocket;
import java.net.SocketException;
public class Main {
public static void main(String[] args) {
try {
Service service = new Service(new DatagramSocket(10000));
Client client = new Client(new DatagramSocket(20000));
new Thread(service).start();
new Thread(client).start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Service implements Runnable {
DatagramSocket ds = null;
public Service(DatagramSocket ds) {
System.out.println("接收端开启....");
this.ds = ds;
}
@Override
public void run() {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
try {
while (true) {
ds.receive(dp);
String hostName = dp.getAddress().getHostName();
int port = dp.getPort();
int length = dp.getLength();
System.out.println("hostName:" + hostName + "port:" + port + '\n' +
new String(buf, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
3.数据发送端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Client implements Runnable {
DatagramSocket ds = null;
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
byte[] buf = new byte[1024];
public Client(DatagramSocket ds) {
System.out.println("发送端开启....");
this.ds = ds;
}
@Override
public void run() {
try {
String str = null;
while ((str = br.readLine()) != null) {
buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf, 0,buf.length,
InetAddress.getByName("192.168.0.106"),10000);
ds.send(dp);
if("886".equals(str)){
System.out.println("接收端关闭");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
ds.close();
}
}
}
java中TCP与UDP不同,UDP的DatagramSocket类既可以接收也可以发送,而TCP传输分为客户端Socket类和服务端ServerSocket类。
不了解UDP的朋友请参考这篇文章
客户端测试程序
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("192.168.0.106"),10000);
OutputStream outputStream = socket.getOutputStream();
//发送数据给服务端,后面加\r\n是给服务端的readLine方法一个结束标记,否则会出现程序阻塞
outputStream.write("测试数据\r\n".getBytes());
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String s = bufferedReader.readLine();
System.out.println(s);
socket.close();
}
服务端测试程序
public static void main(String[] args) throws IOException {
//服务端在监听一个号为10000的端口
ServerSocket serverSocket = new ServerSocket(10000);
//阻塞式方法,用于接收客户端发来的Socket对象
Socket accept = serverSocket.accept();
//服务端本身没有流,流是从客户端获取的(拿着客户端的Socket获取流)
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(accept.getInputStream()));
String s = bufferedReader.readLine();
String ip = accept.getInetAddress().getHostName();
System.out.println("ip:"+ip+s);
//给客户端一个回执信息
OutputStream outputStream = accept.getOutputStream();
outputStream.write("你好客户端,我收到了\r\n".getBytes());
serverSocket.close();
}
构造方法名称 | 说明 |
---|---|
Socket() | 通过系统默认类型的 SocketImpl 创建未连接套接字,后期可通过connect方法进行指定连接。 |
一定要留意流中的阻塞式方法,如果使用不当就会导致程序挂起,如缓冲区没有刷新或给对方发送的数据没有结束标记等。因此java提供了两个方法用于告诉对方,本方已经结束的,你可以关闭进行下一步操作了。
方法名称 | 说明 |
---|---|
shutdownInput() | 此套接字的输入流置于“流的末尾”。 |
shutdownOutput() | 禁用此套接字的输出流。 |
(自己随便写的,有些地方需要优化)
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) {
//源文件
File Pic = new File("F:\\u盘\\xb_4K.mp4");
Socket socket = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
BufferedInputStream sbis = null;
try {
bis = new BufferedInputStream(new FileInputStream(Pic));
//
socket = new Socket(InetAddress.getByName("192.168.0.106"), 60000);
bos = new BufferedOutputStream(socket.getOutputStream());
//传递文件名称
//注意此处bug,传递过去的是一个绝对路径,因此需要切割
String name = Pic.getName()+"\r\n";
if (name.contains("\\")) {
int i = name.lastIndexOf("\\");
name = name.substring(i);
}
bos.write(name.getBytes());
bos.flush();
//传递数据
int len = 0;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.flush();
socket.shutdownOutput();
//接受服务端返回
sbis = new BufferedInputStream(socket.getInputStream());
byte[] buff = new byte[1024];
int read = sbis.read(buff);
String s = new String(buff, 0, read);
System.out.println(s);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class PictureTheServer {
public static void main(String[] args) {
String filename = null;
ServerSocket serverSocket = null;
File putFile = null;
try {
//1.创建Socket服务设置监听端口,获取服务端Socket对象
serverSocket = new ServerSocket(60000);
Socket accept = serverSocket.accept();
//2.获取读取流
InputStream is = accept.getInputStream();
//3.获取读取流的第一个字段(第一个字段为文件名称)
BufferedReader read = new BufferedReader(new InputStreamReader(is));
filename = read.readLine();
putFile = new File("E:\\IO测试文件夹\\" + filename);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(putFile));
BufferedInputStream bis = new BufferedInputStream(is);
int len = 0;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.flush();
accept.shutdownInput();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(accept.getOutputStream()),true);
pw.println("上传成功");
accept.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程改进版
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
class Demo{
public static void main(String[] args) throws IOException {
ServerSocket serverSocket;
serverSocket = new ServerSocket(60000);
while (true) {
//来一个任务就开启一个线程。
new Thread(new ThreadServerPic(serverSocket.accept())).start();
}
}
}
public class ThreadServerPic implements Runnable {
private String fileName = null;
private File putFile = null;
private Socket accept;
public ThreadServerPic(Socket accept) {
this.accept = accept;
}
@Override
public void run() {
try {
InputStream is = accept.getInputStream();
//3.获取读取流的第一个字段(第一个字段为文件名称)
BufferedReader read = new BufferedReader(new InputStreamReader(is));
fileName = read.readLine();
putFile = new File("E:\\IO测试文件夹\\" + fileName);
if (putFile.exists()) {
putFile = new File(putFile.getName() + "(1)");
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(putFile));
BufferedInputStream bis = new BufferedInputStream(is);
int len = 0;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.flush();
accept.shutdownInput();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(accept.getOutputStream()), true);
pw.println("上传成功");
accept.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
统一资源定位符是指向互联网“资源”的指针。如:https://me.csdn.net/qq_39711439 ,通过这个标识符可在网络中定位资源。此类处于应用层,底层封装了Socket,因此在处理浏览器等方面的信息,应使用本类。
而Java中的URL类是对如同【https://blog.csdn.net/qq_39711439/article/details/100859357】这种网络地址的抽象。这个类中有一些方法可以获取这个字符串中的信息,如协议,域名,端口等。下面会介绍一些常用方法。
URI 是统一资源标识符,而 URL 是统一资源定位符。URL 和 URN 都是 URI 的子集
1. 获取url串中的信息
返回值类型 | 方法名称 | 说明 |
---|---|---|
String | getAuthority() | 获取此 URL 的授权部分。 |
Object | getContent() | 获取此 URL 的内容。 |
Object | getContent(Class[] classes) | 获取此 URL 的内容。 |
int | getDefaultPort() | 获取与此 URL 关联协议的默认端口号。 |
String | getFile() | 获取此 URL 的文件名。 |
String | getHost() | 获取此 URL 的主机名(如果适用)。 |
String | getPath() | 获取此 URL 的路径部分。 |
int | getPort() | 获取此 URL 的端口号。 |
String | getProtocol() | 获取此 URL 的协议名称。 |
String | getQuery() | 获取此 URL 的查询部分。 |
String | getRef() | 获取此 URL 的锚点(也称为“引用”)。 |
2. 获取url目标的资源
返回值类型 | 方法名称 | 说明 |
---|---|---|
URLConnection | openConnection() | 返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。 获取url对象的Url连接器对象。将连接封装成了对象: java中内置的可以解析的具体协议的对象+socket。 |
InputStream | openStream() | 打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。也就是获取url资源的Socket的读取流,并舍弃应答头,只获取应答体。其底层是 openConnection().getInputStream()方法。 |
URL类有一个特别的方法就是openConnection,返回一个URLConnection对象,此对象底层封装的是Socket流通,过URLConnection对象可以使用socket中的方法来获取网站的内容 ,URLConnection把浏览器的请求头和服务器返回的响应头都封装起来了,因此我们不用再向使用Socket一样手动写入浏览器请求头了。此URLConnection在应用层,而Socket在传输层。
// An highlighted block
io流是java中很重要的一个部分,不用扫描器进行键盘录入
InputStream in = System.in; //其中System是系统类,成员in默认为键盘录入
try {
int len = 0;
StringBuilder sb = new StringBuilder(); //临时缓冲区
while((len = in.read())!=-1){ //read为阻塞方法如果没有中断标记无法停止
char ch = (char)len;
//下面语段代码为BufferReader 的readLine中的实现方法
if(ch == '\r'){
continue;
}
if(ch == '\n'){
System.out.println(sb);
sb.delete(0, sb.length());
}else{
sb.append(ch);
}
if((char)len=='0') {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
/*有一种改进方法就是对InputStream 的对象进行装饰,其中会用到转换流进行字符流转字节流:
InputStreamReader //字节流转字符流的桥梁
改进如下:
*/
public static void main(String[] args) {
InputStream in = System.in;
try {
String str = null;
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in)); //不断进行装饰的过程
while ((str = br.readLine())!=null){
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
函数名称 | 详细说明 |
---|---|
canExecute() | 是否是执行文件 |
canRead | 是否允许读操作 |
canWrite() | 是否允许读操作 |
exists() | 目录或文件是否存在 |
isFile() | 是否为文件 |
isDirectory() | 是否为目录 |
isHidden() | 是否为隐藏文件 |
以上方法返回值都为boolean类型
返回值类型 | 函数名称 | 详细说明 |
---|---|---|
String | getName() | 获取文件或目录名称 |
File | getAbsoluteFile() | 获取绝对路径的File对象形式,如果没有则为NULL |
String | getAbsolutePath() | 返回绝对路径名字符串,如果没有则为NULL |
String | getParent() | 返回父目录,如没有则返回 null。(构造器传入的父目录,不一定是绝对路径) |
File | getParentFile() | 返回父目录的文件对象,如没有则返回 null |
String | getPath() | 获取相对路径 |
long | lastModified() | 返回文件最后一次被修改的时间(毫秒值,需要Date转换) |
long | length() | 返回文件的长度(字节数) |
static File[] | listRoots() | Windows下返回列出所有盘符 |
long | getFreeSpace() | 返回指定盘符的剩余容量字节数 |
返回值类型 | 函数名称 | 详细说明 |
---|---|---|
String[] | list() | 列出当前目录下所有的文件夹和文件(包含隐藏文件)的列表,(类似于ls命令),如果调用list方法的file对象不是目录或是一个无法访问的系统目录则返回一个null,如果有目录,但目录下子目录为空,则返回一个长度为0的String数组 |
File[] | listFiles() | 返回指定目录中的文件和目录对象 |
String[] | list(FilenameFilter filter) | 返回目录中满足过滤器的文件和目录的名称。 (根据文件名称过滤) |
File[] | listFiles(FilenameFilter filter) | 返回目录中满足指定过滤器的文件和目录的对象 |
File[] | listFiles(FileFilter filter) | 返回满足指定过滤器的目录中的文件和目录。 |
注释:
对interface FilenameFilter
接口的解释:
这里的文件过滤是用到了策略设计模式,详细用法我也不造车轮子了
引用链接:filenamefilter详细说明
这里我们会发现listFiles有重载形式:
filefilter和filenamefilter的区别
返回值类型 | 函数名称 | 详细说明 |
---|---|---|
boolean | createNewFile() | 创建新文件,如果文件已存在不覆盖,返回false |
boolean | delete() | 删除文件或目录,不可删除有内容的目录 |
boolean | renameTo(File dest) | 重命名文件,也可以剪切(与linux mv命令类似)传入的是想要更名的文件对象 |
boolean | mkdir() | 创建目录 |
boolean | mkdirs() | 递归的创建目录 |
未完待续…
@Test
public void test3() {
File file = new File("E:\\");
getDir(file, 0);
}
private void getDir(File file, int count) {
File[] list = file.listFiles();
//count 循环是来控制缩进
for(int i = 0;i<count;i++){
System.out.print("|--");
}
System.out.println("dir:"+file.getName());
for (File file1 : list) {
if (file1.isDirectory()) {
getDir(file1, count + 1);
} else {
for(int i = 0;i<count;i++){
System.out.print("|--");
}
System.out.println("file::"+file1.getName());
}
}
}
public void test2() {
File file = new File("E:\\ccc");
getDir(file);
}
private void getDir(File file) {
File[] list = file.listFiles();
for (File file1 : list) {
if (file1.isDirectory()) {
getDir(file1);
} else {
//递归删除文件
System.out.println("file:"+file1.delete());
}
}
//再删除目录
System.out.println("dir:"+file.delete());
}
配置文件一般与记录用户对程序设置的永久化存储,每当你对手机电脑等系统或软件进行字体、主题等其他设置的选择都会保存在配置文件当中,每当程序启动加载进内存,程序一般都会先读取本地配置文件,加载用户之前的自定义配置。
配置文件不仅用于持久化存储用户的一些配置,在Java中配合反射机制可以增强程序扩展性,这一点后面文章中在提及。
官方文档说明:Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
其实就是Properties是一个可以写到硬盘上的Map集合,默认的键和值都是String类型,适合操纵键值对方式存储的配置文件。
java.util class Properties 继承体系:
java.lang.Object
|— java.util.Dictionary
|—java.util.Hashtable
返回值 | 方法名称 | 说明 | |
---|---|---|---|
Object | setProperty(String key, String value) | 调用 Hashtable 的方法 put,将键值写入到HashTable集合中 | |
void | list(PrintWriter out) | 将Properties对象的map集合用打印流输出(调试用) | |
String | getProperty(String key) | 获取指key的value | |
Set |
stringPropertyNames() | 获取配置文件中的所有Key的Set集合 |
返回值 | 方法名称 | 说明 | |
---|---|---|---|
void | store(Writer writer, String comments) | 将内存中的Map集合数据通过字符流写出 | |
void | store(OutputStream out, String comments) | 将内存中的Map集合数据通过字节流写出 |
public void test1() throws IOException {
Properties prop = new Properties();
prop.setProperty("Back color", "red");
prop.setProperty("found size", "25");
FileWriter fw = new FileWriter("E:\\b.txt");
prop.list(System.out);//先显示到控制台上
prop.store(fw,"Prompt information"); //再写入到b.txt文件中
}
返回值 | 方法名称 | 说明 | |
---|---|---|---|
void | load(InputStream inStream) | 从输入流中读取属性列表(键和元素对) | |
void | load(Reader reader) | 按面向行的格式从输入字符流中读取属性列表(键和元素对) |
/* 对已有的配置文件中的信息进行修改。
* 读取这个文件。
* 并将这个文件中的键值数据存储到集合中。
* 在通过集合对数据进行修改。
* 在通过流将修改后的数据存储到文件中。
*/
@Test
public void test() throws IOException{
//读取这个文件。
File file = new File("info.txt");
FileReader fr = new FileReader(file);
//创建集合存储配置信息。
Properties prop = new Properties();
prop.load(fr);
prop.setProperty("wangwu", "16");
// 一定要注意写入文件对象的位置, 如果在load方法之前创建FileWriter对象会覆盖原有文件的内容
FileWriter fw = new FileWriter(file);
prop.store(fw,"");
fw.close();
fr.close();
}
输入流和输出流相对于内存设备而言.
将外设中的数据读取到内存中:输入
将内存的数写入到外设中:输出。
字符流的由来:
其实就是:字节流读取文字字节数据后,不直接操作而是先查指定的编码表。获取对应的文字。
在对这个文字进行操作。简单说:字节流+编码表
字节流的两个顶层父类:
1,InputStream 2,OutputStream.
字符流的两个顶层父类:
1,Reader 2,Writer
这些体系的子类都以父类名作为后缀。
而且子类名的前缀就是该对象的功能。
字符流缓冲区附加功能:
BufferedWriter
:newLine();
BufferedReader:
: readLine();
对一组对象的功能进行增强时,就可以使用该模式进行问题的解决。 装饰和继承都能实现一样的特点:进行功能的扩展增强。
有什么区别呢?
首先有一个继承体系。
Writer
|–TextWriter:用于操作文本
|–MediaWriter:用于操作媒体。
想要对操作的动作进行效率的提高。
按照面向对象,可以通过继承对具体的进行功能的扩展。
效率提高需要加入缓冲技术。
Writer
|–TextWriter:用于操作文本
|–BufferTextWriter:加入了缓冲技术的操作文本的对象。
|–MediaWriter:用于操作媒体。
|–BufferMediaWriter:
到这里就哦了。但是这样做好像并不理想。
如果这个体系进行功能扩展,有多了流对象。
那么这个流要提高效率,是不是也要产生子类呢?是。这时就会发现只为提高功能,进行的继承,
导致继承体系越来越臃肿。不够灵活。
重新思考这个问题?
既然加入的都是同一种技术–缓冲。
前一种是让缓冲和具体的对象相结合。
可不可以将缓冲进行单独的封装,哪个对象需要缓冲就将哪个对象和缓冲关联。
class Buffer{
Buffer(TextWriter w)
{}
Buffer(MediaWirter w)
{
}
}
class BufferWriter extends Writer{
BufferWriter(Writer w)
{
}
}
Writer
|–TextWriter:用于操作文本
|–MediaWriter:用于操作媒体。
|–BufferWriter:用于提高效率。
装饰比继承灵活。
特点:装饰类和被装饰类都必须所属同一个接口或者父类。
字节流:
InputStream
OutputStream
字节流:
FileInputStream
FileOutputStream
BufferedInputStream
BufferedOutputStream
字符流:
Writer Reader
FileReader
FileWriter
BufferedReader
BufferedWriter
转换流:
InputStreamReader :字节到字符的桥梁。解码。
OutputStreamWriter:字符到字节的桥梁。编码。
之所以要弄清楚这个规律,是因为流对象太多,开发时不知道用哪个对象合适。
想要知道开发时用到哪些对象。只要通过四个明确即可。
1,明确源和目的(汇)
源:InputStream Reader
目的:OutputStream Writer
2,明确数据是否是纯文本数据。
源:是纯文本:Reader
否:InputStream
目的:是纯文本 Writer
否:OutputStream
到这里,就可以明确需求中具体要使用哪个体系。
3,明确具体的设备。
源设备:
硬盘:File
键盘:System.in
内存:数组
网络:Socket流
目的设备:
硬盘:File
控制台:System.out
内存:数组
网络:Socket流
4,是否需要其他额外功能。
1,是否需要高效(缓冲区);
是,就加上buffer.
2,转换。
需求1:复制一个文本文件。
1,明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2,是否是纯文本?
是!
源:Reader
目的:Writer
3,明确具体设备。
源:
硬盘:File
目的:
硬盘:File
FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter(“b.txt”);
4,需要额外功能吗?
需要,需要高效。
BufferedReader bufr = new BufferedReader(new FileReader(“a.txt”));
BufferedWriter bufw = new BufferedWriter(new FileWriter(“b.txt”));
================================================
需求2:读取键盘录入信息,并写入到一个文件中。
1,明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2,是否是纯文本呢?
是,
源:Reader
目的:Writer
3,明确设备
源:
键盘。System.in
目的:
硬盘。File
InputStream in = System.in;
FileWriter fw = new FileWriter("b.txt");
将读取的字节数据转成字符串。再由字符流操作。这样做可以完成,但是麻烦。
4,需要额外功能吗?
需要。转换。 将字节流转成字符流。因为名确的源是Reader,这样操作文本数据做便捷。
所以要将已有的字节流转成字符流。使用字节-->字符 。InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
FileWriter fw = new FileWriter("b.txt");
还需要功能吗?
需要:想高效。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
需求3:将一个文本文件数据显示在控制台上。
1,明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2,是否是纯文本呢?
是,
源:Reader
目的:Writer
3,明确具体设备
源:
硬盘:File
目的:
控制台:System.out
FileReader fr = new FileReader(“a.txt”);
OutputStream out = System.out;//PrintStream
4,需要额外功能吗?
需要,转换。
FileReader fr= new FileReader(“a.txt”);
OutputStreamWriter osw = new OutputStreamWriter(System.out);
需要,高效。
BufferedReader bufr = new BufferedReader(new FileReader(“a.txt”));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
需求4:读取键盘录入数据,显示在控制台上。
1,明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2,是否是纯文本呢?
是,
源:Reader
目的:Writer
3,明确设备。
源:
键盘:System.in
目的:
控制台:System.out
InputStream in = System.in;
OutputStream out = System.out;
4,明确额外功能?
需要转换,因为都是字节流,但是操作的却是文本数据。
所以使用字符流操作起来更为便捷。
InputStreamReader isr = new InputStreamReader(System.in);
OutputStreamWriter osw = new OutputStreamWriter(System.out);
为了将其高效。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
5,将一个中文字符串数据按照指定的编码表写入到一个文本文件中.
1,目的。OutputStream,Writer
2,是纯文本,Writer。
3,设备:硬盘File
FileWriter fw = new FileWriter("a.txt");
fw.write("你好");
注意:既然需求中已经明确了指定编码表的动作。
那就不可以使用FileWriter,因为FileWriter内部是使用默认的本地码表。
只能使用其父类。OutputStreamWriter.
OutputStreamWriter接收一个字节输出流对象,既然是操作文件,那么该对象应该是FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"),charsetName);
需要高效吗?
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a.txt"),charsetName));
什么时候使用转换流呢?
1,源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁。
提高对文本操作的便捷。
2,一旦操作文本涉及到具体的指定编码表时,必须使用转换流 。
它不抛IOException.
构造函数接收三种类型的值:
字符串路径。
File对象。
字节输出流。
PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类
返回值 | 方法名 | 说明 |
---|---|---|
void | write(int b) | 将指定的字节转换成默认字符集的字符写出, 就是把int转换成char再写出。(int 类型是4个字节,而Write方法只写入int的最低八位,前面的3个字节完全忽略) |
void | println(int x) | 把所有传入此方法的内容转换成字符串原样打印出去 (此方法参数有多种重载形式,这里只拿int来演示) |
自动刷新功能,PrintWriter、PrintStream都支持,下面用PrintWriter的其中一个构造函数来解释说明。
构造方法名 | 说明 |
---|---|
PrintWriter(OutputStream / Writer out, boolean autoFlush) | 通过现有的 OutputStream 创建新的 PrintWriter。autoFlush为true是自动刷新(不用手动调用flash),只有在调用 println、printf 或 format 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。如果启用了自动刷新,则这些方法使用平台自有的行分隔符概念,而不是换行符。 |
与打印流不同的是,序列流只负责源的操纵,SequenceInputStream继承于InputStream,其作用是为多个源流的合并为一个大流。
构造方法 | 说明 |
---|---|
SequenceInputStream(Enumeration extends InputStream> e) | 把集合Vector中存储的流合并成一个大流, |
public static void main(String[] args) throws IOException {
Vector<InputStream> v = new Vector<>();
v.add(new FileInputStream("E:\\IO测试文件夹\\a.txt"));
v.add(new FileInputStream("E:\\IO测试文件夹\\b.txt"));
BufferedInputStream bis = new BufferedInputStream(
new SequenceInputStream(v.elements()));
PrintStream ps = new PrintStream("E:\\IO测试文件夹\\E.txt");
int len = 0;
while ((len = bis.read())!=-1){
ps.write(len);
}
ps.close();
bis.close();
}
但是Vector效率不如ArrayList高效,但是SequenceInputStream(Enumeration extends InputStream> e) 接收的是枚举类型,ArrayList只有迭代器。因此我们需要集合中的工具类Collections中的一个函数:
返回值 | 函数名称 | 说明 |
---|---|---|
static |
enumeration(Collection |
传入一个Collection集合,返回枚举 |
public static void main(String[] args) throws IOException {
List<FileInputStream> list = new ArrayList<>();
list.add(new FileInputStream("E:\\IO测试文件夹\\a.txt"));
list.add(new FileInputStream("E:\\IO测试文件夹\\E.txt"));
//iterator 转 enumeration
Enumeration<FileInputStream> enumeration = Collections.enumeration(list);
BufferedInputStream bis = new BufferedInputStream(
new SequenceInputStream(enumeration));
FileOutputStream ps = new FileOutputStream("E:\\IO测试文件夹\\b.txt");
int len = 0;
while ((len = bis.read())!=-1){
ps.write(len);
}
ps.close();
bis.close();
}
import org.junit.Test;
import java.io.*;
import java.util.*;
public class 文件切割 {
private int FileMegabit = 1;
@Test
public void test1() throws IOException {
String srcDir = "F:\\JavaSourceCode\\TestModule\\files\\";
String srcFileName = "file.rar";
String outDri = "F:\\JavaSourceCode\\TestModule\\files\\切割目录\\";
//cutfile(srcDir, srcFileName, outDri);
//合并
merge(outDri, outDri);
}
private void merge(String mergePath, String conDir) throws IOException {
Properties p = new Properties();
p.load(new FileReader(conDir + "partConf.ini"));
int count = Integer.parseInt(p.getProperty("count"));
String fileName = p.getProperty("fileName");
File srcDirFile = new File(mergePath);
if (!srcDirFile.isDirectory()) {
throw new RuntimeException("请选择包含碎片文件的目录");
}
File[] files = srcDirFile.listFiles((fil) -> fil.getName().endsWith(".part"));
if (files.length != count ) {
throw new RuntimeException("碎片文件不完整,无法合并");
}
ArrayList<FileInputStream> sofList = new ArrayList<>();
for (int i = 0; i < count; i++) {
sofList.add(new FileInputStream(new File(mergePath, fileName + i + ".part")));
}
Enumeration<FileInputStream> enumeration = Collections.enumeration(sofList);
SequenceInputStream sis = new SequenceInputStream(enumeration);
FileOutputStream fos = new FileOutputStream(mergePath + fileName);
byte[] flush = new byte[1024 * 1024];
int len = -1;
while ((len = sis.read(flush)) != -1) {
fos.write(flush, 0, len);
}
fos.close();
}
public void cutfile(String srcDir, String srcFileName, String outDri) throws IOException {
File srcFile = new File(srcDir, srcFileName);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
byte[] b = new byte[1024 * 1024 * FileMegabit];
int len = 0;
int fileCuttingCounter = 0;
while ((len = bis.read(b)) != -1) {
FileOutputStream fos = new FileOutputStream(outDri + srcFileName + fileCuttingCounter + ".part");
fos.write(b, 0, len);
fileCuttingCounter++;
fos.close();
}
bis.close();
cutInfoStorage(fileCuttingCounter, srcFileName, outDri);
}
private void cutInfoStorage(int count, String fileName, String saveDir) throws IOException {
Properties p = new Properties();
p.setProperty("count", String.valueOf(count));
p.setProperty("fileName", fileName);
p.store(new FileWriter(saveDir + "partConf.ini"),
"cutting file information Not delete");
}
}
对象序列化就是把堆内存中的对象持久化处理(保存到硬盘上)。
对象序列化前提:对象想要被序列化,就必须实现Serializable接口,此接口是一个标记接口,没有实现方法。
vransient关键字介绍:可以让阻止非静态数据(堆内存)的数据序列化。
Serializable接口介绍:实现此接口的类都有一个隐示的serialVersionUID (这个UID也可以手动定义)其定义格式为:ANY-ACCESS-MODIFIER static final long serialVersionUID
。这个UID用来判断对象和类是否处于同一个版本,如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException
。
官方文档上说,强烈建议自定义UID,因为编译器版本的不同,可能对同一个类进行序列化后导致ID不一致。
其他提示:静态成员不能被序列化
/**
* 自定义一个对象,用于测试对象序列化
*/
class Person implements Serializable {
public static final long serialVersionUID = 42L; //自定义序列化标号ID
//如果不写,系统会根据类中的内容计算出一个序列化ID
//加载时根据ID判断硬盘中的这个对象是否属于这个类。
private String name;
transient int age; //transition 关键字:阻止堆内存中的数据序列化,数据不能写入硬盘,
//只能在堆内存加载
static String country = "cn"; //静态不能被序列化
Person(String name, int age, String country) {
this.name = name;
this.age = age;
Person.country = country;
}
@Override
public String toString() {
return name + ":" + age + ":" + country;
}
}
对象序列化(存储)演示
//把一个对象写入到硬盘。
public static void writeObj() throws IOException {
//创建写对象流,和保存位置
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.object"));
//把一个对象写入到硬盘中,如果有多次写入,则顺序存储
oos.writeObject(new Person("lisi0", 399, "kr"));
//oos.writeObject(new Person("lisi0", 399, "kr"));
oos.close();
}
对象反序列化(读取)演示
读取的readObject方法会抛出一个ClassNotFoundException (类找不到异常),因为要读取对象文件有一个前提,你必须有这个对象的.class文件。如果你有这个对象文件而没有类文件,这个对象就无法在内存中创建。
readObject方法是按照顺序进行读取的,也就是说,调用一次readObject就读一次下一个对象。
还有一点就是要注意,readObject()方法好像没有结束标记,读到末尾会抛出EOFException,因此在catch中结束就好了,有知道的小伙伴可以留言评论一下。
//读取硬盘的对象
public static void readObj() throws Exception {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("files\\obj.object"));
Person person1 = (Person) ois.readObject();
System.out.println(person1.toString());
Person person2 = (Person) ois.readObject();
System.out.println(person2.toString());
}
RandomAccessFile不是io体系中的四大类的子类。它直接继承与Object类。用此类存储的文件字节数必须有规律,因为他使用的是下标进行读取的,有数组的特性。
其特点为:
1,该对象即能读,又能写。
2,该对象内部维护了一个byte数组,并通过指针可以操作数组中的元素,
3,可以通过getFilePointer方法获取指针的位置,和通过seek方法设置指针的位置。
4,其实该对象就是将字节输入流和输出流进行了封装。
5,该对象的源或者目的只能是文件。通过构造函数就可以看出。
6,如果文件不存在,则创建,如果文件存在,则不覆盖文件。(注意:这里的不覆盖指的是文件的覆盖,与下面所说的内容覆盖不同)
值 | 含意 |
---|---|
“r” | 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 |
“rw” | 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
“rws” | 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
“rwd” | 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。 |
我们可以通过seek(long pos) 方法 设置指针的偏移位置,在通过其他的read方法去取数据,从而向数组一样读取文件的任意位置。
由于是通过指针进行操作的,因此如果用新的RandomAccessFile对已有的文件进行writer操作时不会记录原有指针的位置,也就是说指针从0开始写入,这样会覆盖原字节数组下标的数据。
由于他有随机访问的特性,因此可以用于多线程下载。
管道流应结合多线程使用。因为官方文档上说对这两个对象使用单个线程,可能会造成该线程死锁。
import java.io.*;
/**
* 管道流Piped:
* 说明:将输入流和输出流连接起来,(之前的方法是在内存中定义数组临时存储两个流之间的变量)
* 注意:使用管道流,读和写不能在同一个线程执行,会出现死锁。
*
*/
//read线程类
class Read implements Runnable {
private PipedInputStream in;
Read(PipedInputStream in) {
this.in = in;
}
public void run() {
try {
byte[] buf = new byte[1024];
System.out.println("读取前。。没有数据阻塞");
int len = in.read(buf); //假如读线程先获得cpu执行权,如果没有数据,
// 线程就会进入挂起(阻塞/暂停)状态,等待写入完成
System.out.println("读到数据。。阻塞结束");
String s = new String(buf, 0, len);
System.out.println(s);
in.close();
} catch (IOException e) {
throw new RuntimeException("管道读取流失败");
}
}
}
//write线程类
class Write implements Runnable {
private PipedOutputStream out;
Write(PipedOutputStream out) {
this.out = out;
}
public void run() {
try {
System.out.println("开始写入数据,等待3秒后。");
Thread.sleep(3000);
out.write("piped lai la".getBytes());
out.close();
} catch (Exception e) {
throw new RuntimeException("管道输出流失败");
}
}
}
public class PipedStreamTest {
public static void main(String[] args) throws IOException {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out); //把连个管道进行连接
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
查看官方文档的方法摘要就会发现。很多方法在管道流和RandomAccessFile等其他流中见到过,但是注意,管道流是操作管道的,RandomAccessFile是用于随机访问,虽然功能类似,但操作的对象和不一样,而数据流是专门保存原始数据的。
如想要向文件写入一个int类型的数据,普通流的write只写入最低8位,而这个类中的方法提供了原样写入基本数据类型的功能。
此流用于操作内存。由于此对象没有调用系统底层资源,因此此流不用关闭资源,且不会产生任何 IOException。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayStreamDemo {
public static void main(String[] args) {
ByteArrayInputStream bis = new ByteArrayInputStream("abcedf".getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch = 0;
while((ch=bis.read())!=-1){
bos.write(ch);
}
System.out.println(bos.toString());
}
}
其他详情请参考官方文档
public void getClassTest() {
//方式1
Class<TestPerson> aClass1 = TestPerson.class;
//方式2
TestPerson testPerson = new TestPerson();
Class<? extends TestPerson> aClass = testPerson.getClass();
//方式3
Class<?> aClass2 = null;
try {
aClass2 = Class.forName("getClass.TestPerson");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//如下的hashcode相等1018547642 说明
//一个类在内存中只有一个Class对象
//一个类被加载后,类的整体个结构都会被封装在Class对象中。
System.out.println(aClass.hashCode());
System.out.println(aClass1.hashCode());
if (aClass2 != null) {
System.out.println(aClass2.hashCode());
}
//方式4:基本内置类型的包装类都有一个Type属性
Class<Integer> aClass3 = Integer.TYPE;
//方式5:根据一个类的Class对象,获得其父类对象
Class<? super TestPerson> aClass4 = aClass1.getSuperclass();
}
public void test3() {
Class<int[]> aClass = int[].class;
Class<Override> overrideClass = Override.class;
Class<Integer> integerClass = int.class;
//注意void
Class<Void> voidClass = void.class;
Class<Class> classClass = Class.class;
Class<String[][]> aClass1 = String[][].class;
Class<ElementType> elementTypeClass = ElementType.class;
Class<Integer> integerClass1 = Integer.class;
Class<Comparable> comparableClass = Comparable.class;
int[] int10 = new int[10];
int[] int100 = new int[100];
//不同长度,同一类型的数组Class对象也是唯一的
System.out.println(int10.getClass().hashCode());
System.out.println(int100.getClass().hashCode());
//同一类型不同维度,hashCode不行的,Class对象不是同一
int[][] ints = new int[10][10];
System.out.println(ints.getClass().hashCode());
}
类对象的创建过程,加载-》链接-》初始化
1).加载到内存,会产生一个Class对象
2).执行链接 结束后静态变量会初始化为0
3).初始化,如果有父类,则执行父类会从1)开始执行;jvm会执行clinit方法把静态修饰的内容提取出来进行初始化赋值
类的初始化注意点:
通过子类类名点父类静态不会初始化子类,只会初始化父类
对象数组的定义不会初始化对象
通过类名点public static final修饰的内容(常量池的内容)不会触发这个类和其父类的初始化
//获取类加载器可以加载的路径,只有class文件在这些路径下,才会被jvm加载
@Test
public void getClassPath() {
String paths = System.getProperty("java.class.path").toString();
String[] split = paths.split(";");
for (String s : split) {
System.out.println(s);
}
}
Class c1 = Class.forName("getClass.TestPerson");
String name = c1.getName();//获取全路径
String simpleName = c1.getSimpleName();//只获取类名称
2.获取成员属性
//获取所有属性 不加Declared,不能获取私有修饰的属性
Field age = c1.getField("age");
//获取指定属性
Field privateName = c1.getDeclaredField("name");
Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
//结果
//private java.lang.String getClass.TestPerson.name
//public int getClass.TestPerson.age
//此方法会获取本类和父类所有public方法
Method[] methods = c1.getMethods();
for (Method method : methods) {
System.out.println(method);
}
//只获取本类所有方法包括private
methods = c1.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
//获取指定的方法 参数格式 (方法名称,参数类型的Class对象)如果空参就省略。
Method getName = c1.getMethod("getName");
System.out.println(getName);
//传参数的Class对象是用来区分重载方法
Method setName = c1.getMethod("setName", String.class);
System.out.println(setName);
4.获取类的构造器
//获本类全部取构造器
Constructor[] constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
//获取指定构造器
Constructor constructor = c1.getConstructor();
System.out.println(constructor);
//根据参数获取构造器
Constructor constructor1 = c1.getConstructor(String.class, int.class);
System.out.println(constructor1);
Class c1 = Class.forName("getClass.TestPerson");
// newInstance已经过时,他调用无参数构造器,如果类中没声明无参构造,或权限不足,那他将抛出异常
TestPerson person = (TestPerson) c1.newInstance();
//推荐 他更显性化显示具体用哪个构造器来创建的对象
person = (TestPerson)c1.getConstructor().newInstance();
person.setAge(18);
int age = person.getAge();
//获取有参构造,并创建对象
person = (TestPerson) c1.getConstructor(String.class, int.class).newInstance("aaa", 22);
System.out.println(person);//TestPersion{name='aaa', age=22}
person = (TestPerson)c1.getConstructor().newInstance();
Method setName = c1.getMethod("setName", String.class);
//注意:虽然获取到了方法,方法的调用需要传入一个对象,绑定这个方法,然后用传入的对象调用方法
Object aaa = setName.invoke(person, "bbb");
System.out.println(person.getName());
// 注意:私有修饰需要使用Declared
Field name = c1.getDeclaredField("name");
//私有属性需要设置访问权限为true
name.setAccessible(true);//这是一个安全检查的开关,设置为true可提高运行效率
name.set(person,"ccc");
System.out.println(person.getName());
@Test
public void refAnnotation() throws NoSuchFieldException {
Class c1 = AnnotationTestClass.class;
//获取一个类上的注解
//注意此处强转,以及参数为注解名称的Class对象
DbTable dbTable = (DbTable)c1.getAnnotation(DbTable.class);
String value = dbTable.value();
System.out.println(value);
//获取一个域上的注解
//先获取指定的属性
Field name = c1.getDeclaredField("name");
//通过属性对象获取他的注解
DbField field = name.getAnnotation(DbField.class);
//通过注解获取其参数值
String column = field.column();
String type = field.type();
int length = field.length();
System.out.println(column+" "+type+" "+length);
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@DbTable("db_Table")
public class AnnotationTestClass {
@DbField(column = "id",type = "int",length = 10)
private int id;
@DbField(column = "name",type = "varchar",length = 20)
private String name;
public AnnotationTestClass() {
}
public AnnotationTestClass(int id, String name) {
this.id = id;
this.name = name;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DbTable{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DbField{
String column();
String type();
int length();
}
RetentionPolicy.RUNTIME
否则反射无法获取注解信息@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DbTable{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DbField{
String column();
String type();
int length();
}
OutClass.InnerClass oc = new OutClass().new InnerClass();
Outer.Inner in = new Outer.Inner();
Outer.Inner.function();
(1)示例
public class OutClass{
private int num = 10;
class InnerClass{
public void print(){
//直接访问外部类成员
System.out.println(num);
}
}
@Test
public void test(){
new InnerClass().print();
}
}
class Outer{
int num = 3;
class Inner{
int num = 4;
void show(){
int num = 5;
System.out.println(num);//访问5
//由于不明确this是属于哪个类的,因此需要加上类名.this
System.out.println(Outer.this.num);//访问3
System.out.println(Inner.this.num);//访问4
}
}
void method(){
new Inner().show();
}
}
class InnerClassDemo2 {
public static void main(String[] args) {
new Outer().method();
}
}
class Outer {
int num = 3;
void method(final int y) {
//局部内部类要想访问局部变量,此变量必须是final修饰
final int x = 9;
class Inner {
@Override
public String print() {
return "show ..." + x + y;
}
}
Object in = new Inner();
in.toString();
}
}
1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类(类的对象有限个,且内容确定)
2.当需要定义一组常量时,强烈建议使用枚举类
3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
方式一:jdk5.0之前,自定义枚举类
方式二:jdk5.0,可以使用enum关键字定义枚举类
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
toString():返回当前枚举类对象常量的名称
情况一:实现接口,在enum类中实现抽象方法
情况二:让枚举类的对象分别实现接口中的抽象方法
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
//自定义枚举类
class Season{
//1.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final的
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4.其他诉求1:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
说明:定义的枚举类默认继承于java.lang.Enum类
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
// System.out.println(Season1.class.getSuperclass());
System.out.println("****************");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
values[i].show();
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
winter.show();
}
}
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// //4.其他诉求1:提供toString()
//
// @Override
// public String toString() {
// return "Season1{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
// @Override
// public void show() {
// System.out.println("这是一个季节");
// }
}
在java中,字符串“abcd”与字符串“ab你好”的长度是一样,都是四个字符。但对应的字节数不同,一个汉字占两个字节。定义一个方法,按照最大的字节数来取子串。如:对于“ab你好”,如果取三个字节,那么子串就是ab与“你”字的半个,那么半个就要舍弃。如果去四个字节就是“ab你”,取五个字节还是“ab你”。
根据汉字起始字节都是负数,因此可以判断负数个数的奇偶进行取舍操作。
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
String str = "ab你好cd谢谢";
// str = "ab琲琲cd琲琲";
// int len = str.getBytes("gbk").length;
// for(int x=0; x
// System.out.println("截取"+(x+1)+"个字节结果是:"+cutStringByByte(str, x+1));
// }
int len = str.getBytes("utf-8").length;
for(int x=0; x<len; x++){
System.out.println("截取"+(x+1)+"个字节结果是:"+cutStringByU8Byte(str, x+1));
}
// String str = "琲";
// byte[] buf = str.getBytes("gbk");
// for(byte b : buf){
// System.out.println(b);//-84 105
// }
}
public static String cutStringByU8Byte(String str, int len) throws IOException {
byte[] buf = str.getBytes("utf-8");
int count = 0;
for(int x=len-1; x>=0; x--){
if(buf[x]<0) {
count++;
} else {
break;
}
}
if(count%3==0) {
return new String(buf,0,len,"utf-8");
} else if(count%3==1) {
return new String(buf,0,len-1,"utf-8");
} else {
return new String(buf,0,len-2,"utf-8");
}
}
public static String cutStringByByte(String str,int len) throws IOException{
byte[] buf = str.getBytes("gbk");
int count = 0;
for(int x=len-1; x>=0; x--){
if(buf[x]<0) {
count++;
} else {
break;
}
}
if(count%2==0) {
return new String(buf,0,len,"gbk");
} else {
return new String(buf,0,len-1,"gbk");
}
}
}