?? valuelob.java
字號:
/*
* Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.message.Message;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.FileStoreInputStream;
import org.h2.store.FileStoreOutputStream;
import org.h2.store.fs.FileSystem;
import org.h2.util.ByteUtils;
import org.h2.util.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.SmallLRUCache;
import org.h2.util.StringUtils;
/**
* Implementation of the BLOB and CLOB data types.
*/
public class ValueLob extends Value {
// TODO lob: concatenate function for blob and clob
// (to create a large blob from pieces)
// and a getpart function (to get it in pieces) and make sure a file is created!
public static final int TABLE_ID_SESSION = -1;
private final int type;
private long precision;
private DataHandler handler;
private int tableId;
private int objectId;
private String fileName;
private boolean linked;
private byte[] small;
private int hash;
private boolean compression;
private FileStore tempFile;
/**
* This counter is used to calculate the next directory to store lobs.
* It is better than using a random number because less directories are created.
*/
private static int dirCounter;
private ValueLob(int type, DataHandler handler, String fileName, int tableId, int objectId, boolean linked,
long precision, boolean compression) {
this.type = type;
this.handler = handler;
this.fileName = fileName;
this.tableId = tableId;
this.objectId = objectId;
this.linked = linked;
this.precision = precision;
this.compression = compression;
}
private static ValueLob copy(ValueLob lob) {
ValueLob copy = new ValueLob(lob.type, lob.handler, lob.fileName, lob.tableId, lob.objectId, lob.linked, lob.precision, lob.compression);
copy.small = lob.small;
copy.hash = lob.hash;
return copy;
}
private ValueLob(int type, byte[] small) throws SQLException {
this.type = type;
this.small = small;
if (small != null) {
if (type == Value.BLOB) {
this.precision = small.length;
} else {
this.precision = getString().length();
}
}
}
public static ValueLob createSmallLob(int type, byte[] small) throws SQLException {
return new ValueLob(type, small);
}
private static String getFileName(DataHandler handler, int tableId, int objectId) {
if (SysProperties.CHECK && tableId == 0 && objectId == 0) {
throw Message.getInternalError("0 LOB");
}
if (handler.getLobFilesInDirectories()) {
String table = tableId < 0 ? ".temp" : ".t" + tableId;
return getFileNamePrefix(handler.getDatabasePath(), objectId) + table + Constants.SUFFIX_LOB_FILE;
} else {
return handler.getDatabasePath() + "." + tableId + "." + objectId + Constants.SUFFIX_LOB_FILE;
}
}
/**
* Create a LOB value with the given parameters.
*
* @param type the data type
* @param handler the file handler
* @param tableId the table object id
* @param objectId the object id
* @param precision the precision (length in elements)
* @param compression if compression is used
* @return the value object
*/
public static ValueLob open(int type, DataHandler handler, int tableId, int objectId, long precision, boolean compression) {
String fileName = getFileName(handler, tableId, objectId);
return new ValueLob(type, handler, fileName, tableId, objectId, true, precision, compression);
}
public static ValueLob createClob(Reader in, long length, DataHandler handler) throws SQLException {
try {
boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null;
long remaining = Long.MAX_VALUE;
if (length >= 0 && length < remaining) {
remaining = length;
}
int len = getBufferSize(handler, compress, remaining);
char[] buff = new char[len];
len = IOUtils.readFully(in, buff, len);
len = len < 0 ? 0 : len;
if (len <= handler.getMaxLengthInplaceLob()) {
byte[] small = StringUtils.utf8Encode(new String(buff, 0, len));
return ValueLob.createSmallLob(Value.CLOB, small);
}
ValueLob lob = new ValueLob(Value.CLOB, null);
lob.createFromReader(buff, len, in, remaining, handler);
return lob;
} catch (IOException e) {
throw Message.convertIOException(e, null);
}
}
private static int getBufferSize(DataHandler handler, boolean compress, long remaining) {
if (remaining < 0 || remaining > Integer.MAX_VALUE) {
remaining = Integer.MAX_VALUE;
}
long inplace = handler.getMaxLengthInplaceLob();
if (inplace >= Integer.MAX_VALUE) {
inplace = remaining;
}
long m = compress ? Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE;
if (m < remaining && m <= inplace) {
m = Math.min(remaining, inplace + 1);
// the buffer size must be bigger than the inplace lob, otherwise we can't
// know if it must be stored in-place or not
m = MathUtils.roundUpLong(m, Constants.IO_BUFFER_SIZE);
}
m = Math.min(remaining, m);
m = MathUtils.convertLongToInt(m);
if (m < 0) {
m = Integer.MAX_VALUE;
}
return (int) m;
}
private void createFromReader(char[] buff, int len, Reader in, long remaining, DataHandler handler) throws SQLException {
try {
FileStoreOutputStream out = initLarge(handler);
boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null;
try {
while (true) {
precision += len;
byte[] b = StringUtils.utf8Encode(new String(buff, 0, len));
out.write(b, 0, b.length);
remaining -= len;
if (remaining <= 0) {
break;
}
len = getBufferSize(handler, compress, remaining);
len = IOUtils.readFully(in, buff, len);
if (len <= 0) {
break;
}
}
} finally {
out.close();
}
} catch (IOException e) {
throw Message.convertIOException(e, null);
}
}
private static String getFileNamePrefix(String path, int objectId) {
String name;
int f = objectId % SysProperties.LOB_FILES_PER_DIRECTORY;
if (f > 0) {
name = File.separator + objectId;
} else {
name = "";
}
objectId /= SysProperties.LOB_FILES_PER_DIRECTORY;
while (objectId > 0) {
f = objectId % SysProperties.LOB_FILES_PER_DIRECTORY;
name = File.separator + f + Constants.SUFFIX_LOBS_DIRECTORY + name;
objectId /= SysProperties.LOB_FILES_PER_DIRECTORY;
}
name = path + Constants.SUFFIX_LOBS_DIRECTORY + name;
return name;
}
private int getNewObjectId(DataHandler handler) throws SQLException {
String path = handler.getDatabasePath();
int objectId = 0;
while (true) {
String dir = getFileNamePrefix(path, objectId);
String[] list = getFileList(handler, dir);
int fileCount = 0;
boolean[] used = new boolean[SysProperties.LOB_FILES_PER_DIRECTORY];
for (int i = 0; i < list.length; i++) {
String name = list[i];
if (name.endsWith(Constants.SUFFIX_DB_FILE)) {
name = name.substring(name.lastIndexOf(File.separatorChar) + 1);
String n = name.substring(0, name.indexOf('.'));
int id;
try {
id = Integer.parseInt(n);
} catch (NumberFormatException e) {
id = -1;
}
if (id > 0) {
fileCount++;
used[id % SysProperties.LOB_FILES_PER_DIRECTORY] = true;
}
}
}
int fileId = -1;
if (fileCount < SysProperties.LOB_FILES_PER_DIRECTORY) {
for (int i = 1; i < SysProperties.LOB_FILES_PER_DIRECTORY; i++) {
if (!used[i]) {
fileId = i;
break;
}
}
}
if (fileId > 0) {
objectId += fileId;
invalidateFileList(handler, dir);
break;
} else {
if (objectId > Integer.MAX_VALUE / SysProperties.LOB_FILES_PER_DIRECTORY) {
// this directory path is full: start from zero
// (this can happen only theoretically,
// for example if the random number generator is broken)
objectId = 0;
} else {
// calculate the directory
// start with 1 (otherwise we don't know the number of directories)
// it doesn't really matter what directory is used, it might as well be random
// (but that would generate more directories):
// int dirId = RandomUtils.nextInt(
// SysProperties.LOB_FILES_PER_DIRECTORY - 1) + 1;
int dirId = (dirCounter++ / (SysProperties.LOB_FILES_PER_DIRECTORY - 1)) + 1;
objectId = objectId * SysProperties.LOB_FILES_PER_DIRECTORY;
objectId += dirId * SysProperties.LOB_FILES_PER_DIRECTORY;
}
}
}
return objectId;
}
private void invalidateFileList(DataHandler handler, String dir) {
SmallLRUCache cache = handler.getLobFileListCache();
if (cache != null) {
synchronized (cache) {
cache.remove(dir);
}
}
}
private String[] getFileList(DataHandler handler, String dir) throws SQLException {
SmallLRUCache cache = handler.getLobFileListCache();
String[] list;
if (cache == null) {
list = FileUtils.listFiles(dir);
} else {
synchronized (cache) {
list = (String[]) cache.get(dir);
if (list == null) {
list = FileUtils.listFiles(dir);
cache.put(dir, list);
}
}
}
return list;
}
public static ValueLob createBlob(InputStream in, long length, DataHandler handler) throws SQLException {
try {
long remaining = Long.MAX_VALUE;
boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null;
if (length >= 0 && length < remaining) {
remaining = length;
}
int len = getBufferSize(handler, compress, remaining);
byte[] buff = new byte[len];
len = IOUtils.readFully(in, buff, 0, len);
if (len <= handler.getMaxLengthInplaceLob()) {
byte[] small = new byte[len];
System.arraycopy(buff, 0, small, 0, len);
return ValueLob.createSmallLob(Value.BLOB, small);
}
ValueLob lob = new ValueLob(Value.BLOB, null);
lob.createFromStream(buff, len, in, remaining, handler);
return lob;
} catch (IOException e) {
throw Message.convertIOException(e, null);
}
}
private FileStoreOutputStream initLarge(DataHandler handler) throws IOException, SQLException {
this.handler = handler;
this.tableId = 0;
this.linked = false;
this.precision = 0;
this.small = null;
this.hash = 0;
String compressionAlgorithm = handler.getLobCompressionAlgorithm(type);
this.compression = compressionAlgorithm != null;
synchronized (handler) {
if (handler.getLobFilesInDirectories()) {
objectId = getNewObjectId(handler);
fileName = getFileNamePrefix(handler.getDatabasePath(), objectId) + ".temp.db";
} else {
objectId = handler.allocateObjectId(false, true);
fileName = handler.createTempFile();
}
tempFile = handler.openFile(fileName, "rw", false);
tempFile.autoDelete();
}
FileStoreOutputStream out = new FileStoreOutputStream(tempFile, handler, compressionAlgorithm);
return out;
}
private void createFromStream(byte[] buff, int len, InputStream in, long remaining, DataHandler handler)
throws SQLException {
try {
FileStoreOutputStream out = initLarge(handler);
boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null;
try {
while (true) {
precision += len;
out.write(buff, 0, len);
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -