Java BufferedReader BufferedWriter类源码解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

BufferedReader

像BufferedInputStream为FileInputStream提供了缓冲区一样,BufferedReader为InputStreamReader提供了缓冲区。一般情况下,所有的读取都是先从下层输入流读取到缓冲区,然后再从缓冲区读取到目标数组,除非出现要读取的长度超过了缓冲区大小且缓冲区没有有效数据和有效mark,可以直接从下层输入流读取字符。

来看一下这个类的头部注释:从字符输入流读取文本,缓冲字符来提供高效地读取字符、数组和行。缓冲区的大小可能是指定的或者是默认大小,默认大小对于大部分情况足够大了。一般来说,Reader的每一个读取请求引起一个下层的字节或者字符输入流的读取请求。所以建议用BufferedReader来包裹一个read方法花费大的Reader,比如FileReaders和InputStreamReaders。例如 BufferedReader in = new BufferedReader(new FileReader("foo.in"));将会缓存从特定文件来的输入。没有缓冲,每一次调用read()或者readLine()可能引起从文件读取字节,转换为字符后返回,这样效率不高。使用DataInputStreams进行文本输入的程序可以通过替换每一个DataInputStream为合适的BufferedReader进行局部化。

前面已经解释过为什么可以通过从文件读取大块内容存储到内存缓冲区来提高整体读取速度,BufferedReader就是字符缓冲输入流。它提供了一个字符数组来作为缓冲区,默认大小是8K,在构造时初始化,fill()方法可能会出现需要重新分配一个更大数组的情况。从构造函数和内部变量中,我们可以看到它也具有内部包裹的下层输入流并且需要是Reader及其子类。

    /**
     * 下层输入流
     */
    private Reader in;
    /**
     * 缓冲区
     */
    private char cb[];
    /**
     * 最后一个有效字符的位置
     */
    private int nChars;
    /**
     * 下一个要读取的字符位置
     */
    private int nextChar;

    private static final int INVALIDATED = -2;
    private static final int UNMARKED = -1;
    /**
     * 标记的位置,小于等于-1时代表不存在mark
     */
    private int markedChar = UNMARKED;
    private int readAheadLimit = 0; /* 只有markedChar > 0时是有效的 */

    /** 如果下一个字符是换行符,跳过它 */
    private boolean skipLF = false;

    /** 设定mark时skipLF的标记 */
    private boolean markedSkipLF = false;

    private static int defaultCharBufferSize = 8192;
    private static int defaultExpectedLineLength = 80;

    /**
     * 创建一个缓冲字符输入流使用一个指定大小的输入缓冲区
     */
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    /**
     * 创建一个缓冲字符输入流使用一个默认大小8K的输入缓冲区
     */
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }

ensureOpen这个内部方法的作用就是检查流有没有被关闭,标志是下层输入流为null

    private void ensureOpen() throws IOException {
        if (in == null)
            throw new IOException("Stream closed");
    }

无参数的read()读取单个字符,返回的是转为int的字符,返回是0-0xffff,因为存在两个字节的字符,到达文件末尾返回-1。这里存在一种情况,所有提到跳过换行符都是这个意思,上一次读取到的是'r'此时如果当前要读取的是'n'则直接跳过读取下一个字符,这是LRLF代表换行的情况,Windows平台下输入的文本很多是这种换行方式

    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                if (nextChar >= nChars) {
                    fill();//读取到最后一个有效字符时,从输入流读取字符到缓冲区
                    if (nextChar >= nChars)
                        return -1;
                }
                if (skipLF) {//如果下一个字符是换行符要跳过
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];//从缓冲区返回要读取的字符
            }
        }
    }

