Древние говорили: «Когда ты прочитаешь тысячи томов, ты сможешь писать как бог». Иными словами, при письме может быть успешным только много чтение — письмо означает вывод (яраспространять знания среди других), в то время как чтение означает ввод (получение пищи из знаний других).
Для ввода-вывода Java I означает ввод, а O означает вывод. Чтение и запись — непростая задача, а создать хорошую систему ввода-вывода еще сложнее.
01. Байты и символы потока данных
Все механизмы ввода-вывода в Java основаны напоток данныхввод и вывод. Существует два типа потоков данных:
1) Поток байтов, необработанные необработанные двоичные данные, наименьшая единица данныхбайт.
2) Поток символов, данные, которые соответствуют определенному формату после определенного процесса кодирования, наименьшая единица данныхперсонаж- Занимает два байта.
OutputStream
а такжеInputStream
используется для обработки потоков байтов;Writer
а такжеReader
используется для обработки потоков символов;OutputStreamWriter
можно поставитьOutputStream
Перевести вWriter
,InputStreamReader
можно поставитьInputStream
Перевести вReader
.
Разработчики Java разработали для этого множество классов, как показано на рисунке ниже.
Вы, должно быть, чувствуете головокружение, видя так много категорий. В любом случае, я теряю терпение. Кажется, что голова действительно большая с таким количеством категорий - это также показывает со стороны, что реальные сценарии применения разные - вам вообще не нужно беспокоиться, потому что в реальных проектах невозможно использовать их все (Я не буду использоватьSequenceOutputStream
).
Я предлагаю вамПри обучении мы должны овладеть способностью «выбора».—— Изучайте интересующие вас знания, которые необходимо освоить, и улучшайте свои способности. Вы не должны глотать финики целиком и заставлять себя учить все подряд. Узнай все, и конечным результатом может быть вообще ничего.
Потоки символов основаны на потоках байтов, поэтому давайте сначала узнаем о двух самых основных классах потоков байтов:OutputStream
а такжеInputStream
, они должны быть освоены.
1) Выходной поток
OutputStream
Ниже приведены четыре очень полезных метода.
-
public void write(byte b[])
: записать байты из массива b в выходной поток. -
public void write(byte b[], int off, int len)
: Записывает len байтов массива b, начиная со смещения, в выходной поток. -
public void flush()
: Вывести все данные из буфера данных и очистить буфер. -
public void close()
: закрывает выходной поток и освобождает системные ресурсы, связанные с потоком.
его подклассыByteArrayOutputStream
а такжеBufferedOuputStream
Наиболее часто используемые (классы, связанные с файлами, размещены в следующем разделе).
①,ByteArrayOutputStream
Обычно используется для создания буфера байтовых массивов в памяти, где данные размещаются «временно», а не выводятся в файл или сетевой сокет — например, релейная станция, отвечающая за отправку входного потока. Данные считываются в буфер памяти, вы можете назовите егоtoByteArray()
метод для получения массива байтов.
Взгляните на пример ниже.
public static byte[] readBytes(InputStream in, long length) throws IOException {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read = 0;
while (read < length) {
int cur = in.read(buffer, 0, (int) Math.min(1024, length - read));
if (cur < 0) {
break;
}
read += cur;
bo.write(buffer, 0, cur);
}
return bo.toByteArray();
}
ByteArrayOutputStream
ответственность заключается вInputStream
Поток байтов в «подробном» считывании — этот инструментальный метод очень важен, очень важен, очень важен — может решить проблему залипания пакетов.
②,BufferedOuputStream
Реализован буферизованный поток вывода, который может буферизовать множество небольших данных в большой блок данных, а затем за один раз выводить в файл или сетевой сокет — «буферизация» здесь иByteArrayOutputStream
"Буферизация" очень разная - первая для очередного разового вывода, а вторая чисто для буферизации, а вывода нет.
Взгляните на пример ниже.
protected void write(byte[] data) throws IOException {
out.write(intToByte(data.length));
out.write(data);
out.flush();
out.close();
}
public static byte[] intToByte(int num)
{
byte[] data = new byte[4];
for (int i = 0; i < data.length; i++)
{
data[3-i] = (byte)(num % 256);
num = num / 256;
}
return data;
}
использоватьBufferedOuputStream
Когда вы должны помнить, чтобы позвонитьflush()
Метод выводит все данные из буфера. После использования позвонитеclose()
Метод закрывает выходной поток, освобождая системные ресурсы, связанные с потоком.
2) Входной поток
InputStream
Также предоставляются четыре очень полезных метода, а именно:
-
public int read(byte b[])
: чтение b.length байтов данных в массив b, возвращаемое значение — количество прочитанных байтов. -
public int read(byte b[], int off, int len)
: прочитать до len байтов данных из входного потока и сохранить их в массиве b с выключенным смещением. -
public int available()
: возвращает количество байтов, которые можно прочитать из входного потока. -
public int close()
: закрыть открытый поток после использования.
его подклассыBufferedInputStream
(буферизация входного потока) является наиболее распространенным и наиболее эффективным (когда мы не уверены, читаем ли мы большие или маленькие данные).
Каждый запрос на чтение небуферизованного потока обычно приводит к вызову операционной системы для чтения запрошенного количества байтов, что делает системный вызов очень дорогим. Но буферизация входного потока отличается, она снижает накладные расходы на системные вызовы, выполняя большое чтение (скажем) до 8k байт во внутренний буфер, а затем перераспределяя байты по размеру буфера — производительность значительно улучшится.
Пример использования следующий.
Сначала рассмотрим вспомогательный методbyteToInt
, преобразует байты в int.
public static int byteToInt(byte[] b) {
int num = 0;
for (int i = 0; i < b.length; i++) {
num*=256;
num+=(b[i]+256)%256;
}
return num;
}
Давайте посмотрим, как читать данные из входного потока по заданной длине contentLength.readBytes()
Метод упоминался ранее.
BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
byte[] tmpByte = new byte[4];
// 读取四个字节判断消息长度
in.read(tmpByte, 0, 4);
// 将byte转为int
int contentLength = byteToInt(tmpByte);
byte[] buf = null;
if (contentLength > in.available()) {
// 之前提到的方法
buf = readBytes(in, contentLength);
} else {
buf = new byte[contentLength];
in.read(buf, 0, contentLength);
// 发生粘包了
if (in.available() > 0) {
}
}
Я гарантирую, что пока вы понимаете потоки байтов, потоки символов не представляют проблемы, поэтому здесь мы пропустим потоки символов.
02. Класс файла
Ранее мы узнали, что данные имеют два формата: байты и символы. Так откуда берутся эти данные и куда они идут?
Основной способ — чтение и сохранение с физических дисков, единственным минимальным описанием которых является файл. Другими словами, приложение верхнего уровня может работать с данными на диске только через файл, а файл также является наименьшей единицей взаимодействия между операционной системой и дисководом.
В Java обычно используетсяFile
класс для работы с файлами. Конечно, File — это не просто файл, это еще и папка (директория). Класс File сохраняет различные метаданные файла или каталога (имя файла, длину файла, время последнего изменения, доступность для чтения, путь к текущему файлу и т. д.).
Через класс File и файловые входные и выходные потоки (FileInputStream
,FileOutputStream
), чтобы легко создавать, удалять и копировать файлы или каталоги.
Здесь я предлагаю вам практичный класс утилиты для работы с файлами — FileUtils.
package com.cmower.common.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
/**
* 文件操作工具类
* 实现文件的创建、删除、复制、压缩、解压以及目录的创建、删除、复制、压缩解压等功能
*/
public class FileUtils extends org.apache.commons.io.FileUtils {
private static Logger logger = LoggerFactory.getLogger(FileUtils.class);
/**
* 复制单个文件,如果目标文件存在,则不覆盖
* @param srcFileName 待复制的文件名
* @param descFileName 目标文件名
* @return 如果复制成功,则返回true,否则返回false
*/
public static boolean copyFile(String srcFileName, String descFileName) {
return FileUtils.copyFileCover(srcFileName, descFileName, false);
}
/**
* 复制单个文件
* @param srcFileName 待复制的文件名
* @param descFileName 目标文件名
* @param coverlay 如果目标文件已存在,是否覆盖
* @return 如果复制成功,则返回true,否则返回false
*/
public static boolean copyFileCover(String srcFileName,
String descFileName, boolean coverlay) {
File srcFile = new File(srcFileName);
// 判断源文件是否存在
if (!srcFile.exists()) {
logger.debug("复制文件失败,源文件 " + srcFileName + " 不存在!");
return false;
}
// 判断源文件是否是合法的文件
else if (!srcFile.isFile()) {
logger.debug("复制文件失败," + srcFileName + " 不是一个文件!");
return false;
}
File descFile = new File(descFileName);
// 判断目标文件是否存在
if (descFile.exists()) {
// 如果目标文件存在,并且允许覆盖
if (coverlay) {
logger.debug("目标文件已存在,准备删除!");
if (!FileUtils.delFile(descFileName)) {
logger.debug("删除目标文件 " + descFileName + " 失败!");
return false;
}
} else {
logger.debug("复制文件失败,目标文件 " + descFileName + " 已存在!");
return false;
}
} else {
if (!descFile.getParentFile().exists()) {
// 如果目标文件所在的目录不存在,则创建目录
logger.debug("目标文件所在的目录不存在,创建目录!");
// 创建目标文件所在的目录
if (!descFile.getParentFile().mkdirs()) {
logger.debug("创建目标文件所在的目录失败!");
return false;
}
}
}
// 准备复制文件
// 读取的位数
int readByte = 0;
InputStream ins = null;
OutputStream outs = null;
try {
// 打开源文件
ins = new FileInputStream(srcFile);
// 打开目标文件的输出流
outs = new FileOutputStream(descFile);
byte[] buf = new byte[1024];
// 一次读取1024个字节,当readByte为-1时表示文件已经读取完毕
while ((readByte = ins.read(buf)) != -1) {
// 将读取的字节流写入到输出流
outs.write(buf, 0, readByte);
}
logger.debug("复制单个文件 " + srcFileName + " 到" + descFileName
+ "成功!");
return true;
} catch (Exception e) {
logger.debug("复制文件失败:" + e.getMessage());
return false;
} finally {
// 关闭输入输出流,首先关闭输出流,然后再关闭输入流
if (outs != null) {
try {
outs.close();
} catch (IOException oute) {
oute.printStackTrace();
}
}
if (ins != null) {
try {
ins.close();
} catch (IOException ine) {
ine.printStackTrace();
}
}
}
}
/**
* 复制整个目录的内容,如果目标目录存在,则不覆盖
* @param srcDirName 源目录名
* @param descDirName 目标目录名
* @return 如果复制成功返回true,否则返回false
*/
public static boolean copyDirectory(String srcDirName, String descDirName) {
return FileUtils.copyDirectoryCover(srcDirName, descDirName,
false);
}
/**
* 复制整个目录的内容
* @param srcDirName 源目录名
* @param descDirName 目标目录名
* @param coverlay 如果目标目录存在,是否覆盖
* @return 如果复制成功返回true,否则返回false
*/
public static boolean copyDirectoryCover(String srcDirName,
String descDirName, boolean coverlay) {
File srcDir = new File(srcDirName);
// 判断源目录是否存在
if (!srcDir.exists()) {
logger.debug("复制目录失败,源目录 " + srcDirName + " 不存在!");
return false;
}
// 判断源目录是否是目录
else if (!srcDir.isDirectory()) {
logger.debug("复制目录失败," + srcDirName + " 不是一个目录!");
return false;
}
// 如果目标文件夹名不以文件分隔符结尾,自动添加文件分隔符
String descDirNames = descDirName;
if (!descDirNames.endsWith(File.separator)) {
descDirNames = descDirNames + File.separator;
}
File descDir = new File(descDirNames);
// 如果目标文件夹存在
if (descDir.exists()) {
if (coverlay) {
// 允许覆盖目标目录
logger.debug("目标目录已存在,准备删除!");
if (!FileUtils.delFile(descDirNames)) {
logger.debug("删除目录 " + descDirNames + " 失败!");
return false;
}
} else {
logger.debug("目标目录复制失败,目标目录 " + descDirNames + " 已存在!");
return false;
}
} else {
// 创建目标目录
logger.debug("目标目录不存在,准备创建!");
if (!descDir.mkdirs()) {
logger.debug("创建目标目录失败!");
return false;
}
}
boolean flag = true;
// 列出源目录下的所有文件名和子目录名
File[] files = srcDir.listFiles();
for (int i = 0; i < files.length; i++) {
// 如果是一个单个文件,则直接复制
if (files[i].isFile()) {
flag = FileUtils.copyFile(files[i].getAbsolutePath(),
descDirName + files[i].getName());
// 如果拷贝文件失败,则退出循环
if (!flag) {
break;
}
}
// 如果是子目录,则继续复制目录
if (files[i].isDirectory()) {
flag = FileUtils.copyDirectory(files[i]
.getAbsolutePath(), descDirName + files[i].getName());
// 如果拷贝目录失败,则退出循环
if (!flag) {
break;
}
}
}
if (!flag) {
logger.debug("复制目录 " + srcDirName + " 到 " + descDirName + " 失败!");
return false;
}
logger.debug("复制目录 " + srcDirName + " 到 " + descDirName + " 成功!");
return true;
}
/**
*
* 删除文件,可以删除单个文件或文件夹
*
* @param fileName 被删除的文件名
* @return 如果删除成功,则返回true,否是返回false
*/
public static boolean delFile(String fileName) {
File file = new File(fileName);
if (!file.exists()) {
logger.debug(fileName + " 文件不存在!");
return true;
} else {
if (file.isFile()) {
return FileUtils.deleteFile(fileName);
} else {
return FileUtils.deleteDirectory(fileName);
}
}
}
/**
*
* 删除单个文件
*
* @param fileName 被删除的文件名
* @return 如果删除成功,则返回true,否则返回false
*/
public static boolean deleteFile(String fileName) {
File file = new File(fileName);
if (file.exists() && file.isFile()) {
if (file.delete()) {
logger.debug("删除文件 " + fileName + " 成功!");
return true;
} else {
logger.debug("删除文件 " + fileName + " 失败!");
return false;
}
} else {
logger.debug(fileName + " 文件不存在!");
return true;
}
}
/**
*
* 删除目录及目录下的文件
*
* @param dirName 被删除的目录所在的文件路径
* @return 如果目录删除成功,则返回true,否则返回false
*/
public static boolean deleteDirectory(String dirName) {
String dirNames = dirName;
if (!dirNames.endsWith(File.separator)) {
dirNames = dirNames + File.separator;
}
File dirFile = new File(dirNames);
if (!dirFile.exists() || !dirFile.isDirectory()) {
logger.debug(dirNames + " 目录不存在!");
return true;
}
boolean flag = true;
// 列出全部文件及子目录
File[] files = dirFile.listFiles();
for (int i = 0; i < files.length; i++) {
// 删除子文件
if (files[i].isFile()) {
flag = FileUtils.deleteFile(files[i].getAbsolutePath());
// 如果删除文件失败,则退出循环
if (!flag) {
break;
}
}
// 删除子目录
else if (files[i].isDirectory()) {
flag = FileUtils.deleteDirectory(files[i]
.getAbsolutePath());
// 如果删除子目录失败,则退出循环
if (!flag) {
break;
}
}
}
if (!flag) {
logger.debug("删除目录失败!");
return false;
}
// 删除当前目录
if (dirFile.delete()) {
logger.debug("删除目录 " + dirName + " 成功!");
return true;
} else {
logger.debug("删除目录 " + dirName + " 失败!");
return false;
}
}
/**
* 创建单个文件
* @param descFileName 文件名,包含路径
* @return 如果创建成功,则返回true,否则返回false
*/
public static boolean createFile(String descFileName) {
File file = new File(descFileName);
if (file.exists()) {
logger.debug("文件 " + descFileName + " 已存在!");
return false;
}
if (descFileName.endsWith(File.separator)) {
logger.debug(descFileName + " 为目录,不能创建目录!");
return false;
}
if (!file.getParentFile().exists()) {
// 如果文件所在的目录不存在,则创建目录
if (!file.getParentFile().mkdirs()) {
logger.debug("创建文件所在的目录失败!");
return false;
}
}
// 创建文件
try {
if (file.createNewFile()) {
logger.debug(descFileName + " 文件创建成功!");
return true;
} else {
logger.debug(descFileName + " 文件创建失败!");
return false;
}
} catch (Exception e) {
e.printStackTrace();
logger.debug(descFileName + " 文件创建失败!");
return false;
}
}
/**
* 创建目录
* @param descDirName 目录名,包含路径
* @return 如果创建成功,则返回true,否则返回false
*/
public static boolean createDirectory(String descDirName) {
String descDirNames = descDirName;
if (!descDirNames.endsWith(File.separator)) {
descDirNames = descDirNames + File.separator;
}
File descDir = new File(descDirNames);
if (descDir.exists()) {
logger.debug("目录 " + descDirNames + " 已存在!");
return false;
}
// 创建目录
if (descDir.mkdirs()) {
logger.debug("目录 " + descDirNames + " 创建成功!");
return true;
} else {
logger.debug("目录 " + descDirNames + " 创建失败!");
return false;
}
}
/**
* 写入文件
* @param file 要写入的文件
*/
public static void writeToFile(String fileName, String content, boolean append) {
try {
FileUtils.write(new File(fileName), content, "utf-8", append);
logger.debug("文件 " + fileName + " 写入成功!");
} catch (IOException e) {
logger.debug("文件 " + fileName + " 写入失败! " + e.getMessage());
}
}
/**
* 写入文件
* @param file 要写入的文件
*/
public static void writeToFile(String fileName, String content, String encoding, boolean append) {
try {
FileUtils.write(new File(fileName), content, encoding, append);
logger.debug("文件 " + fileName + " 写入成功!");
} catch (IOException e) {
logger.debug("文件 " + fileName + " 写入失败! " + e.getMessage());
}
}
/**
* 获目录下的文件列表
* @param dir 搜索目录
* @param searchDirs 是否是搜索目录
* @return 文件列表
*/
public static List<String> findChildrenList(File dir, boolean searchDirs) {
List<String> files = Lists.newArrayList();
for (String subFiles : dir.list()) {
File file = new File(dir + "/" + subFiles);
if (((searchDirs) && (file.isDirectory())) || ((!searchDirs) && (!file.isDirectory()))) {
files.add(file.getName());
}
}
return files;
}
}
-
public static boolean createFile(String descFileName)
: Создать файл. -
public static boolean createDirectory(String descDirName)
: Создать каталог. -
public static boolean copyFile(String srcFileName, String descFileName)
: Скопируйте файл. -
public static boolean copyDirectory(String srcDirName, String descDirName)
: Скопируйте каталог. -
public static boolean deleteFile(String fileName)
:Удалить файлы. -
public static boolean deleteDirectory(String dirName)
: удалить каталог. -
public static void writeToFile(String fileName, String content, boolean append)
: Запишите содержимое в файл.
03. Сетевая розетка - Розетка
Хотя сетевые сокеты (Socket
) нет в пакете java.io, но он тесно связан с входными и выходными потоками.File
а такжеSocket
Существует два основных метода передачи данных.
Socket
Это абстракция, описывающая завершение взаимного общения между компьютерами. можно поставитьSocket
В качестве средства передвижения между двумя городами с помощью транспортных средств (высокоскоростная железная дорога, автомобили) вы можете путешествовать между городами туда и обратно. Существует множество видов транспортных средств, и каждому виду транспортных средств также соответствуют правила дорожного движения.Socket
Кроме того, есть много видов. В большинстве случаев мы используемTCP/IP
Сокеты - стабильный протокол связи.
Предположим, что хост A является клиентом, а хост B — сервером. Для связи с сервером клиент должен сначала создатьSocket
например, операционная система будетSocket
Экземпляр выделяет номер локального порта, который не используется, и создает структуру данных сокета, пока соединение не будет закрыто.
Примеры следующие.
Socket socket = new Socket(serverIp, serverPort);
BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
Соответственно сервер должен создатьServerSocket
экземпляр, затем позвонитеaccept()
Метод переходит в состояние блокировки, ожидая запроса клиента. При поступлении нового запроса для этого соединения будет создана новая структура данных сокета.
Примеры следующие.
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
InputStream in = new BufferedInputStream(socket.getInputStream());
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
Socket
После открытия вы можете пройтиInputStream
а такжеOutputStream
Выполняется передача данных.
04. Сжатие
Ввод-вывод Java поддерживает потоки данных в сжатом формате. существуетSocket
В общении я часто используюGZIPOutputStream
а такжеGZIPInputStream
просто сжимать и распаковывать потоки данных.
Преимущество сжатия заключается в том, что оно может уменьшить размер данных, передаваемых по сети. код показывает, как показано ниже.
package com.cmower.common.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* 压缩解压
*/
public class CompressionUtil {
public static byte[] compress(byte[] data) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] result = null;
GZIPOutputStream zos = new GZIPOutputStream(bos);
zos.write(data);
zos.finish();
zos.flush();
result = bos.toByteArray();
zos.close();
bos.close();
return result;
}
public static byte[] deCompress(byte[] in) throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
GZIPInputStream inStream = new GZIPInputStream(new ByteArrayInputStream(in));
byte[] buf = new byte[1024];
while (true) {
try {
int size = inStream.read(buf);
if (size <= 0)
break;
outStream.write(buf, 0, size);
} catch (Exception e) {
e.printStackTrace();
break;
}
}
inStream.close();
outStream.close();
return outStream.toByteArray();
}
}
Предыдущий:Аннотация Java: Пожалуйста, не недооценивайте меня!
Следующий:Java: параллелизм — это непросто, сначала научитесь использовать
Поиск в WeChat »Тихий король 2"Общественный номер, подпишитесь и ответьте"бесплатное видео"Получите 500 ГБ высококачественных обучающих видео (по категориям).