我正在开发一个功能,我应该显示一个巨大的文本文件。所以我想一旦文件超过一定大小,我就会尝试将文件映射到磁盘上。
文本至少是不可变的,这应该比编写支持相同尺寸的完整编辑器更容易。
这是我到目前为止所拥有的:
public class CharBufferContent implements AbstractDocument.Content {
private final CharBuffer charBuffer;
private final int length;
public CharBufferContent(CharBuffer charBuffer) {
this.charBuffer = charBuffer;
length = charBuffer.length();
}
public Position createPosition(int offset) throws BadLocationException {
return new ImmutablePosition(offset);
}
public int length() {
return length;
}
public UndoableEdit insertString(int where, String string)
throws BadLocationException {
throw new UnsupportedOperationException("Document is immutable");
}
public UndoableEdit remove(int where, int nItems) throws BadLocationException {
throw new UnsupportedOperationException("Document is immutable");
}
public String getString(int where, int length) throws BadLocationException {
if (where < 0 || where + length > this.length) {
throw new BadLocationException("Invalid range", this.length);
}
char[] out = new char[length];
charBuffer.position(where);
charBuffer.get(out);
return new String(out);
}
public void getChars(int where, int length, Segment segment)
throws BadLocationException {
if (where < 0 || where + length > this.length) {
throw new BadLocationException("Invalid range", this.length);
}
// This will be inefficient, but I'm just trying to get it working first.
segment.array = new char[length];
charBuffer.position(where);
charBuffer.get(segment.array, 0, length);
segment.offset = 0;
segment.count = length;
}
private static class ImmutablePosition implements Position {
private final int offset;
private ImmutablePosition(int offset) {
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
}
}
我编写了一个小测试程序,它只使用内存缓冲区来测试它:
public class Test implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Test());
}
public void run() {
CharBuffer charBuffer = CharBuffer.wrap("This is a fairly simple test, " +
"so nothing should go wrong, right?\n");
AbstractDocument.Content content = new CharBufferContent(charBuffer);
final Document document = new PlainDocument(content);
JTextArea text = new JTextArea(document);
text.setEditable(false);
JScrollPane textScroll = new JScrollPane(text);
textScroll.setPreferredSize(new Dimension(600, 500));
JFrame frame = new JFrame("Test");
frame.setLayout(new BorderLayout());
frame.add(textScroll, BorderLayout.CENTER);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
当我运行它时,窗口显示“T”。在调试器中,我可以看到 Swing 仅调用 length()
和 getChars()
。每次调用 getChars()
时都会有 where == 0
和 length == 1
。因此,它只显示一个字符是有道理的,但是 Swing 调用我的代码并且只要求第一个字符似乎很奇怪,即使我可以看到 length()
返回文本的完整长度。
当我使用 StringContent 作为实现运行相同的测试时,将使用文档的完整长度调用其中的 getChars()
。
这个 API 中没有太多看起来可能出错的地方,所以我很困惑。
这是怎么回事?
最佳答案
我不太熟悉文档 API,但我的理解是 AbstractDocument.Content
预计不会预先配备自己的文本。 AbstractDocument 不知道在这种情况下该怎么办。如果您尝试使用 StringContent,您会看到同样的问题:
content = new StringContent();
content.insertString(0, "some text");
final AbstractDocument document = new PlainDocument(content);
这只显示前导“s”。显然,AbstractDocument 在文档末尾为插入符建模了一个额外的有效位置,并且它期望其内容的长度 +1 其实际长度来支持这一点,我相信这是第一个字符确实如此的事实的起源出现。
如果您在创建文档后调用StringContent.insertString
,它似乎可以显示所有文本,但我认为这可能只是运气。如果以这种方式插入的字符串包含换行符,它将始终忽略它们,因为它们没有按照要求建模为单独的文档元素。
我认为实现这项工作的唯一方法是直接实现 Document,而不是 AbstractDocument.Content。这是一个更复杂的界面,但这确实有效:
class CharBufferDocument implements Document {
private final CharBuffer charBuffer;
private final int length;
private final int[] lineOffsets;
public CharBufferDocument(CharBuffer charBuffer) {
this.charBuffer = charBuffer;
charBuffer.position(0);
length = charBuffer.length();
int[] lineOffsets = new int[] { 0 };
int lineCount = 1;
for (int i = 0; i < length; i++) {
char c = charBuffer.get(i);
if (c == '\n') {
if (lineCount == lineOffsets.length) {
if (lineCount == Integer.MAX_VALUE) throw new OutOfMemoryError();
int newLength = (int)Math.min(lineCount * 2L, Integer.MAX_VALUE);
lineOffsets = Arrays.copyOf(lineOffsets, newLength);
}
lineOffsets[lineCount++] = i + 1;
}
}
lineOffsets = Arrays.copyOf(lineOffsets, lineCount);
this.lineOffsets = lineOffsets;
}
@Override
public int getLength() {
return length;
}
@Override
public void addDocumentListener(DocumentListener listener) {}
@Override
public void removeDocumentListener(DocumentListener listener) {}
@Override
public void addUndoableEditListener(UndoableEditListener listener) {}
@Override
public void removeUndoableEditListener(UndoableEditListener listener) {}
@Override
public void putProperty(Object key, Object value) {}
@Override
public Object getProperty(Object key) {
return null;
}
@Override
public void remove(int offs, int len) throws BadLocationException {
throw new UnsupportedOperationException("Document is immutable");
}
@Override
public void insertString(int offset, String str, AttributeSet a)
throws BadLocationException {
throw new UnsupportedOperationException("Document is immutable");
}
@Override
public String getText(int offset, int length) throws BadLocationException {
char[] out = new char[length];
charBuffer.position(offset);
charBuffer.get(out);
return new String(out);
}
@Override
public void getText(int offset, int length, Segment segment) throws BadLocationException {
segment.array = new char[length];
charBuffer.position(offset);
charBuffer.get(segment.array);
segment.offset = 0;
segment.count = length;
}
@Override
public Position getStartPosition() {
return createPosition(0);
}
@Override
public Position getEndPosition() {
return createPosition(getLength());
}
@Override
public Position createPosition(int offset) {
return new ImmutablePosition(offset);
}
private final Element rootElement = new Element() {
@Override
public Document getDocument() {
return CharBufferDocument.this;
}
@Override
public Element getParentElement() {
return null;
}
@Override
public String getName() {
return "root";
}
@Override
public AttributeSet getAttributes() {
return null;
}
@Override
public int getStartOffset() {
return 0;
}
@Override
public int getEndOffset() {
return getLength();
}
@Override
public int getElementIndex(int offset) {
// binary search for the line that contains offset
int low = 0;
int high = lineOffsets.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midLineStart = lineOffsets[mid];
int midLineEnd = (mid + 1 < lineOffsets.length) ? (lineOffsets[mid + 1] - 1) : getLength();
if (offset < midLineStart) {
high = mid - 1;
} else if (offset > midLineEnd) {
low = mid + 1;
} else {
return mid;
}
}
return 0;
}
@Override
public int getElementCount() {
return lineOffsets.length;
}
@Override
public Element getElement(int index) {
return createLineElement(
lineOffsets[index],
(index + 1 < lineOffsets.length) ? (lineOffsets[index + 1] - 1) : getLength()
);
}
@Override
public boolean isLeaf() {
return false;
}
};
private Element createLineElement(final int start, final int end) {
return new Element() {
@Override
public Document getDocument() {
return CharBufferDocument.this;
}
@Override
public Element getParentElement() {
return CharBufferDocument.this.getDefaultRootElement();
}
@Override
public String getName() {
return "line"; // XXX: Does the name matter?
}
@Override
public AttributeSet getAttributes() {
return null;
}
@Override
public int getStartOffset() {
return start;
}
@Override
public int getEndOffset() {
return end;
}
@Override
public int getElementIndex(int offset) {
return -1;
}
@Override
public int getElementCount() {
return 0;
}
@Override
public Element getElement(int index) {
return null;
}
@Override
public boolean isLeaf() {
return true;
}
};
}
@Override
public Element getDefaultRootElement() {
return rootElement;
}
@Override
public Element[] getRootElements() {
return new Element[] { getDefaultRootElement() };
}
@Override
public void render(Runnable r) {
r.run();
}
private static class ImmutablePosition implements Position {
private final int offset;
private ImmutablePosition(int offset) {
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
}
}
关于java - Swing 只读取 AbstractDocument.Content 的第一个字符。我究竟做错了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20174827/