内部方法fill()填充输入缓冲区,如果mark是有效的需要将它加入考虑,只有在可被读取的字符被全部读取完后才会调用这个方法。该类中所有要从下层输入流读取字符到缓冲区都需要经过该方法。没有mark时,会直接将内容从数组头部开始尽可能填满缓冲区。存在mark时,如果从mark开始读取的内容超过了readAheadLimit的限制,mark失效。mark未超过范围时,检查readAheadLimit是否超过了缓冲区大小,没有超过时将mark开始的部分移动到缓冲区头部,然后读取数据尽可能填满缓冲区;mark超过了缓冲区大小,需要分配一个大小等于readAheadLimit的新数组,复制mark开始的内容,然后再读取字符,这是唯一引起分配新数组的方法。所有调用fill()的方法需要保证线程的安全性,我们可以看到这里分配新数组是简单赋值而不是CAS方法。

    private void fill() throws IOException {
        int dst;
        if (markedChar <= UNMARKED) {
            /* 没有mark */
            dst = 0;
        } else {
            /* 存在mark */
            int delta = nextChar - markedChar;
            if (delta >= readAheadLimit) {
                /* 从mark开始到读取位置的字节数超过了限制,将markedChar设为-2使它失效 */
                markedChar = INVALIDATED;
                readAheadLimit = 0;
                dst = 0;
            } else {
                if (readAheadLimit <= cb.length) {//能够保留的最大mark部分长度超过了缓冲区大小
                    /* 在当前缓冲区中洗牌 */
                    System.arraycopy(cb, markedChar, cb, 0, delta);//将从markedChar开始的所有有效字符移到缓冲区头部
                    markedChar = 0;
                    dst = delta;
                } else {
                    /* 重新分配缓冲区的大小来满足预读要求 */
                    char ncb[] = new char[readAheadLimit];//新大小是readAheadLimit
                    System.arraycopy(cb, markedChar, ncb, 0, delta);//将有效字符复制到新的数组
                    cb = ncb;
                    markedChar = 0;//因为从标记位开始的有效字符被移动到了头上,所以标记位从0开始
                    dst = delta;
                }
                nextChar = nChars = delta;
            }
        }

        int n;
        do {
            n = in.read(cb, dst, cb.length - dst);//从下层输入流读取字符
        } while (n == 0);
        if (n > 0) {
            nChars = dst + n;
            nextChar = dst;
        }
    }

int read(char cbuf[], int off, int len)读取字符到数组的一部分中,这个方法实现了Reader.read(char[], int, int)方法的一般约束。作为额外的便利,它会试图读取尽可能多的字符,通过重复调用下层输入流的read方法。这个重复的read会持续到一下一种情况为true:指定的字符数已经被读取,下层输入流的read方法返回-1说明到达了文件结束符,或者下层输入流的ready方法返回false说明后面的输入请求会阻塞。如果第一个下层输入流的read方法返回-1,这个方法会返回-1.否则这个方法返回实际读取的字符数。这个类的子类鼓励但不是必须去尝试用同样的方法读取尽可能多的字符。一般这个方法从这个输入流的字符缓冲区获取字符,如果需要的话从下层输入流来填充缓冲区。但是,如果缓冲区是空的,mark是无效的,请求的长度超过了缓冲区的长度,这个方法会从下层输入流直接读取字符到给定的数组。因此多余的BufferedReader不会进行不必要的复制。

    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {//同步操作
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }

            int n = read1(cbuf, off, len);//从缓冲区读取数据到数组,如果调用时缓冲区就已经没有有效数据从下层输入流读取
            if (n <= 0) return n;
            while ((n < len) && in.ready()) {
                int n1 = read1(cbuf, off + n, len - n);//循环尝试从下层输入流读取数据到缓冲区
                if (n1 <= 0) break;//下层输入流没有数据时返回
                n += n1;
            }
            return n;
        }
    }

    private int read1(char[] cbuf, int off, int len) throws IOException {
        if (nextChar >= nChars) {
            /* If the requested length is at least as large as the buffer, and
               if there is no mark/reset activity, and if line feeds are not
               being skipped, do not bother to copy the characters into the
               local buffer.  In this way buffered streams will cascade
               harmlessly.如果需要的长度超过了缓冲区大小,并且没有mark/reset活动,并且换行没有被跳过
               不要麻烦地将字符复制到本地缓冲区。这样可以避免没必要的损耗 */
            if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
                return in.read(cbuf, off, len);
            }
            fill();//读取到最后一个缓冲区内的有效字符时,从下层输入流读取字符到缓冲区
        }
        if (nextChar >= nChars) return -1;//下层输入流没有有效字符时返回-1
        if (skipLF) {
            skipLF = false;
            if (cb[nextChar] == '\n') {
                nextChar++;//下一个字符时换行符时跳过
                if (nextChar >= nChars)//如果跳过换行后有效字符不足则尝试从下层输入流中读取
                    fill();
                if (nextChar >= nChars)
                    return -1;
            }
        }
        int n = Math.min(len, nChars - nextChar);//读取的字符长度是len和缓冲区内有效字符的较小值
        System.arraycopy(cb, nextChar, cbuf, off, n);//从缓冲区复制字符到目标数组中
        nextChar += n;
        return n;//返回实际读取的字符数
    }

