FileChannels.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.io.channels;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
/**
* Works with {@link FileChannel}.
*
* @since 2.15.0
*/
public final class FileChannels {
/**
* Tests if two file channel contents are equal starting at their respective current positions.
*
* @param channel1 A file channel.
* @param channel2 Another file channel.
* @param bufferCapacity The two internal buffer capacities, in bytes.
* @return true if the contents of both RandomAccessFiles are equal, false otherwise.
* @throws IOException if an I/O error occurs.
* @deprecated Use {@link #contentEquals(SeekableByteChannel, SeekableByteChannel, int)}.
*/
@Deprecated
public static boolean contentEquals(final FileChannel channel1, final FileChannel channel2, final int bufferCapacity) throws IOException {
return contentEquals((SeekableByteChannel) channel1, channel2, bufferCapacity);
}
/**
* Tests if two readable byte channel contents are equal starting at their respective current positions.
*
* @param channel1 A readable byte channel.
* @param channel2 Another readable byte channel.
* @param bufferCapacity The two internal buffer capacities, in bytes.
* @return true if the contents of both RandomAccessFiles are equal, false otherwise.
* @throws IOException if an I/O error occurs or the timeout is met.
* @since 2.19.0
*/
public static boolean contentEquals(final ReadableByteChannel channel1, final ReadableByteChannel channel2, final int bufferCapacity) throws IOException {
// Before making any changes, please test with org.apache.commons.io.jmh.IOUtilsContentEqualsInputStreamsBenchmark
// Short-circuit test
if (Objects.equals(channel1, channel2)) {
return true;
}
// Don't use ByteBuffer#compact() to avoid extra copying.
final ByteBuffer c1Buffer = ByteBuffer.allocateDirect(bufferCapacity);
final ByteBuffer c2Buffer = ByteBuffer.allocateDirect(bufferCapacity);
int c1NumRead = 0;
int c2NumRead = 0;
boolean c1Read0 = false;
boolean c2Read0 = false;
// If a channel is a non-blocking channel, it may return 0 bytes read for any given call.
while (true) {
if (!c2Read0) {
c1NumRead = readToLimit(channel1, c1Buffer);
c1Buffer.clear();
c1Read0 = c1NumRead == 0;
}
if (!c1Read0) {
c2NumRead = readToLimit(channel2, c2Buffer);
c2Buffer.clear();
c2Read0 = c2NumRead == 0;
}
if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) {
return c1Buffer.equals(c2Buffer);
}
if (c1NumRead == 0 || c2NumRead == 0) {
// 0 may be returned from a non-blocking channel.
Thread.yield();
continue;
}
if (c1NumRead != c2NumRead) {
return false;
}
if (!c1Buffer.equals(c2Buffer)) {
return false;
}
}
}
/**
* Tests if two seekable byte channel contents are equal starting at their respective current positions.
* <p>
* If the two channels have different sizes, no content comparison takes place, and this method returns false.
* </p>
*
* @param channel1 A seekable byte channel.
* @param channel2 Another seekable byte channel.
* @param bufferCapacity The two internal buffer capacities, in bytes.
* @return true if the contents of both RandomAccessFiles are equal, false otherwise.
* @throws IOException if an I/O error occurs or the timeout is met.
* @since 2.19.0
*/
public static boolean contentEquals(final SeekableByteChannel channel1, final SeekableByteChannel channel2, final int bufferCapacity) throws IOException {
// Short-circuit test
if (Objects.equals(channel1, channel2)) {
return true;
}
// Short-circuit test
final long size1 = size(channel1);
final long size2 = size(channel2);
if (size1 != size2) {
return false;
}
return size1 == 0 && size2 == 0 || contentEquals((ReadableByteChannel) channel1, channel2, bufferCapacity);
}
/**
* Reads a sequence of bytes from a channel into the given buffer until the buffer reaches its limit or the channel has reaches end-of-stream.
* <p>
* The buffer's limit is not changed.
* </p>
*
* @param channel The source channel.
* @param dst The buffer into which bytes are to be transferred.
* @return The number of bytes read, <em>never</em> zero, or {@code -1} if the channel has reached end-of-stream
* @throws IOException If some other I/O error occurs.
* @throws IllegalArgumentException If there is room in the given buffer.
*/
private static int readToLimit(final ReadableByteChannel channel, final ByteBuffer dst) throws IOException {
if (!dst.hasRemaining()) {
throw new IllegalArgumentException();
}
int totalRead = 0;
while (dst.hasRemaining()) {
final int numRead;
if ((numRead = channel.read(dst)) == IOUtils.EOF) {
break;
}
if (numRead == 0) {
// 0 may be returned from a non-blocking channel.
Thread.yield();
} else {
totalRead += numRead;
}
}
return totalRead != 0 ? totalRead : IOUtils.EOF;
}
private static long size(final SeekableByteChannel channel) throws IOException {
return channel != null ? channel.size() : 0;
}
/**
* Don't instantiate.
*/
private FileChannels() {
// no-op
}
}