当前位置: 首页 > article >正文

PipedInputStream和PipedOutputStream的源码分析和使用方法详细分析

一、PipedOutputStream生产者源码——向PipedInputStream消费者中的缓冲区byte[]数组写入字节数据的输出Stream生产者package java.io; import java.io.*; public class PipedOutputStream extends OutputStream { //与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者 private PipedInputStream sink; //构造函数 public PipedOutputStream(PipedInputStream snk) throws IOException { connect(snk);//调用connect()函数来改变PipedInputStream 消费者中一些变量的值 } //构造函数 public PipedOutputStream() { } //线程同步函数用来改变将要关联的PipedInputStream 消费者中一些变量的值 public synchronized void connect(PipedInputStream snk) throws IOException { if (snk null) { throw new NullPointerException();//如果将要关联的PipedInputStream 消费者为null抛出NullPointerException } else if (sink ! null || snk.connected) { //如果与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者!null或者将要关联的PipedInputStream 消费者的boolean connected变量为true则抛出IOException throw new IOException(Already connected); } sink snk;//将这个PipedOutputStream生产者与这个PipedInputStream 消费者相关联 snk.in -1;//改变PipedInputStream 消费者中的变量int in-1 snk.out 0;//改变PipedInputStream 消费者中的变量int out0 snk.connected true;//改变PipedInputStream 消费者中的变量boolean connectedtrue } //向与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者的缓冲区byte[]数组写入1个字节 public void write(int b) throws IOException { if (sink null) { //如果与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者 null抛出IOException throw new IOException(Pipe not connected); } sink.receive(b);//最终调用的是这个相关联的 PipedInputStream 消费者的receive(int b)函数 } //向与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者的缓冲区byte[]数组写入byte[]数组b的[off,offlen)左闭右开不包括offlen索引位置的字节 public void write(byte b[], int off, int len) throws IOException { if (sink null) { //如果与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者 null抛出IOException throw new IOException(Pipe not connected); } else if (b null) { throw new NullPointerException();//如果byte[]数组bnull抛出一个NullPointerException } else if ((off 0) || (off b.length) || (len 0) || ((off len) b.length) || ((off len) 0)) {//byte[]数组b的[off,offlen)左闭右开索引位置是否有越界的检查 throw new IndexOutOfBoundsException();//越界的话抛出一个IndexOutOfBoundsException } else if (len 0) { return;//如果len0结束本次函数调用 } sink.receive(b, off, len);//最终调用的是这个相关联的 PipedInputStream 消费者的receive(byte b[], int off, int len)函数 } //线程同步函数使用notifyAll()函数唤醒所有与这个PipedOutputStream生产者相关联的 PipedInputStream 消费者线程这个消费者可以绑定1~多个线程 public synchronized void flush() throws IOException { if (sink ! null) { synchronized (sink) { sink.notifyAll(); } } } //关闭这个PipedOutputStream生产者这个PipedOutputStream生产者不能再向与它相关联的PipedInputStream消费者中的缓冲区byte[]数组写入字节数据 public void close() throws IOException { if (sink ! null) { sink.receivedLast(); } } }二、PipedInputStream消费者源码——从自己的缓冲区byte[]数组读取字节数据的输入Stream消费者package java.io; public class PipedInputStream extends InputStream { //标记符true表示与这个 PipedInputStream 消费者相关联的PipedOutputStream生产者已经关闭反之反之 boolean closedByWriter false; //标记符true表示当前这个 PipedInputStream 消费者已经关闭了反之反之 volatile boolean closedByReader false; //标记符true表示与这个 PipedInputStream 消费者相关联的PipedOutputStream生产者已经持有了这个PipedInputStream 消费者对象或者叫已经连接上了反之反之 boolean connected false; Thread readSide;//当前消费的线程 Thread writeSide;//当前生产者的线程 //默认的PipedInputStream 消费者的缓冲区byte[]数组的长度 private static final int DEFAULT_PIPE_SIZE 1024; //PipedInputStream 消费者的缓冲区byte[]数组 protected byte buffer[]; //缓冲区byte[]数组的写指针 protected int in -1; //缓冲区byte[]数组的读指针 protected int out 0; //构造函数 public PipedInputStream(PipedOutputStream src) throws IOException { this(src, DEFAULT_PIPE_SIZE);//缓冲区byte[]数组的长度使用默认值1024 } //构造函数 public PipedInputStream(PipedOutputStream src, int pipeSize) throws IOException { initPipe(pipeSize);//缓冲区byte[]数组的长度使用指定的长度 //最终还是调用PipedOutputStream生产者的connect()函数并把自身对象this传递进去然后在PipedOutputStream生产者的connect()函数中改变自己的3个变量int in-1、int out0、boolean connectedtrue connect(src); } //构造函数缓冲区byte[]数组的长度使用默认值1024 public PipedInputStream() { initPipe(DEFAULT_PIPE_SIZE); } //构造函数缓冲区byte[]数组的长度使用指定的长度 public PipedInputStream(int pipeSize) { initPipe(pipeSize); } //初始化缓冲区byte[]数组 private void initPipe(int pipeSize) { if (pipeSize 0) { throw new IllegalArgumentException(Pipe Size 0); } buffer new byte[pipeSize]; } public void connect(PipedOutputStream src) throws IOException { src.connect(this); //最终还是调用PipedOutputStream生产者的connect()函数并把自身对象this传递进去然后在PipedOutputStream生产者的connect()函数中改变自己的3个变量int in-1、int out0、boolean connectedtrue } //线程同步函数该函数只被PipedOutputStream生产者的write(int b)函数调用 protected synchronized void receive(int b) throws IOException { checkStateForReceive();//检查PipedInputStream 消费者的状态 writeSide Thread.currentThread();//当前执行该函数的线程就是生产者线程 if (in out) //如果缓冲区byte[]数组的读指针缓冲区byte[]数组的写指针唤醒所有消费者线程自己这个生产者线程调用wait(1000)函数 awaitSpace(); if (in 0) {//缓冲区byte[]数组的写指针0时设置缓冲区byte[]数组的写指针0缓冲区byte[]数组的读指针0 in 0; out 0; } buffer[in] (byte)(b 0xFF);//向缓冲区的写指针位置写入1个字节 if (in buffer.length) { in 0;//如果缓冲区满了设置缓冲区的写指针0 } } //线程同步函数该函数只被PipedOutputStream生产者的write(byte b[], int off, int len)函数调用 synchronized void receive(byte b[], int off, int len) throws IOException { checkStateForReceive();//检查PipedInputStream 消费者的状态 writeSide Thread.currentThread();//当前执行该函数的线程就是生产者线程 int bytesToTransfer len;//生产者线程要写入到缓冲区byte[]数组中的字节总量 while (bytesToTransfer 0) { if (in out) //如果缓冲区byte[]数组的读指针缓冲区byte[]数组的写指针唤醒所有消费者线程自己这个生产者线程调用wait(1000)函数 awaitSpace(); int nextTransferAmount 0;//本次生产者线程要写入到缓冲区byte[]数组中的字节数量 if (out in) { //如果缓冲区的读指针缓冲区的写指针本次要写入到缓冲区byte[]数组中的字节数量缓冲区的长度-缓冲区的写指针 nextTransferAmount buffer.length - in; } else if (in out) { if (in -1) { in out 0; //如果缓冲区的读指针out 缓冲区的写指针in并且缓冲区的写指针in-1先设置缓冲区的读out、写in指针0本次要写入到缓冲区byte[]数组中的字节数量缓冲区的长度 nextTransferAmount buffer.length - in; } else { //如果缓冲区的读指针out 缓冲区的写指针in并且缓冲区的写指针in-1本次要写入到缓冲区byte[]数组中的字节数量读指针out-写指针in nextTransferAmount out - in; } } //本次生产者线程要写入到缓冲区byte[]数组中的字节数量最多为len下次为len-本次写入到缓冲区byte[]数组中的字节数量也就是每次写入的基于len个字节循环递减上一次写入的 if (nextTransferAmount bytesToTransfer) nextTransferAmount bytesToTransfer; assert(nextTransferAmount 0); System.arraycopy(b, off, buffer, in, nextTransferAmount);//向缓冲区byte[]数组的[in,innextTransferAmount)索引位置写入byte[]数组b中[off,offnextTransferAmount)索引位置的字节都是左闭右开。 bytesToTransfer - nextTransferAmount;//每一次都基于len个字节循环递减本次写入到缓冲区byte[]数组中的字节数量nextTransferAmount off nextTransferAmount;//将下次要从byte[]数组b中取字节的起始索引的位置偏移量本次写入到缓冲区byte[]数组中的字节数量nextTransferAmount in nextTransferAmount;//将缓冲区的写指针in本次写入到缓冲区byte[]数组中的字节数量nextTransferAmount if (in buffer.length) { in 0;//如果缓冲区的写指针in 缓冲区byte[]数组的长度设置缓冲区的写指针in0 } } } //检查PipedInputStream 消费者的状态 private void checkStateForReceive() throws IOException { if (!connected) { throw new IOException(Pipe not connected); } else if (closedByWriter || closedByReader) { throw new IOException(Pipe closed); } else if (readSide ! null !readSide.isAlive()) { throw new IOException(Read end dead); } } //如果缓冲区byte[]数组的读指针缓冲区byte[]数组的写指针唤醒所有消费者线程自己这个生产者线程调用wait(1000)函数 private void awaitSpace() throws IOException { while (in out) { checkStateForReceive(); /* full: kick any waiting readers */ notifyAll(); try { wait(1000); } catch (InterruptedException ex) { throw new java.io.InterruptedIOException(); } } } //关闭与这个 PipedInputStream 消费者相关联的PipedOutputStream生产者 synchronized void receivedLast() { closedByWriter true; notifyAll();//唤醒所有消费者线程 } //线程同步函数消费者线程每次从缓冲区byte[]数组中读取1个字节 public synchronized int read() throws IOException { if (!connected) {//检查标记符connected如果为false抛出IOException throw new IOException(Pipe not connected); } else if (closedByReader) {//检查标记符closedByReader如果为true抛出IOException throw new IOException(Pipe closed); } else if (writeSide ! null !writeSide.isAlive() !closedByWriter (in 0)) { //检查当前这个PipedInputStream 消费者对象中引用的生产者线程和生产者线程的状态如果和标记符closedByWriter还有缓冲区byte[]数组的写指针in不能对应的话抛出一个IOException throw new IOException(Write end dead); } readSide Thread.currentThread();//当前执行该函数的线程就是消费者线程 int trials 2;//这是一个多次检测的策略变量防止生产者线程没有关闭了与这个 PipedInputStream 消费者相关联的PipedOutputStream生产者时便抛出IOException //in-1的情况有种 //①、生产者线程还没有向缓冲区byte[]数组中写任何字节 //②、消费者线程从缓冲区byte[]数组中读完字节byte数据以后读指针out写指针in那么当前消费者线程会设置写指针in-1 //③、消费者线程执行PipedInputStream 的close()函数后关闭了这个 PipedInputStream 消费者 while (in 0) { if (closedByWriter) { /* closed by writer, return EOF */ return -1; } if ((writeSide ! null) (!writeSide.isAlive()) (--trials 0)) { //多个消费者线程从缓冲区byte[]数组中读的时候并且前一个消费者线程已经把缓冲区byte[]数组中写入的字节读完了并且前一个线程设置了写指针in-1生产者线程也关闭了与这个 PipedInputStream 消费者相关联的PipedOutputStream生产者时抛出一个IOException throw new IOException(Pipe broken); } /* might be a writer waiting */ notifyAll();//此处的目的是为了唤醒所有生产者线程 try { wait(1000); } catch (InterruptedException ex) { throw new java.io.InterruptedIOException(); } } int ret buffer[out] 0xFF;//获取缓冲区byte[]数组中读指针out索引位置的字节,并且将读指针out1 if (out buffer.length) { out 0;//如果读指针out缓冲区byte[]数组的长度设置读指针out0 } if (in out) { /* now empty */ in -1;//如果消费者线程从缓冲区byte[]数组中读完字节byte数据以后读指针out写指针in那么当前消费者线程会设置写指针in-1 } return ret; } //线程同步函数如果缓冲区byte[]数组中有足够多的字节的话数量len消费者线程每次从缓冲区byte[]数组中读取len个字节放到byte[]数组b的[off, offlen)索引位置左闭右开不包括offlen //如果缓冲区byte[]数组中字节的数量len个比如有in写指针-out读指针个消费者线程每次从缓冲区byte[]数组中读取in-out个字节放到byte[]数组b的[off, offin-out)索引位置左闭右开不包括offin-out public synchronized int read(byte b[], int off, int len) throws IOException { if (b null) { throw new NullPointerException(); } else if (off 0 || len 0 || len b.length - off) {//byte[]数组b的[off,offlen)左闭右开索引位置是否有越界的检查 throw new IndexOutOfBoundsException();//越界的话抛出一个IndexOutOfBoundsException } else if (len 0) { return 0;//如果len0返回0 } /* possibly wait on the first character */ int c read();//先调用read()函数试探性从缓冲区byte[]数组中读1个字节 if (c 0) { return -1;//如果试探性的从缓冲区byte[]数组中都读不到1个字节返回-1 } b[off] (byte) c;//把试探性从缓冲区byte[]数组中读到的第1个字节放到byte[]数组b的off索引位置 int rlen 1;//累计从缓冲区byte[]数组中读到的所有字节数量 while ((in 0) (len 1)) { int available;//本次执行System.arraycopy()函数可以从缓冲区byte[]数组中读到byte[]数组b中的字节数量 if (in out) { available Math.min((buffer.length - out), (in - out)); } else { available buffer.length - out; } // A byte is read beforehand outside the loop if (available (len - 1)) {//减掉试探性从缓冲区byte[]数组中读到的第1个字节 available len - 1; } System.arraycopy(buffer, out, b, off rlen, available); out available;//读指针outSystem.arraycopy()函数从缓冲区byte[]数组中读到byte[]数组b中的字节数量 rlen available;//累计从缓冲区byte[]数组中读到的所有字节数量 System.arraycopy()函数从缓冲区byte[]数组中读到byte[]数组b中的字节数量 len - available;//len - System.arraycopy()函数从缓冲区byte[]数组中读到byte[]数组b中的字节数量 if (out buffer.length) { out 0;//如果读指针out缓冲区byte[]数组的长度设置读指针out0 } if (in out) { /* now empty */ in -1;//如果消费者线程从缓冲区byte[]数组中读完字节byte数据以后读指针out写指针in那么当前消费者线程会设置写指针in-1 } } return rlen;//返回累计从缓冲区byte[]数组中读到的所有字节数量 } //线程同步函数返回缓冲区byte[]数组中可以被消费者线程读取的字节数量 public synchronized int available() throws IOException { if(in 0) return 0; else if(in out) return buffer.length; else if (in out) return in - out; else return in buffer.length - out; } //关闭这个 PipedInputStream 消费者其实就是设置标记符closedByReadertrue 设置写指针in-1 public void close() throws IOException { closedByReader true; synchronized (this) { in -1; } } }三、1个线程向PipedOutputStream生产者写字节数据1个线程从PipedInputStream消费者读取字节数据的过程3.1、非循环直接写和非循环直接读package com.chelong.StreamAndReader; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class PipedTest { public static void main(String[] args) throws IOException { final PipedOutputStream output new PipedOutputStream(); final PipedInputStream input new PipedInputStream(output); Thread thread1 new Thread(new Runnable() { Override public void run() { try { output.write(Hello world, pipe!.getBytes());//write()函数是阻塞的 } catch (IOException e) { } } }); Thread thread2 new Thread(new Runnable() { Override public void run() { try { int data -1; while ((data input.read()) ! -1) {//read()函数是阻塞的 System.out.print((char) data); } } catch (IOException e) { } } }); thread1.start(); thread2.start(); } }程序运行结果如下所示main线程构造PipedOutputStream生产者和PipedInputStream消费者的过程如下向PipedOutputStream生产者写字节数据的生产者线程的执行过程如下从PipedInputStream消费者读取字节数据的消费者线程的执行过程如下3.1.1、非循环直接写和非循环直接读时1个生产者线程和1个消费者线程处理数据的过程Java 语言定义了 6 种线程状态, 在任意一个时间点, 一个线程只能有且只有其中的一种状态, 这 6 种状态分别如下这 6 种线程状态的简单介绍如下所示JVM运行时内存结构主要包含了五个部分程序计数器 PC寄存器、 JVM栈、Native方法栈、堆、 方法区。如下图所示图中红色部分是线程私有区域进入这个区域的数据不会出现线程竞争的关系。而绿色区域中的数据则被所有线程共享其中Java堆中存放的是大量对象方法区中存放class信息、常量、静态变量等数据。每个线程的线程栈中会存放函数方法的描述符成员本地变量等函数方法在线程栈中会通过压栈和弹栈来执行除了8种byte、short、int、long、float、double、boolean、char基本的数据类型存储在线程栈中以外其余的引用数据类型对象都存储在堆中然后通过引用将堆中的对象和线程栈中的变量关联起来也可以叫线程栈中的引用指向堆中的对象。那么当使用者执行3.1中的代码时1个生产者线程和1个消费者线程处理数据的过程如下①、main线程初始化一个缓冲区byte[]数组长度为1024默认值然后生产者线程通过不断的压栈来完成函数之间的调用最终执行PipedInputStream.class::receive(byte b[], int off, int len)函数来对缓冲区byte[]数组进行填充如下所示②、当生产者线程填充完缓冲区之后写指针变量int in17读指针变量int out0Thread writeSide 当前这个生产者线程Thread对象生产者线程会把自己线程栈中修改的变量最终刷新到堆中PipedInputStream对象中以确保其它消费者线程的线程栈从堆中读取这3个变量时这3个变量已经为修改后的值如下所示③、消费者线程读缓冲区byte[]数组的过程中会不断地执行out读指针以读取缓冲区byte[]数组中的可用字节并返回直到out读指针in写指针修改in写指针-1并且每次同步执行PipedInputStream.class::read()函数时都会更新Thread readSide 当前这个消费者线程Thread对象消费者线程也会把自己线程栈中修改的变量最终刷新到堆中PipedInputStream对象中以确保其它消费者线程的线程栈从堆中读取这3个变量时这3个变量已经为修改后的值如下所示④、更新in写指针-1后消费者线程再次同步执行PipedInputStream.class::read()函数时如果PipedInputStream::boolean closedByWriter变量为true则会返回-13.2、加锁循环写和非加锁循环读到byte[]数组b中再处理package com.chelong.pipe; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class PipeForTransferInThread { public static void main(String[] args) throws IOException, InterruptedException { final PipedOutputStream output new PipedOutputStream(); final PipedInputStream input new PipedInputStream(output); //生产者线程 Thread producer new Thread(new Runnable() { Override public void run() { for (int i 0; i 3; i) { synchronized (input) { try { // input.wait(); output.write(Hello world, pipe!.getBytes()); input.wait();//释放锁并无限等待直到消费者线程consumer 执行notifyAll()函数来唤醒当前阻塞 } catch (Exception e) { e.printStackTrace(); } } } } },生产者线程); //消费者线程 Thread consumer new Thread(new Runnable() { Override public void run() { try { byte[] b new byte[1024];//1KB int readBytes -1; long lastTime System.currentTimeMillis(); while ((readBytes input.read(b, 0, b.length)) ! -1) { long curTime System.currentTimeMillis(); System.out.print(Thread.currentThread().getName()本次读取花费时间 (curTime - lastTime) ms读到的数据是); lastTime curTime; for (int i 0; i readBytes; i) { System.out.print((char) b[i]);//模拟处理字节数据 } System.out.println(); } } catch (IOException e) { e.printStackTrace(); } } },消费者线程); producer.start();//生产者线程启动 consumer.start();//消费者线程启动 } }程序运行结果如下所示main线程构造PipedOutputStream生产者和PipedInputStream消费者的过程可以参考3.1向PipedOutputStream生产者写字节数据的生产者线程的执行过程可以参考3.1从PipedInputStream消费者读取字节数据的消费者线程的执行过程如下3.2.1、加锁循环写和非加锁循环读到byte[]数组b中再处理时1个生产者线程和1个消费者线程处理数据的过程标题3.2中的代码的整个执行过程如下①、main线程初始化一个缓冲区byte[]数组长度为1024默认值如下所示②、然后生产者线程通过不断的压栈来完成函数之间的调用最终执行PipedInputStream.class::receive(byte b[], int off, int len)函数来对缓冲区byte[]数组进行填充并且先在自己的线程栈中更新in写指针17out读指针0writeSide当前这个生产者线程Thread对象 如下所示当生产者线程对缓冲区byte[]数组填充完成之后再执行标题3.2中的代码input.wait();这行代码会释放锁并让生产者线程进入无限等待直到消费者线程consumer执行notifyAll()函数来唤醒当前这个生产者线程。在这之前生产者线程会将自己线程栈中的in写指针17out读指针0writeSide当前这个生产者线程这3个变量更新到主内存也就是堆中的PipedInputStream对象中。③、消费者线程读缓冲区byte[]数组的过程也是通过不断的压栈来完成函数之间的调用最终执行PipedInputStream::read()函数试探性的读取1个字节和PipedInputStream::read(byte b[], int off, int len)函数读取剩余其它的字节将步骤②中生产者线程写入到缓冲区byte[]数组中的17个字节读取出来附言最终消费者线程也会将自己线程栈中的in写指针 -1out读指针 17writeSide当前这个消费者线程这3个变量更新到主内存也就是堆中的PipedInputStream对象中。因此本次消费者线程从缓冲区byte[]数组中读数据的过程中没有执行read()函数中的wait(1000)这一行代码如下所以本次消费者线程从缓冲区byte[]数组中读取数据到消费者线程中自己创建的byte[]数组中时只花费了0ms接下来当消费者线程将步骤②中生产者线程写入到缓冲区byte[]数组中的17个字节读取出来以后通过System.arraycopy()函数复制到了消费者线程中自己创建的byte[]数组中消费者线程会遍历从缓冲区读到的这个byte[]数组来处理这些数据如下所示标题3.2中的代码片段//标题3.2中的代码片段 for (int i 0; i readBytes; i) { System.out.print((char) b[i]);//模拟处理字节数据 }然后当消费者线程再次执行//标题3.2中的代码片段 input.read(b, 0, b.length)从缓冲区byte[]数组中读数据到自己创建的byte[]数组中时由于此时in写指针-1并且当下图中的其它5个条件都不成立时唤醒执行了input.wait()的生产者线程然后当前这个正在从缓冲区(byte数组)中读数据的消费者线程执行wait 1000ms 如下④、当生产者线程被消费者线程执行的notifyAll();唤醒之后会再次通过不断的压栈来完成函数之间的调用再次执行PipedInputStream.class::receive(byte b[], int off, int len)函数来对缓冲区byte[]数组进行填充并且先在自己的线程栈中先更新in写指针17out读指针0writeSide当前这个生产者线程Thread对象 如下所示当生产者线程对缓冲区byte[]数组填充完成之后再执行标题3.2中的代码input.wait();这行代码会释放锁并让生产者线程进入无限等待直到消费者线程consumer执行notifyAll()函数来唤醒当前这个生产者线程。在这之前生产者线程会将自己线程栈中的in写指针17out读指针0writeSide当前这个生产者线程这3个变量更新到主内存也就是堆中的PipedInputStream对象中。如下所示⑤、消费者线程在第③步执行了wait(1000);在等待了1000ms之后消费者线程会自动唤醒继续执行此时自己线程栈中的in写指针 -1out读指针 17已经被第④步中的生产者线程修改为in写指针17out读指针0生产者线程不会直接修改消费者线程栈中的变量生产者线程会先将自己线程栈中in写指针out读指针变量的值修改到主内存中然后消费者线程会自己将主内存中的这2个变量值刷新到消费者自己的线程栈中如下所示

相关文章:

PipedInputStream和PipedOutputStream的源码分析和使用方法详细分析

一、PipedOutputStream(生产者)源码——向PipedInputStream(消费者)中的缓冲区(byte[]数组)写入字节数据的输出Stream(生产者)package java.io;import java.io.*;public class Piped…...

图像增强技术指南:让模糊图片重获新生的实用方法

图像增强技术指南:让模糊图片重获新生的实用方法 【免费下载链接】Real-ESRGAN-ncnn-vulkan NCNN implementation of Real-ESRGAN. Real-ESRGAN aims at developing Practical Algorithms for General Image Restoration. 项目地址: https://gitcode.com/gh_mirro…...

DMA内存访问与Cheat Engine插件开发全指南:零基础配置到高效内存分析

DMA内存访问与Cheat Engine插件开发全指南:零基础配置到高效内存分析 【免费下载链接】CheatEngine-DMA Cheat Engine Plugin for DMA users 项目地址: https://gitcode.com/gh_mirrors/ch/CheatEngine-DMA CheatEngine-DMA是一款专为技术爱好者和开发者设计…...

AI辅助开发:让快马智能生成代码优化50台云桌面的动态资源调度策略

今天想和大家分享一个特别实用的技术实践——如何用AI辅助开发来优化云桌面的资源调度。最近在做一个项目,需要在一台主机上运行50台云桌面,这对资源调度提出了很高的要求。传统的静态分配方式显然不够灵活,于是我开始探索AI辅助开发的解决方…...

Claude Code Harness入门到精通,收藏这一篇就够了!

01 真正的难点, 在模型之外的 Harness Claude Code 的架构核心,是一个「Harness」本地运行时的外壳,更多地是依靠 Harness 的工程化与可靠性。 根据公开镜像仓库 nirholas/claude-code,Claude Code 的 TypeScript 源代码跨越了…...

告别驱动臃肿:Radeon Software Slimmer轻量优化实现显卡性能释放

告别驱动臃肿:Radeon Software Slimmer轻量优化实现显卡性能释放 【免费下载链接】RadeonSoftwareSlimmer Radeon Software Slimmer is a utility to trim down the bloat with Radeon Software for AMD GPUs on Microsoft Windows. 项目地址: https://gitcode.co…...

Qwen3-14B WebUI权限分级:管理员/普通用户/只读访客三类角色配置

Qwen3-14B WebUI权限分级:管理员/普通用户/只读访客三类角色配置 1. 权限分级的重要性与场景需求 在私有化部署Qwen3-14B模型时,企业或团队通常需要根据不同成员的职责分配不同的操作权限。合理的权限分级能够: 保障系统安全:防…...

2026届学术党必备的六大降重复率工具推荐榜单

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 基于自然语言处理技术的智能应用是AI写作工具,它能辅助用户完成文本生成、语法纠…...

2025届学术党必备的十大AI辅助写作网站实际效果

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在于学术写作范畴之内,AI工具正一步一步地变成提高论文质量以及写作效率的关键辅…...

2025最权威的十大降AI率方案推荐榜单

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 人工智慧写作工具凭借自然语言生成这项技术,能够快速产出契合语法规则的文本内容…...

腾讯云推出“领域虾”CloudQ:把企业云上治理,装进你每天都在用的聊天框

好家伙,腾讯云又给龙虾市场上新了。最近,腾讯云官宣的 CloudQ IT 老师傅(全球首款 ITOM“领域虾”),直接把云上的技术难题给办了。你甚至都不用登录控制台、不用敲命令,在微信里聊聊天就能完成架构巡检、风…...

安全测试左移:在CI/CD中集成安全扫描

安全困境与左移的必要性 在快速迭代的敏捷开发与DevOps浪潮中,软件交付的周期被急剧压缩,然而,传统安全测试模式却显得格格不入。测试阶段末期的一次性渗透测试或代码审计,发现的往往是积重难返的高危漏洞,修复成本高…...

Windows运行库终极解决方案:VisualCppRedist AIO完全指南

Windows运行库终极解决方案:VisualCppRedist AIO完全指南 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这种情况:满怀…...

基于AI的测试用例自动生成:效果与局限

随着人工智能技术的飞速发展与在软件工程领域的深度融合,测试用例自动生成正经历一场深刻的变革。传统的测试用例设计高度依赖测试工程师的经验与手工劳动,面临效率瓶颈、覆盖率不足、维护成本高昂等诸多挑战。以机器学习、自然语言处理、知识图谱等为代…...

OpenClaw学习路径:从Phi-3-mini-128k-instruct对接到复杂自动化编排

OpenClaw学习路径:从Phi-3-mini-128k-instruct对接到复杂自动化编排 1. 为什么选择OpenClawPhi-3-mini组合 去年我在整理学术文献时,每天要花3小时重复操作:下载PDF→提取关键段落→归类到不同主题文件夹→生成摘要。直到发现OpenClaw这个能…...

数字孪生技术的测试方法论:虚拟与现实的同步

对于软件测试从业者而言,数字孪生技术的崛起正引发一场深刻的范式革命。测试的对象已从传统的、边界清晰的软件系统,演变为一个由物理实体、动态数字模型、实时数据流以及控制闭环构成的复杂异构系统。这一转变将测试工作的核心,从验证“功能…...

挖掘机伸缩臂的原理

挖掘机伸缩臂是装载于挖掘机上的一种特殊工作装置,旨在灵活扩展挖掘机的作业半径、挖掘深度。它集挖掘机、起重机、抓木器等多种工程机械结构的优点于一体,结构紧凑且重量轻,操作简便,作业效率高。挖掘机伸缩臂的工作原理主要依赖…...

Phi-4-mini-reasoning Chainlit插件开发:集成代码执行与结果可视化

Phi-4-mini-reasoning Chainlit插件开发:集成代码执行与结果可视化 1. 项目概述 Phi-4-mini-reasoning 是一个基于合成数据构建的轻量级开源模型,专注于高质量、密集推理的数据处理能力。作为Phi-4模型家族的一员,它特别强化了数学推理能力…...

定制化 H 型滑触线:抗温抗腐,高效赋能极端工业场景

【超越极限,安全稳定——为极端环境量身定制的H型滑触线】 在充满挑战的工作环境中,选择恰当的电源输送解决方案不仅是提高效率的关键,更是确保安全与可持续发展的基石。针对高温高湿、极寒或强腐蚀性场所特别设计的H型滑触线系列产品&#x…...

Phi-4-mini-reasoning实用刚需:3.8B模型在边缘服务器部署可行性分析

Phi-4-mini-reasoning实用刚需:3.8B模型在边缘服务器部署可行性分析 1. 模型概述与核心优势 Phi-4-mini-reasoning是微软推出的3.8B参数轻量级开源模型,专为数学推理、逻辑推导和多步解题等强逻辑任务设计。这个模型最突出的特点是"小参数、强推理…...

LeaguePrank:英雄联盟客户端个性化引擎完全指南

LeaguePrank:英雄联盟客户端个性化引擎完全指南 【免费下载链接】LeaguePrank 项目地址: https://gitcode.com/gh_mirrors/le/LeaguePrank 1. 价值定位:重新定义游戏界面体验 1.1 产品核心价值 LeaguePrank作为开源的英雄联盟客户端个性化引擎…...

个人学习实时数据管道框架--4 数据入湖实战

4.1 环境准备 1. 安装 Java 8+ 和 Maven 3.6+ 2. 下载项目代码:git clone <项目地址> 3. 配置环境变量:JAVA_HOME, HADOOP_HOME 4.2 配置文件 核心配置文件 application.properties: # Flink 配置 flink.job.name=VehicleSOCPipeline flink.parallelism=4 flink…...

【typst-rs】Typst CLI 入口代码解析

这段代码是 Typst CLI 工具的入口点&#xff08;main.rs&#xff09;&#xff0c;Typst 是一个基于 Rust 的排版系统。让我详细解析这段代码的结构和功能。 模块声明 (1-18行) mod args; mod compile; mod completions; mod deps; mod download; mod eval; mod fonts; mod gree…...

50 岁苹果:变与不变的科技传奇

从车库小作坊到行业巨头&#xff1a;苹果早期的革命之路1976 年&#xff0c;在科技巨头普遍认为电脑仅供企业和政府使用时&#xff0c;乔布斯和沃兹尼亚克在车库用零件组装出 Apple I&#xff0c;虽像半成品却为苹果拿到启动资金。次年的 Apple II 才是关键&#xff0c;它是第一…...

英语祈使句结构

英语祈使句:极简结构 + 直白例句(一看就懂) 核心: 主语一律省略(默认 You 你 / 你们),直接用动词开头,用来命令 / 请求 / 禁止 一、基础核心结构(3 种万能模板) 1 肯定祈使句(直接让做某事) 结构:动词原形 + 其他成分(宾语 / 状语) 例句: Open the door.…...

数据仓库建模:事实表类型详解与选型实战指南

数据仓库建模&#xff1a;事实表类型详解与选型实战指南一、引言二、定义&#xff1a;什么是数据仓库事实表&#xff1f;三、数据仓库中三大核心事实表类型3.1 类型1&#xff1a;事务事实表&#xff08;Transaction Fact Table&#xff09;3.2 类型2&#xff1a;周期快照事实表…...

新手入门网络安全:用快马AI生成你的第一个密码强度检测器

最近在自学网络安全基础知识&#xff0c;发现密码强度检测是个很好的入门实践。作为新手&#xff0c;我尝试用InsCode(快马)平台的AI辅助功能&#xff0c;快速生成了一个密码强度检测器&#xff0c;整个过程特别适合零基础学习者。这里记录下实现思路和关键要点&#xff1a; 密…...

深入理解Vue的响应式原理:从Object.defineProperty到Proxy

Vue的响应式系统是其核心特性之一&#xff0c;它使得数据变化能够自动驱动视图更新。从Vue 2.x的Object.defineProperty到Vue 3.x的Proxy&#xff0c;这一演进不仅是技术实现上的突破&#xff0c;更体现了Vue对性能、兼容性和开发体验的深度思考。以下从技术原理、实现差异、性…...

U8/修改采购入库单辅计量单位

SELECT cAssUnit,Inventory.cSTComUnitCode,* --UPDATE rdrecords01 SET cAssUnit Inventory.cSTComUnitCode FROM rdrecords01 LEFT JOIN Inventory ON rdrecords01.cInvCode Inventory.cInvCode...

DXVK:彻底解决Linux游戏兼容性难题的Vulkan翻译层

DXVK&#xff1a;彻底解决Linux游戏兼容性难题的Vulkan翻译层 【免费下载链接】dxvk Vulkan-based implementation of D3D8, 9, 10 and 11 for Linux / Wine 项目地址: https://gitcode.com/gh_mirrors/dx/dxvk 你是否曾经因为心爱的Windows游戏无法在Linux上运行而感到…...