readLine读取一行文本。一行通过一个换行符或者一个回车或者一个回车跟着一个换行符来终止。返回一个包含行内容但是不包含行终止字符的字符串,如果已经到达流结尾的话返回null。

    public String readLine() throws IOException {
        return readLine(false);
    }

    String readLine(boolean ignoreLF) throws IOException {
        StringBuffer s = null;
        int startChar;

        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;

        bufferLoop:
            for (;;) {

                if (nextChar >= nChars)
                    fill();//缓冲区内没有有效字符时,从下层输入流读取字符到缓冲区
                if (nextChar >= nChars) { /* 到达了EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();//读取到了字符则返回字符串
                    else
                        return null;//没有读取到字符返回null
                }
                boolean eol = false;//缓冲区内有有效数据,还没有到达行结束
                char c = 0;
                int i;

                /* 如果有必要的话跳过一个 '\n'*/
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;//只有第一个字符是'\n'时才会跳过
                omitLF = false;

            charLoop:
                for (i = nextChar; i < nChars; i++) {//读取范围不超过缓冲区内的所有有效字符
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;//到达了行结束
                        break charLoop;//这里跳出了循环,所以nextChar依然在换行符或者回车的位置上
                    }
                }

                startChar = nextChar;
                nextChar = i;

                if (eol) {//到达了行结束
                    String str;
                    if (s == null) {//将新读取的内容添加到返回结果中
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;//此时nextChar向前移动一位,经过了之前的换行符或回车
                    if (c == '\r') {//如果行结束标记是回车,跳过下一个换行符对应LRLF这种情况
                        skipLF = true;
                    }
                    return str;
                }

                if (s == null)
                    s = new StringBuffer(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);//没有到达行结尾时,直接将新增内容添加到返回的字符串中
            }
        }
    }

skip跳过字符的操作是通过读取字符到缓冲区然后改变缓冲区的当前读取位置下标nextChar实现的,跳过的字符数为参数n和缓冲区内剩余有效字符的较小值

    public long skip(long n) throws IOException {
        if (n < 0L) {
            throw new IllegalArgumentException("skip value is negative");
        }
        synchronized (lock) {
            ensureOpen();
            long r = n;
            while (r > 0) {
                if (nextChar >= nChars)
                    fill();//缓冲区内没有有效字符时尝试从下层输入流读取字符到缓冲区
                if (nextChar >= nChars) /* 到达了EOF */
                    break;
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;//调用时第一个有效字符时'\n'时跳过它
                    }
                }
                long d = nChars - nextChar;//当前缓冲区内的有效字符个数
                if (r <= d) {//缓冲区内的有效字符数超过了要跳过的字符数,直接增加nextChar
                    nextChar += r;
                    r = 0;
                    break;
                }
                else {//缓冲区内数据不足时,nextChar移动到最后一个有效字符的位置
                    r -= d;
                    nextChar = nChars;
                }
            }
            return n - r;//返回跳过的字符数
        }
    }

ready告知这个流是否准备完读取。一个字符缓冲流在缓冲区非空或者下层输入流准备完时是准备完成的状态。

    public boolean ready() throws IOException {
        synchronized (lock) {
            ensureOpen();

            /*
             * 如果一个新行需要跳过并且下一个要读取的字符是新行的字符,立刻跳过它
             */
            if (skipLF) {
                /* 
                 * in.ready()仅当流中的下一个读取不会被阻塞时会返回true
                 */
                if (nextChar >= nChars && in.ready()) {
                    fill();//缓冲区没有有效数据且下层输入流准备完时进行读取
                }
                if (nextChar < nChars) {
                    if (cb[nextChar] == '\n')//如果缓冲区内第一个有效字符是'\n'则跳过
                        nextChar++;
                    skipLF = false;
                }
            }
            return (nextChar < nChars) || in.ready();
        }
    }

