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 + -
显示快捷键?