前言:
1、需要少量硬件知识 需要懂IO流
2、作为总结 内容有点多
3、打印机分为串口打印、网口打印、并口打印等,一般情况 只有指令打印才需要去区分,而驱动打印的话 ,只需要知道打印机名字就好了 Printable+awt+javax.print
4、一般情况 热敏打印机 驱动和指令两种打印方式 速度基本差不多 因为都很快 ;而老旧的针式打印机 驱动打印会特别慢,或者热敏打印 但不适合装驱动时 需要指令打印。

一、驱动打印:

// google的core 包,用于网址转二维码 不需要相应代码可删除
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;

import javax.imageio.ImageIO;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashAttributeSet;
import javax.print.attribute.standard.PrinterName;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.awt.print.*;
import java.io.*;
/**
*@author by qkj
*/
public class PrintDemo {
    public static void main(String[] args)
    {
    	// 可以理解为新建一个打印面板
		Book book = new Book();
        PageFormat pf = new PageFormat();
        pf.setOrientation(1);
        // 打印纸张 以及设置规格等
        Paper p = new Paper();
        p.setSize(250, 750);
        p.setImageableArea(0.0D, 0.0D, 250, 750);
        pf.setPaper(p);
        
        Printable printable = new Printable(){
            @Override
            public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
            //Graphics是抽象类 ,多态 转型
                Graphics2D g2 = (Graphics2D)graphics;
                BufferedImage logo = null;
                try {
                // 图片的路径 这里是取的项目根目录 绝对路径
                //(以及某些需要打包成exe的java项目 一定要用绝对路径 不能取src底下根目录)
                    String strBmpFile = System.getProperty("user.dir")
                            + "\\logo1.bmp";
                    InputStream is = new FileInputStream(strBmpFile);
                    logo = ImageIO.read(is);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // 打印图片
                g2.drawImage(logo, (BufferedImageOp)null, 25, 10);
                //打印文本
                g2.drawString(" - - - - - - - - - - - - - - - - - -  ", 20.0F, 100.0F);
                g2.drawString("测试: 你好", 25.0F, 115.0F);
                g2.drawString("测试: 你好" , 25.0F, 130.0F);
                g2.drawString("测试: 你好" , 25.0F, 145.0F);
                g2.drawString("测试: 你好" , 25.0F, 160.0F);
                g2.drawString("测试: 你好" , 25.0F, 175.0F);
                g2.drawString("测试: 你好" , 25.0F, 190.0F);
                g2.drawString("测试: 你好" , 25.0F, 205.0F);
                g2.drawString("测试: 你好:测试", 25.0F, 220.0F);
                g2.drawString("测试: 你好:测试" , 25.0F, 235.0F);
                g2.drawString("测试: 你好时间:10:00" , 25.0F, 250.0F);
                g2.drawString("测试: 你好时间:10:00" , 25.0F, 265.0F);
              
				// 网址转二维码
                BufferedImage image = null;
                try {
                    BitMatrix byteMatrix = new MultiFormatWriter().encode(new String("www.baidu.com".getBytes(), "iso-8859-1"),
                            BarcodeFormat.QR_CODE, 175, 175);

                    int width = byteMatrix.getWidth();
                    int height = byteMatrix.getHeight();
                    final int BLACK = 0xFF000000;
                    final int WHITE = 0xFFFFFFFF;
                    image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                    for (int x = 0; x < width; x++) {
                        for (int y = 0; y < height; y++) {
                            image.setRGB(x, y, byteMatrix.get(x, y) ? BLACK : WHITE);
                        }
                    }
                } catch (WriterException e) {
                    e.printStackTrace();
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }


// 图片转单色位 需要则取之 某些老旧针式打印机中 指令打印只能打单色位bmp图片
                String path = System.getProperty("user.dir") + "\\code.bmp";
                File file = new File(path);

                int h = 0;
                int w = 0;
                int[] pixels = new int[0];
                try {
                    OutputStream fileOutputStream = new FileOutputStream(file);
                    ImageIO.write(image, "bmp", fileOutputStream);
                    fileOutputStream.close();
                    BufferedImage sourceImg = ImageIO.read(new File(path));
                    h = sourceImg.getHeight();
                    w = sourceImg.getWidth();

                    pixels = new int[w * h];
                    PixelGrabber pixelGrabber = new PixelGrabber(sourceImg, 0, 0, w, h, pixels, 0, w);
                    pixelGrabber.grabPixels();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int gray;
                for (int j = 0; j < h; j++) {
                    for (int i1 = 0; i1 < w; i1++) { // 由红,绿,蓝值得到灰度值
                        gray = (int) (((pixels[w * j + i1] >> 16) & 0xff) * 0.8);
                        gray += (int) (((pixels[w * j + i1] >> 8) & 0xff) * 0.1);
                        gray += (int) (((pixels[w * j + i1]) & 0xff) * 0.1);
                        pixels[w * j + i1] = (255 << 24) | (gray << 16) | (gray << 8) | gray;
                    }
                }

                MemoryImageSource imageSource = new MemoryImageSource(w, h, pixels, 0, w);
                Image newimage = Toolkit.getDefaultToolkit().createImage(imageSource);
                BufferedImage bufImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
                bufImage.createGraphics().drawImage(newimage, 0, 0, null);

                try {
                    ImageIO.write(bufImage, "BMP", new File(path));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                g2.drawImage(bufImage, (BufferedImageOp)null, 15, 335);
              
                return 0;
            }

        };
         book.append(printable, pf);
        // 获取打印服务对象
        PrinterJob job = PrinterJob.getPrinterJob();
        job.setPageable(book);
        try {
            HashAttributeSet hs = new HashAttributeSet();
            // 实际使用一定要写配置文件 不能直接写死打印机名字 
            //打印机名字为控制面板-打印机 属性里面的全名,例如A打印机 使用的是B打印机通用驱动,名字以B为准
            String printerName = "TP805";

            hs.add(new PrinterName(printerName, null));

            PrintService[] pss = PrintServiceLookup.lookupPrintServices(null, hs);

            if (pss.length == 0 /*|| pss1.length == 0*/) {
                System.out.println("无法找到打印机:" + printerName);

            }

            // 设置打印类
            job.setPageable(book);
            //添加指定的打印机
            job.setPrintService(pss[0]);
            // 打印
            job.print();

        } catch (PrinterException e) {
            e.printStackTrace();
        }
    }
}

二、难点:指令打印
首先需要知道:打印机有网口(类似于网线口 但是通常都是比网线口小一点),串口 (COM 上4下5 九个点 梯形状),并口(LPT 比串口长很多的梯形状),USB口 …
有的电脑主机可能没有串口,那么需要使用USB转串线 (电脑需要安装usb转串的驱动),效果等同于串口。

  1. 串口
    需要下载RXTX一个jar包,两个dll文件,dll严格区分电脑32,,64位!并将其放入对应目录下。
    优点:指令打印速度远大于驱动打印;
    缺点:需要配置环境,如果用户规模大 且用户完全不懂技术 需要自行考虑时间与后果 是否能够承受

b java s 打印机 java 针式打印机_Image

package printDemo;
// rxtx包
import gnu.io.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.TooManyListenersException;
/**
*@author by qkj
*/
public class NewPrintDemo implements SerialPortEventListener {


    private static boolean isOpen = false;
    static Set<CommPortIdentifier> portList = new HashSet<CommPortIdentifier>();
  
    final static String appName = "MyApp";
    private static InputStream is;
    private static OutputStream os;
    private static SerialPort serialPort;
    byte[] readBuffer = new byte[100];
    static OutputStream outputStream;

    public static void main(String[] args) {
        Enumeration tempPortList;  //枚举类
        CommPortIdentifier portIp;
        tempPortList = CommPortIdentifier.getPortIdentifiers();
        while (tempPortList.hasMoreElements()) {
            //在这里可以调用getPortType方法返回端口类型,串口为CommPortIdentifier.PORT_SERIAL
            portIp = (CommPortIdentifier) tempPortList.nextElement();
            portList.add(portIp);
            int portType = portIp.getPortType();
            // 得到串口类型,并且串口名字为 XXX  com6表示 串口接入电脑的com6口,(在设备管理器可查看)
            if (portType == (CommPortIdentifier.PORT_SERIAL) && portIp.getName().equals("COM6")) {
                try {
                // com口以及超时时间(毫秒) 
                // 注:这里很可能报错 unknown application 实际意义为 串口打开失败 目前通过测试 
                //已知原因有: 1.串口占用,例如你写两个demo程序,另一个还开着占用了该串口 一定会报错;
                //2.串口识别失败,比如插入电脑 没反应 或者串口坏了 这涉及到硬件问题 无法深一步解释
                    serialPort = (SerialPort) portIp.open("COM6", 5000);
                    // 显而易见 9600是打印机默认波特率 
                    serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                    // 输出流
                    outputStream = serialPort.getOutputStream();

 OutputStreamWriter writer = new OutputStreamWriter(SimpleWrite.outputStream, "UTF-8");

                    byte[] cmd = new byte[3];
                    cmd[0] = 0x1B;
                    cmd[1] = 'J';
                    cmd[2] = 0x0D;


                    outputStream.write(cmd);
                    outputStream.close();
                  

                  //    SimpleWrite.outputStream.write(cmd);
				 //     writer.write("\n\n");
				 //     writer.write(" 单号:11111\n");
                //图片打印失败,打印出来内容实际为图片用文本格式打开的乱码内容,暂时不清楚怎么通过该IO
                //方式打印 不知道是自身对IO理解错误还是和打印机本身有关,但需要注意的是 很多针式老款打				     	 
                //印机 只能打印单色位图片,一定要严格转成单色位
                // ImageIO.write(read, "jpg", outputStream);




                } catch (Exception e) {
                    e.printStackTrace();
                }


            }
        }


    }

    public Set<CommPortIdentifier> getPortList() {
        Enumeration tempPortList;  //枚举类
        CommPortIdentifier portIp;
        tempPortList = CommPortIdentifier.getPortIdentifiers();
        /*不带参数的getPortIdentifiers方法获得一个枚举对象,该对象又包含了系统中管理每个端口的CommPortIdentifier对象。
         * 注意这里的端口不仅仅是指串口,也包括并口。
         * 这个方法还可以带参数。
         * getPortIdentifiers(CommPort)获得与已经被应用程序打开的端口相对应的CommPortIdentifier对象。
         * getPortIdentifier(String portName)获取指定端口名(比如“COM1”)的CommPortIdentifier对象。
         */
        while (tempPortList.hasMoreElements()) {
            //在这里可以调用getPortType方法返回端口类型,串口为CommPortIdentifier.PORT_SERIAL
            portIp = (CommPortIdentifier) tempPortList.nextElement();
            portList.add(portIp);
        }
        return portList;
    }

    public boolean openSerialPort(CommPortIdentifier portIp, int delay) {
        try {
            serialPort = (SerialPort) portIp.open(appName, delay);
            /* open方法打开通讯端口,获得一个CommPort对象。它使程序独占端口。
             * 如果端口正被其他应用程序占用,将使用 CommPortOwnershipListener事件机制,传递一个PORT_OWNERSHIP_REQUESTED事件。
             * 每个端口都关联一个 InputStream 和一个OutputStream。
             * 如果端口是用open方法打开的,那么任何的getInputStream都将返回相同的数据流对象,除非有close 被调用。
             * 有两个参数,第一个为应用程序名;第二个参数是在端口打开时阻塞等待的毫秒数。
             */
        } catch (PortInUseException e) {
            return false;
        }
        try {
            is = serialPort.getInputStream();/*获取端口的输入流对象*/
            os = serialPort.getOutputStream();/*获取端口的输出流对象*/
        } catch (IOException e) {
            return false;
        }
        try {
            serialPort.addEventListener(this);/*注册一个SerialPortEventListener事件来监听串口事件*/
        } catch (TooManyListenersException e) {
            return false;
        }
        serialPort.notifyOnDataAvailable(true);/*数据可用*/
        try {
            /*设置串口初始化参数,依次是波特率,数据位,停止位和校验*/
            serialPort.setSerialPortParams(4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
        } catch (UnsupportedCommOperationException e) {
            return false;
        }
        return true;
    }

    public boolean closeSerialPort() {
        if (isOpen) {
            try {
                is.close();
                os.close();
                serialPort.notifyOnDataAvailable(false);
                serialPort.removeEventListener();
                serialPort.close();
                isOpen = false;
            } catch (IOException e) {
                return false;
            }
        }
        return true;
    }

    public boolean sendMessage(String message) {
        try {
            os.write(message.getBytes());
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        /*
         * 此处省略一下事件,可酌情添加
         *  SerialPortEvent.BI:/*Break interrupt,通讯中断
         *  SerialPortEvent.OE:/*Overrun error,溢位错误
         *  SerialPortEvent.FE:/*Framing error,传帧错误
         *  SerialPortEvent.PE:/*Parity error,校验错误
         *  SerialPortEvent.CD:/*Carrier detect,载波检测
         *  SerialPortEvent.CTS:/*Clear to send,清除发送
         *  SerialPortEvent.DSR:/*Data set ready,数据设备就绪
         *  SerialPortEvent.RI:/*Ring indicator,响铃指示
         *  SerialPortEvent.OUTPUT_BUFFER_EMPTY:/*Output buffer is empty,输出缓冲区清空
         */
        if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
            /*Data available at the serial port,端口有可用数据。读到缓冲数组,输出到终端*/
            try {
                while (is.available() > 0) {
                    is.read(readBuffer);//收到的数据再此,可视情况处理
                }

            } catch (IOException e) {
            }
        }
    }


}

2.网口,和串口打印基本相似,需要在电脑配置相应的IP地址,换成socket获取输出流,指令数组放入输出流即可

3.补充 :通过其它方式:
有部分打印机厂商会提供dll文件以及相应demo, dll里面也是通过指令方式打印,并且会提供打印位图的方法,因为指令大多数与爱普生的一致,所以大部分打印机都是通用的,但可能存在部分兼容性问题,比如打印不全 ,没出问题还好 出现问题很难解决!! 如果有需要可以直接联系相关打印机厂商,涉及商业层面 代码以及dll不公开!