mark/reset操作只需要操作内部变量就行了,另外readAheadLimit是在mark时输入的,当这个限制超过了缓冲区大小时会引起新分配一个不小于这个大小的缓冲区,因此要小心输入一个大值。

    public void mark(int readAheadLimit) throws IOException {
        if (readAheadLimit < 0) {
            throw new IllegalArgumentException("Read-ahead limit < 0");
        }
        synchronized (lock) {
            ensureOpen();
            this.readAheadLimit = readAheadLimit;
            markedChar = nextChar;
            markedSkipLF = skipLF;
        }
    }

    public void reset() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (markedChar < 0)
                throw new IOException((markedChar == INVALIDATED)
                                      ? "Mark invalid"
                                      : "Stream not marked");
            nextChar = markedChar;
            skipLF = markedSkipLF;
        }
    }

close方法会关闭下层输入流并将in和cb设为null,所以关闭之后所有操作都会抛出异常,即使只涉及缓冲区,因为缓冲区被清除了。

    public void close() throws IOException {
        synchronized (lock) {
            if (in == null)
                return;
            try {
                in.close();
            } finally {
                in = null;
                cb = null;
            }
        }
    }

最后lines()方法是JDK1.8引入了流式操作,具体流式操作的内容以后再讲吧,这里就翻译下注释:返回一个流,元素是从这个BufferedReader读取的行。流是延迟构成的,比如读取只发生在终止流操作中。reader在执行终止流操作期间不能被操作,否则,终止流的结果会不确定。如果访问下层BufferedReader时抛出了IOException,它会被包装为UncheckedIOException由Stream抛出。这个方法如果调用在一个关闭的BufferedReader上会返回stream,任何请求从关闭后的BufferedReader中读取的请求都会引起抛出UncheckedIOException。

BufferedWriter

BufferedWriter是对应的缓冲字符输出流。将文本写到字符输出流,缓冲字符来提供单个字符、数组、字符串的高效写入。缓冲区的大小可能是指定的,或者默认大小8K,默认大小对于大多数情况足够大。

newLine()方法使用平台定义的行分隔符line.separator。不是所有平台使用'n'来表示行结束。调用这个方法来结束每个输入行比直接写一个'n'字符更好。

一般来说,一个Writer发送它的输出直接到下层的字符流或者字节流。除非要求立刻输出,否则建议用BufferedWriter来包装一个write()操作花费大的Writer,比如FileWriters和OutputStreamWriters。例如,PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));会缓冲PrintWriter到一个文件的输出。没有缓冲,每一次调用print()方法会引起字符被转换成字节然后直接写入到文件,这样效率低。

含有一个底层输入流Writer,在构造时提供,并且Writer的锁对象会是它自身。

    /**
     * 下层输出流
     */
    private Writer out;
    /**
     * 缓冲区
     */
    private char cb[];
    /**
     * 缓冲区大小
     */
    private int nChars;
    /**
     * 下一个有效字符
     */
    private int nextChar;

    private static int defaultCharBufferSize = 8192;

    /**
     * 行分隔符,在这个流创建时行分隔符的值
     */
    private String lineSeparator;

    /**
     * 创建一个使用默认大小8K输出缓冲区的缓冲字符输出流
     */
    public BufferedWriter(Writer out) {
        this(out, defaultCharBufferSize);
    }

    /**
     * 创建一个使用指定大小sz输出缓冲区的缓冲字符输出流
     */
    public BufferedWriter(Writer out, int sz) {
        super(out);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.out = out;
        cb = new char[sz];
        nChars = sz;
        nextChar = 0;

        lineSeparator = java.security.AccessController.doPrivileged(
            new sun.security.action.GetPropertyAction("line.separator"));
    }

ensureOpen内部方法用于确认这个流是否被关闭,标志是out为null

    private void ensureOpen() throws IOException {
        if (out == null)
            throw new IOException("Stream closed");
    }

write(int c)写入单个字符,在缓冲区满了时需要将缓冲区内的字符写入到下层输入流

    public void write(int c) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (nextChar >= nChars)
                flushBuffer();//如果缓冲区已满,将缓冲区内的字符写入到下层输出流
            cb[nextChar++] = (char) c;//将c复制到数组中
        }
    }

