📄 blobbuffer.java
字号:
// 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.3 2005/04/28 14:29:31 alin_sinpalean 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 maximum size of an in memory buffer. */ private final int maxMemSize; /** * Creates a blob buffer. * * @param maxMemSize the maximum size of the in memory buffer */ public BlobBuffer(long maxMemSize) { 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"); 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; } if (writePtr > length) { length = writePtr; } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -