Введение
Операции ввода-вывода для файлов следует использовать часто.Из-за сложности файлов у нас также есть много вещей, на которые следует обратить внимание при использовании операций с файлами.Позвольте мне взглянуть.
Укажите соответствующие разрешения при создании файла
Будь то в Windows или Linux, файлы имеют концепцию управления разрешениями.Мы можем установить владельца файла и разрешение файла.Если права доступа к файлу плохо контролируются, злоумышленники могут выполнять вредоносные операции с нашими файлами.
Поэтому нам нужно учитывать вопрос разрешений при создании файлов.
К сожалению, Java плохо справляется с файловыми операциями, поэтому до JDK1.6 операции ввода-вывода в Java были очень слабыми, а базовые классы операций с файлами, такие как FileOutputStream и FileWriter, не имели параметров разрешений.
Writer out = new FileWriter("file");
Итак, как с этим бороться?
До JDK1.6 нам нужно было использовать некоторые нативные методы для реализации функции модификации разрешений.
После JDK1.6 java представила NIO, который может управлять функцией доступа к файлам с помощью некоторых функций NIO.
Давайте взглянем на метод createFile класса инструментов Files:
public static Path createFile(Path path, FileAttribute<?>... attrs)
throws IOException
{
newByteChannel(path, DEFAULT_CREATE_OPTIONS, attrs).close();
return path;
}
Где FileAttribute — это атрибут файла, давайте посмотрим, как указать права доступа к файлу:
public void createFileWithPermission() throws IOException {
Set<PosixFilePermission> perms =
PosixFilePermissions.fromString("rw-------");
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(perms);
Path file = new File("/tmp/www.flydean.com").toPath();
Files.createFile(file,attr);
}
Будьте осторожны, чтобы проверить возвращаемое значение файловых операций
Многие операции с файлами в Java имеют возвращаемые значения, такие как file.delete().Нам нужно судить о том, завершена ли файловая операция в соответствии с возвращаемым значением, поэтому не игнорируйте возвращаемое значение.
Удалить используемые временные файлы
Если мы используем файлы, которым не требуется постоянное хранилище, мы можем легко использовать createTempFile File для создания временных файлов. Имя временного файла генерируется случайным образом, и мы хотим удалить временный файл после его использования.
Как это удалить? File предоставляет метод deleteOnExit, который удаляет файл при выходе из JVM.
Обратите внимание, что JVM здесь должна завершиться нормально.Если она завершится ненормально, файл не будет удален.
Давайте посмотрим на следующий пример:
public void wrongDelete() throws IOException {
File f = File.createTempFile("tmpfile",".tmp");
FileOutputStream fop = null;
try {
fop = new FileOutputStream(f);
String str = "Data";
fop.write(str.getBytes());
fop.flush();
} finally {
// 因为Stream没有被关闭,所以文件在windows平台上面不会被删除
f.deleteOnExit(); // 在JVM退出的时候删除临时文件
if (fop != null) {
try {
fop.close();
} catch (IOException x) {
// Handle error
}
}
}
}
В приведенном выше примере мы создали временный файл и, наконец, вызвали метод deleteOnExit, но поскольку Stream не закрывается при вызове этого метода, файл не будет удален на платформе Windows.
Как это решить?
NIO предоставляет параметр DELETE_ON_CLOSE, который гарантирует, что файл будет удален после его закрытия:
public void correctDelete() throws IOException {
Path tempFile = null;
tempFile = Files.createTempFile("tmpfile", ".tmp");
try (BufferedWriter writer =
Files.newBufferedWriter(tempFile, Charset.forName("UTF8"),
StandardOpenOption.DELETE_ON_CLOSE)) {
// Write to file
}
}
В приведенном выше примере мы добавили StandardOpenOption.DELETE_ON_CLOSE во время создания модуля записи, поэтому файл будет удален после закрытия модуля записи.
Бесплатные ресурсы, которые больше не используются
Если ресурсы больше не используются, нам нужно не забыть их закрыть, иначе это приведет к утечке ресурсов.
Но много раз мы можем забыть закрыть, так что же делать? Механизм try-with-resources был введен в JDK 7. Пока ресурсы, реализующие интерфейс Closeable, помещены в оператор try, они будут автоматически закрыты, что очень удобно.
Обратите внимание на безопасность Buffer
NIO предоставляет много очень полезных классов Buffer, таких как IntBuffer, CharBuffer и ByteBuffer.Эти буферы на самом деле являются инкапсуляцией базового массива.Хотя создается новый объект Buffer, этот буфер связан с базовым массивом. , поэтому не выставляйте Buffer легко, иначе базовый массив может быть изменен.
public CharBuffer getBuffer(){
char[] dataArray = new char[10];
return CharBuffer.wrap(dataArray);
}
В приведенном выше примере представлен CharBuffer, который на самом деле также предоставляет базовый массив символов.
Есть два способа его улучшить:
public CharBuffer getBuffer1(){
char[] dataArray = new char[10];
return CharBuffer.wrap(dataArray).asReadOnlyBuffer();
}
Первый способ — преобразовать CharBuffer в режим только для чтения.
Второй способ — создать новый буфер и разорвать связь между буфером и массивом:
public CharBuffer getBuffer2(){
char[] dataArray = new char[10];
CharBuffer cb = CharBuffer.allocate(dataArray.length);
cb.put(dataArray);
return cb;
}
Обратите внимание на стандартный ввод и вывод Process
В java вы можете выполнять нативные команды через Runtime.exec(), а Runtime.exec() имеет возвращаемое значение, которое представляет собой объект Process, который используется для управления и получения информации о выполнении нативной программы.
По умолчанию созданный Процесс не имеет собственного потока ввода-вывода, что означает, что Процесс использует ввод-вывод родительского процесса (stdin, stdout, stderr).Процесс предоставляет следующие три метода для получения ввода-вывода:
getOutputStream()
getInputStream()
getErrorStream()
Если используется IO родительского процесса, то на некоторых системах эти буферные пространства относительно малы, и при большом количестве операций ввода-вывода они могут быть заблокированы или даже заблокированы.
Как это сделать? Все, что нам нужно сделать, это обработать ввод-вывод, сгенерированный процессом, чтобы предотвратить блокировку буфера.
public class StreamProcesser implements Runnable{
private final InputStream is;
private final PrintStream os;
StreamProcesser(InputStream is, PrintStream os){
this.is=is;
this.os=os;
}
@Override
public void run() {
try {
int c;
while ((c = is.read()) != -1)
os.print((char) c);
} catch (IOException x) {
// Handle error
}
}
public static void main(String[] args) throws IOException, InterruptedException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("vscode");
Thread errorGobbler
= new Thread(new StreamProcesser(proc.getErrorStream(), System.err));
Thread outputGobbler
= new Thread(new StreamProcesser(proc.getInputStream(), System.out));
errorGobbler.start();
outputGobbler.start();
int exitVal = proc.waitFor();
errorGobbler.join();
outputGobbler.join();
}
}
В приведенном выше примере мы создали StreamProcesser для обработки ошибок и ввода процесса.
InputStream.read() и Reader.read()
И у InputStream, и у Reader есть метод read().Разница между этими двумя методами заключается в том, что InputStream читает Byte, а Reader читает char.
Хотя диапазон Byte составляет от -128 до 127, InputStream.read() преобразует прочитанный байт в целое число в диапазоне 0-255 (0x00-0xff).
Диапазон Char составляет 0x0000-0xffff, и Reader.read() вернет значение int в том же диапазоне: 0x0000-0xffff.
Если возвращаемое значение равно -1, это означает, что поток завершен. Здесь int-представление -1: 0xffffffff.
В процессе использования нам нужно судить о возвращаемом значении чтения, чтобы различать границу Stream.
Мы рассматриваем такой вопрос:
FileInputStream in;
byte data;
while ((data = (byte) in.read()) != -1) {
}
Выше мы сначала конвертируем результат чтения InputStream в byte, а затем оцениваем, равен ли он -1. В чем проблема?
Если значение самого Byte равно 0xff, что само по себе равно -1, но InputStream после чтения преобразует его в int в диапазоне 0-255, тогда преобразованное значение int равно: 0x000000FF, и преобразование байта будет снова перехвачено Окончательный Oxff, Oxff == -1, в конце концов приводит к ошибочному суждению о том, что Поток заканчивается.
Поэтому нам нужно сначала оценить возвращаемое значение, а затем преобразовать его:
FileInputStream in;
int inbuff;
byte data;
while ((inbuff = in.read()) != -1) {
data = (byte) inbuff;
// ...
}
Дальнейшее чтение:
Каков результат этого кода? (целое)(знак)(байт)-1
Сначала преобразуйте -1 в байт: -1 равно 0xffffffff, преобразуйте в байт и напрямую перехватите последние несколько битов, чтобы получить 0xff, что равно -1.
Затем byte преобразуется в char: байт 0xff является знаковым, а преобразование в 2-байтовый char требует, чтобы расширение знакового бита стало 0xffff, но char не имеет знака, и соответствующее десятичное число равно 65535.
Наконец, char преобразуется в int.Поскольку char не имеет знака, он расширяется до 0x0000ffff, а соответствующее десятичное число — 65535.
В том же примере ниже, если вы используете char для предварительного преобразования int, поскольку диапазон char не имеет знака, он никогда не будет равен -1.
FileReader in;
char data;
while ((data = (char) in.read()) != -1) {
// ...
}
метод write() не выходит за рамки
В OutputStream есть очень странный метод записи, давайте посмотрим на определение метода записи:
public abstract void write(int b) throws IOException;
write получает параметр int, но на самом деле записывает байт.
Поскольку диапазоны значений int и byte различаются, входящее целое число будет усечено до последних 8 бит для преобразования в байт.
Поэтому, когда мы его используем, мы должны судить о сфере написания:
public void writeInt(int value){
int intValue = Integer.valueOf(value);
if (intValue < 0 || intValue > 255) {
throw new ArithmeticException("Value超出范围");
}
System.out.write(value);
System.out.flush();
}
Или некоторые операции Stream могут напрямую писать Int, мы можем вызывать напрямую.
Обратите внимание на использование read с массивами
InputStream имеет два метода чтения с массивами:
public int read(byte b[]) throws IOException
а также
public int read(byte b[], int off, int len) throws IOException
Если мы используем эти два метода, то мы должны обратить внимание на то, заполнен ли массив считанных байтов, рассмотрим следующий пример:
public String wrongRead(InputStream in) throws IOException {
byte[] data = new byte[1024];
if (in.read(data) == -1) {
throw new EOFException();
}
return new String(data, "UTF-8");
}
Если в данных InputStream нет 1024, или из-за того, что сеть не заполнена 1024, то мы получим массив, который не заполнен, то у нас собственно с ним проблема.
Как им правильно пользоваться?
public String readArray(InputStream in) throws IOException {
int offset = 0;
int bytesRead = 0;
byte[] data = new byte[1024];
while ((bytesRead = in.read(data, offset, data.length - offset))
!= -1) {
offset += bytesRead;
if (offset >= data.length) {
break;
}
}
String str = new String(data, 0, offset, "UTF-8");
return str;
}
Нам нужно записать количество фактически прочитанных байтов, и, записав смещение, мы получим окончательный фактический результат чтения.
Или мы можем использовать метод readFully класса DataInputStream, чтобы гарантировать чтение всего массива байтов.
Проблемы с прямым и обратным порядком байтов
Данные в java по умолчанию хранятся с прямым порядком байтов, readByte(), readShort(), readInt(), readLong(), readFloat() и readDouble() в DataInputStream также имеют обратный порядок байтов по умолчанию Проблемы могут возникнуть, когда чтение данных в процессе взаимодействия с другими прямыми байтами.
Что нам нужно, так это преобразовать обратный порядок байтов в прямой.
Как это преобразовать?
Например, если мы хотим прочитать целое число, мы можем сначала использовать метод чтения для чтения 4 байтов, а затем выполнить преобразование с прямым порядком байтов в прямой для прочитанных 4 байтов.
public void method1(InputStream inputStream) throws IOException {
try(DataInputStream dis = new DataInputStream(inputStream)) {
byte[] buffer = new byte[4];
int bytesRead = dis.read(buffer); // Bytes are read into buffer
if (bytesRead != 4) {
throw new IOException("Unexpected End of Stream");
}
int serialNumber =
ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
}
}
В приведенном выше примере мы использовали методы переноса и порядка, предоставляемые ByteBuffer, для преобразования массива Byte.
Конечно, мы также можем сделать преобразование вручную.
Существует также самый простой метод, который заключается в вызове reverseBytes() после JDK1.5, чтобы напрямую преобразовать прямой порядок байтов в прямой.
public int reverse(int i) {
return Integer.reverseBytes(i);
}
Код для этой статьи:
learn-java-base-9-to-20/tree/master/security
Эта статья была включена вWoohoo. Floyd Press.com/Java-Sec URI…
Самая популярная интерпретация, самая глубокая галантерея, самые краткие уроки и множество трюков, о которых вы не знаете, ждут вас!
Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: «Программируйте эти вещи», разбирайтесь в технологиях, лучше поймите себя!