Eu recomendo fortemente que você aprenda a usar o SocketChannel, nem que seja do jeito feijão com arroz. É mais fácil do que usar os streams… e mais eficiente também.
A diferença é a que você falou. No primeiro caso, cria-se um buffer, que nada mais é do que um array, onde os bytes são gravados. A diferença é maior no caso de arquivos, experimente fazer um teste com eles. Isso porque uma leitura mais continua do arquivo otimiza o uso do HD.
Então, mesmo que você faça a leitura de um só byte (com read()) o BufferedInputStream irá ler um bloco inteiro, do tamanho do buffer e te fornecer um byte só. No segundo byte requisitado, não existe leitura, ele te fornece o segundo byte do buffer. Quando o buffer acaba, ele lê outro bloco, do tamanho do buffer.
A gravação ocorre da mesma forma, mas no sentido inverso. Você acumula os dados a serem gravados num buffer e, quando o buffer está cheio, ele é enviado para o arquivo ou socket de uma só vez. Por isso existem comandos como flush(), que exigem que a descarga dos buffers seja feita. O TCP/IP tem esse mecanismo automático, mas você pode desliga-lo usando algumas opções do Socket (setTCPNoDelay(true), se eu não me engano).
Se você é tão curioso à respeito do que ocorre por baixo dos panos, dá uma lida no padrão Decorator (que é o dos InputStreams) e abra o código fonte dessas classes e tente entender. Eu já fiz isso várias vezes e, na maioria dos casos, não é tão difícil quanto parece.