flushBuffer将这个输出缓冲区刷新到下层字符流中,但不刷新下层流本身。这个类不是private,所以可以被PrintStream调用

    void flushBuffer() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (nextChar == 0)
                return;
            out.write(cb, 0, nextChar);//下层输出流输出缓冲区内的所有有效字符
            nextChar = 0;
        }
    }

write(char cbuf[], int off, int len)写入一个字符数组的一部分。一般来说这个方法从数组存储字符到流的缓冲区,如果需要的话刷新缓冲区到下层输出流。如果请求的长度超过了缓冲区大小,这个方法会刷新缓冲区并将字符直接写入到下层输出流。因此多余的BufferedWriter不会不必要的复制数据。

    public void write(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return;
            }

            if (len >= nChars) {
                /* 如果请求长度超过了输出缓冲区大小,刷新缓冲区直接写数据。这样缓冲输出流是级联无害的 */
                flushBuffer();
                out.write(cbuf, off, len);//直接使用下层输出流的write方法
                return;
            }

            int b = off, t = off + len;
            while (b < t) {
                int d = min(nChars - nextChar, t - b);
                System.arraycopy(cbuf, b, cb, nextChar, d);//复制字符到缓冲区,长度为缓冲区剩余空间和剩余要输入字符的较小值
                b += d;
                nextChar += d;
                if (nextChar >= nChars)
                    flushBuffer();//缓冲区满了时刷新
            }
        }
    }

write(String s, int off, int len)写一个字符串的一部分,一定会经过复制到缓冲区这个过程。需要注意的是,如果len参数是负数,没有字符会被写入。这个和java.io.Writer.write(java.lang.String,int,int)超类是相反的,它要求抛出IndexOutOfBoundsException

    public void write(String s, int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();

            int b = off, t = off + len;
            while (b < t) {
                int d = min(nChars - nextChar, t - b);
                s.getChars(b, b + d, cb, nextChar);
                b += d;
                nextChar += d;
                if (nextChar >= nChars)
                    flushBuffer();
            }
        }
    }

在上面的写入方法中,我们注意到有个min方法来获得两个数中的最小值,这里的min是自身的内部方法,避免加载java.lang.Math在用尽文件描述符和尝试打印堆栈追踪时

    private int min(int a, int b) {
        if (a < b) return a;
        return b;
    }

newLine()写一个行分隔符,行分隔符通过系统属性line.separator来定义,不一定是一个'n'字符

    public void newLine() throws IOException {
        write(lineSeparator);
    }

flush刷新流,同时会刷新下层输入流

    public void flush() throws IOException {
        synchronized (lock) {
            flushBuffer();
            out.flush();//下层输入流的刷新在这里调用
        }
    }

close关闭这个输出流和下层输出流,重复关闭没有效果。关闭后由于out和cb都为null,所以一切写入操作都会抛出异常。这里关闭out也是使用了JDK7引入的try-with-resources写法

    public void close() throws IOException {
        synchronized (lock) {
            if (out == null) {//out为null说明已经被关闭了
                return;
            }
            try (Writer w = out) {//out随着这个代码块结束而关闭
                flushBuffer();//先做一次刷新,避免有没有输入到下层输出流的数据
            } finally {
                out = null;
                cb = null;//缓冲区被移除
            }
        }
    }
相关文章
|
16天前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
28 4
|
23天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
60 2
|
27天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
77 6
|
8天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
14天前
|
数据采集 存储 Web App开发
Java爬虫:深入解析商品详情的利器
在数字化时代,信息处理能力成为企业竞争的关键。本文探讨如何利用Java编写高效、准确的商品详情爬虫,涵盖爬虫技术概述、Java爬虫优势、开发步骤、法律法规遵守及数据处理分析等内容,助力电商领域市场趋势把握与决策支持。
|
17天前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
39 8
|
18天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
24天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java的集合框架中,Set接口以其“无重复”特性著称。本文解析了Set的实现原理,包括HashSet和TreeSet的不同数据结构和算法,以及如何通过示例代码实现最佳实践。选择合适的Set实现类和正确实现自定义对象的hashCode()和equals()方法是关键。
26 4
|
27天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
34 3
|
26天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。