blobbuffer.java
来自「jtds的源码 是你学习java的好东西」· Java 代码 · 共 1,265 行 · 第 1/3 页
JAVA
1,265 行
// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
package net.sourceforge.jtds.util;
import java.io.*;
import java.sql.SQLException;
import net.sourceforge.jtds.jdbc.Messages;
/**
* Manages a buffer (backed by optional disk storage) for use as a data store
* by the CLOB and BLOB objects.
* <p/>
* The data can be purely memory based until the size exceeds the value
* dictated by the <code>lobBuffer</code> URL property after which it will be
* written to disk. The disk array is accessed randomly one page (1024 bytes)
* at a time.
* <p/>
* This class is not synchronized and concurrent open input and output
* streams can conflict.
* <p/>
* Tuning hints:
* <ol>
* <li>The <code>PAGE_SIZE</code> governs how much data is buffered when
* reading or writing data a byte at a time. 1024 bytes seems to work well
* but if very large objects are being written a byte at a time 4096 may be
* better. <b>NB.</b> ensure that the <code>PAGE_MASK</code> and
* <code>BYTE_MASK</code> fields are also adjusted to match.
* <li>Reading or writing byte arrays that are greater than or equal to the
* page size will go directly to or from the random access file cutting out
* an ArrayCopy operation.
* <li>If BLOBs are being buffered exclusively in memory you may wish to
* adjust the <code>MAX_BUF_INC</code> value. Every time the buffer is
* expanded the existing contents are copied and this may get expensive
* with very large BLOBs.
* <li>The BLOB file will be kept open for as long as there are open input or
* output streams. Therefore BLOB streams should be explicitly closed as
* soon as they are finished with.
* </ol>
*
* @author Mike Hutchinson
* @version $Id: BlobBuffer.java,v 1.4 2007/07/08 21:38:14 bheineman Exp $
*/
public class BlobBuffer {
/**
* Default zero length buffer.
*/
private static final byte[] EMPTY_BUFFER = new byte[0];
/**
* Default page size (must be power of 2).
*/
private static final int PAGE_SIZE = 1024;
/**
* Mask for page component of read/write pointer.
*/
private static final int PAGE_MASK = 0xFFFFFC00;
/**
* Mask for page offset component of R/W pointer.
*/
private static final int BYTE_MASK = 0x000003FF;
/**
* Maximum buffer increment.
*/
private static final int MAX_BUF_INC = 16384;
/**
* Invalid page marker.
*/
private static final int INVALID_PAGE = -1;
/**
* The BLOB buffer or the current page buffer.
*/
private byte[] buffer;
/**
* The total length of the valid data in buffer.
*/
private int length;
/**
* The number of the current page in memory.
*/
private int currentPage;
/**
* The name of the temporary BLOB disk file.
*/
private File blobFile;
/**
* The RA file object reference or null if closed.
*/
private RandomAccessFile raFile;
/**
* Indicates page in memory must be saved.
*/
private boolean bufferDirty;
/**
* Count of callers that have opened the BLOB file.
*/
private int openCount;
/**
* True if attempts to create a BLOB file have failed.
*/
private boolean isMemOnly;
/**
* The directory to buffer data to.
*/
private final File bufferDir;
/**
* The maximum size of an in memory buffer.
*/
private final int maxMemSize;
/**
* Creates a blob buffer.
*
* @param bufferDir
* @param maxMemSize the maximum size of the in memory buffer
*/
public BlobBuffer(File bufferDir, long maxMemSize) {
this.bufferDir = bufferDir;
this.maxMemSize = (int) maxMemSize;
buffer = EMPTY_BUFFER;
}
/**
* Finalizes this object by deleting any work files.
*/
protected void finalize() throws Throwable {
try {
if (raFile != null) {
raFile.close();
}
} catch (IOException e) {
// Ignore we are going to delete anyway
} finally {
if (blobFile != null) {
blobFile.delete();
}
}
}
/**
* Creates a random access disk file to use as backing storage for the LOB
* data.
* <p/>
* This method may fail due to security exceptions or local disk problems,
* in which case the blob storage will remain entirely in memory.
*/
public void createBlobFile() {
try {
blobFile = File.createTempFile("jtds", ".tmp", bufferDir);
blobFile.deleteOnExit();
raFile = new RandomAccessFile(blobFile, "rw");
if (length > 0) {
raFile.write(buffer, 0, (int) length);
}
buffer = new byte[PAGE_SIZE];
currentPage = INVALID_PAGE;
openCount = 0;
} catch (SecurityException e) {
blobFile = null;
raFile = null;
isMemOnly = true;
Logger.println("SecurityException creating BLOB file:");
Logger.logException(e);
} catch (IOException ioe) {
blobFile = null;
raFile = null;
isMemOnly = true;
Logger.println("IOException creating BLOB file:");
Logger.logException(ioe);
}
}
/**
* Opens the BLOB disk file.
* <p/>
* A count of open and close requests is kept so that the file may be
* closed when no longer required thus keeping the number of open files to
* a minimum.
*
* @throws IOException if an I/O error occurs
*/
public void open() throws IOException {
if (raFile == null && blobFile != null) {
// reopen file
raFile = new RandomAccessFile(blobFile, "rw");
openCount = 1;
currentPage = INVALID_PAGE;
buffer = new byte[PAGE_SIZE];
return;
}
if (raFile != null) {
openCount++;
}
}
/**
* Reads byte from the BLOB buffer at the specified location.
* <p/>
* The read pointer is partitioned into a page number and an offset within
* the page. This routine will read new pages as required. The page size
* must be a power of 2 and is currently set to 1024 bytes.
*
* @param readPtr the offset in the buffer of the required byte
* @return the byte value as an <code>int</code> or -1 if at EOF
* @throws IOException if an I/O error occurs
*/
public int read(int readPtr) throws IOException {
if (readPtr >= length) {
// At end of file.
return -1;
}
if (raFile != null) {
// Paged storage as a file exists
if (currentPage != (readPtr & PAGE_MASK)) {
// Requested page not in memory so read it
readPage(readPtr);
}
// Use the byte offset to return the correct
// byte from the page.
return buffer[readPtr & BYTE_MASK] & 0xFF;
} else {
// In memory buffer just return byte.
return buffer[readPtr] & 0xFF;
}
}
/**
* Reads bytes from the BLOB buffer at the specified location.
*
* @param readPtr the offset in the buffer of the required byte
* @param bytes the byte array to fill
* @param offset the start position in the byte array
* @param len the number of bytes to read
* @return the number of bytes read or -1 if at end of file
* @throws IOException if an I/O error occurs
*/
public int read(int readPtr, byte[] bytes, int offset, int len)
throws IOException {
// Validate parameters
if (bytes == null) {
throw new NullPointerException();
} else if ((offset < 0) || (offset > bytes.length) || (len < 0)
|| ((offset + len) > bytes.length) || ((offset + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (readPtr >= length) {
// At end of file
return -1;
}
if (raFile != null) {
// Need to read from disk file
len = Math.min(length - readPtr, len);
if (len >= PAGE_SIZE) {
// This is a big write so we optimize by reading directly
// from the RA File.
if (bufferDirty) {
writePage(currentPage);
}
currentPage = INVALID_PAGE;
raFile.seek(readPtr);
raFile.readFully(bytes, offset, len);
} else {
//
// Partial read so buffer locally
//
int count = len;
while (count > 0) {
if (currentPage != (readPtr & PAGE_MASK)) {
// Requested page not in memory so read it
readPage(readPtr);
}
int inBuffer = Math.min(PAGE_SIZE - (readPtr & BYTE_MASK), count);
System.arraycopy(buffer, readPtr & BYTE_MASK, bytes, offset, inBuffer);
offset += inBuffer;
readPtr += inBuffer;
count -= inBuffer;
}
}
} else {
// In memory buffer
len = Math.min(length - readPtr, len);
System.arraycopy(buffer, readPtr, bytes, offset, len);
}
return len;
}
/**
* Inserts a byte into the buffer at the specified location.
* <p/>
* The write pointer is partitioned into a page number and an offset within
* the page. This routine will write new pages as required. The page size
* must be a power of 2 and is currently set to 1024 bytes.
*
* @param writePtr the offset in the buffer of the required byte
* @param b the byte value to write
* @throws IOException if an I/O error occurs
*/
public void write(int writePtr, int b) throws IOException {
if (writePtr >= length) {
if (writePtr > length) {
// Probably because the user called truncate at
// the same time as writing to the blob!
throw new IOException("BLOB buffer has been truncated");
}
// We are writing beyond the current length
// of the buffer and need to update the total length.
if (++length < 0) {
// We have wrapped 31 bits!
// This should ensure that the disk file is limited to 2GB.
// If in memory JVM will probably have failed by now anyway.
throw new IOException("BLOB may not exceed 2GB in size");
}
}
if (raFile != null) {
// OK we have a disk based buffer
if (currentPage != (writePtr & PAGE_MASK)) {
// The page we need is not in memory
readPage(writePtr);
}
buffer[writePtr & BYTE_MASK] = (byte) b;
// Ensure change will saved if buffer is replaced
bufferDirty = true;
} else {
// In memory buffer only (only used here if disk unavailable
if (writePtr >= buffer.length) {
growBuffer(writePtr + 1);
}
buffer[writePtr] = (byte) b;
}
}
/**
* Inserts bytes into the buffer at the specified location.
*
* @param writePtr the offset in the buffer of the required byte
* @param bytes the byte array value to write
* @param offset the start position in the byte array
* @param len the number of bytes to write
* @throws IOException if an I/O error occurs
*/
void write(int writePtr, byte[] bytes, int offset, int len)
throws IOException {
// Validate parameters
if (bytes == null) {
throw new NullPointerException();
} else if ((offset < 0) || (offset > bytes.length) || (len < 0) ||
((offset + len) > bytes.length) || ((offset + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
if ((long) writePtr + len > (long) Integer.MAX_VALUE) {
throw new IOException("BLOB may not exceed 2GB in size");
}
if (writePtr > length) {
// Probably because the user called truncate at
// the same time as writing to the blob!
throw new IOException("BLOB buffer has been truncated");
}
if (raFile != null) {
// dealing with disk storage (normal case)
//
if (len >= PAGE_SIZE) {
// This is a big write so we optimize by writing directly
// to the RA File.
if (bufferDirty) {
writePage(currentPage);
}
currentPage = INVALID_PAGE;
raFile.seek(writePtr);
raFile.write(bytes, offset, len);
writePtr += len;
} else {
// Small writes so use the page buffer for
// effeciency.
int count = len;
while (count > 0) {
// Paged storage as a file exists
if (currentPage != (writePtr & PAGE_MASK)) {
// Requested page not in memory so read it
readPage(writePtr);
}
int inBuffer = Math.min(
PAGE_SIZE - (writePtr & BYTE_MASK), count);
System.arraycopy(bytes, offset, buffer,
writePtr & BYTE_MASK, inBuffer);
bufferDirty = true;
offset += inBuffer;
writePtr += inBuffer;
count -= inBuffer;
}
}
} else {
// In memory (only used here if disk not available)
if (writePtr + len > buffer.length) {
growBuffer(writePtr + len);
}
System.arraycopy(bytes, offset, buffer, writePtr, len);
writePtr += len;
}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?